Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions packages/insomnia/src/common/interactive-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,31 @@ import { showSettingsModal } from '~/ui/components/modals/settings-modal';

const PREF_SECURITY = 'Insomnia’s Preferences → Security';

const interactives = [{ text: PREF_SECURITY, handler: () => showSettingsModal({ tab: 'general' }) }];
interface InteractiveItem {
text: string;
handler?: () => void;
Component?: any;
}

const interactives: InteractiveItem[] = [
{ text: PREF_SECURITY, handler: () => showSettingsModal({ tab: 'general' }) },
{ text: '<br/>', Component: 'br' },
];

export function buildInteractiveMessage(message: string) {
const parts = [];
let prev = '';
for (let i = 0; i < message.length; i++) {
let matched = false;
for (const { text, handler } of interactives) {
for (const item of interactives) {
const { text } = item;
matched = message.startsWith(text, i);
if (matched) {
if (prev) {
parts.push({ text: prev });
prev = '';
}
parts.push({ text, handler });
parts.push(item);
i += text.length - 1;
break;
}
Expand Down
49 changes: 49 additions & 0 deletions packages/insomnia/src/common/validators.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { CaCertificate } from '../models/ca-certificate';
import type { ClientCertificate } from '../models/client-certificate';
import type { Request } from '../models/request';
import type { Settings } from '../models/settings';
import type { RenderedRequest } from '../templating/types';

Expand Down Expand Up @@ -73,3 +74,51 @@ export function isFsAccessingAllowed(

// case5: check "file" template tags, which is checked in tag implementation
}

const FILE_VAR_REGEX = /\{\%\s*file\s+['"](.+?)['"]\s*\%\}/;
export function getAccessingFiles(request: Request | RenderedRequest): string[] {
const result = new Set<string>();
function checkValues(list: { disabled?: boolean; value?: string }[] | undefined) {
list?.forEach(param => {
if (!param.disabled && param.value) {
const paramMatch = param.value.match(FILE_VAR_REGEX);
if (paramMatch && paramMatch[1]) {
result.add(paramMatch[1]);
}
}
});
}

// body
const { url, body, parameters, headers } = request;
if (body.fileName) {
result.add(body.fileName);
}
body.params?.forEach(param => {
if (param.type === 'file' && !param.disabled && param.fileName) {
result.add(param.fileName);
}
});

// url
const urlMatch = url.match(FILE_VAR_REGEX);
if (urlMatch && urlMatch[1]) {
result.add(urlMatch[1]);
}

// parameters
checkValues(parameters);

// headers
checkValues(headers);

return [...result];
}

export function getNoAuthFiles(request: Request | RenderedRequest, settings: Settings) {
const toCheckFiles = getAccessingFiles(request);
return toCheckFiles.filter(fileName => {
const allowed = settings?.dataFolders.some(folder => folder !== '' && fileName.startsWith(folder));
return !allowed;
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { href, redirect } from 'react-router';
import { v4 as uuidv4 } from 'uuid';

import { getContentDispositionHeader } from '~/common/misc';
import { isFsAccessingAllowed } from '~/common/validators';
import { getNoAuthFiles, isFsAccessingAllowed } from '~/common/validators';
import type { ResponsePatch } from '~/main/network/libcurl-promise';
import type { TimingStep } from '~/main/network/request-timing';
import * as models from '~/models';
Expand All @@ -32,6 +32,8 @@ import { createFetcherSubmitHook } from '~/utils/router';
import type { RequestTestResult } from '../../../insomnia-scripting-environment/src/objects';
import type { Route } from './+types/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.send';

const PREF_SECURITY = 'Insomnia’s Preferences → Security';

export interface SendActionParams {
requestId: string;
shouldPromptForPathAfterResponse?: boolean;
Expand Down Expand Up @@ -140,6 +142,13 @@ export const sendActionImplementation = async (options: {
iterationCount,
runtime,
);

const noAuthFiles = getNoAuthFiles(mutatedContext.request, mutatedContext.settings);
if (noAuthFiles.length > 0) {
console.log('noAuthFiles', noAuthFiles);
throw `Insomnia cannot access the following files. You must specify which directories Insomnia can access in ${PREF_SECURITY}.<br/>${noAuthFiles.join('<br/>')}`;
}

if ('error' in mutatedContext) {
window.main.completeExecutionStep({ requestId });
throw {
Expand Down
8 changes: 6 additions & 2 deletions packages/insomnia/src/ui/components/modals/alert-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface AlertModalOptions {
message?: ReactNode;
addCancel?: boolean;
okLabel?: React.ReactNode;
operationSlot?: React.ReactNode;
onConfirm?: () => void | Promise<void>;
bodyClassName?: string;
}
Expand All @@ -26,6 +27,7 @@ export const AlertModal = forwardRef<AlertModalHandle, ModalProps>((_, ref) => {
addCancel: false,
okLabel: '',
bodyClassName: '',
operationSlot: undefined,
});

useImperativeHandle(
Expand All @@ -34,28 +36,30 @@ export const AlertModal = forwardRef<AlertModalHandle, ModalProps>((_, ref) => {
hide: () => {
modalRef.current?.hide();
},
show: ({ title, message, addCancel, onConfirm, okLabel, bodyClassName = '' }) => {
show: ({ title, message, addCancel, onConfirm, okLabel, bodyClassName = '', operationSlot }) => {
setState({
title,
message,
addCancel,
okLabel,
onConfirm,
bodyClassName,
operationSlot,
});
modalRef.current?.show();
},
}),
[],
);

const { message, title, addCancel, okLabel, bodyClassName } = state;
const { message, title, addCancel, okLabel, bodyClassName, operationSlot } = state;
return (
<Modal ref={modalRef}>
<ModalHeader>{title || 'Uh Oh!'}</ModalHeader>
<ModalBody className={classnames(['wide', 'pad', bodyClassName])}>{message}</ModalBody>
<ModalFooter>
<div>
{operationSlot && operationSlot}
{addCancel ? (
<button className="btn" onClick={() => modalRef.current?.hide()}>
Cancel
Expand Down
23 changes: 21 additions & 2 deletions packages/insomnia/src/ui/components/request-url-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { buildQueryStringFromParams, joinUrlAndQueryString } from '../../utils/u
import { SegmentEvent } from '../analytics';
import { useInsomniaTabContext } from '../context/app/insomnia-tab-context';
import { useReadyState } from '../hooks/use-ready-state';
import { useRequestPatcher } from '../hooks/use-request';
import { useDataFoldersPatcher, useRequestPatcher } from '../hooks/use-request';
import { useRequestMetaPatcher } from '../hooks/use-request';
import { useTimeoutWhen } from '../hooks/useTimeoutWhen';
import { Dropdown, type DropdownHandle, DropdownItem, DropdownSection, ItemContent } from './base/dropdown';
Expand Down Expand Up @@ -66,24 +66,41 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(
const [showInputVaultKeyModal, setShowInputVaultKeyModal] = useState(false);
const [undefinedEnvironmentVariables, setUndefinedEnvironmentVariables] = useState('');
const undefinedEnvironmentVariableList = undefinedEnvironmentVariables?.split(',');
const dataFoldersPatcher = useDataFoldersPatcher();
if (searchParams.has('error')) {
if (searchParams.has('envVariableMissing') && searchParams.get('undefinedEnvironmentVariables')) {
setShowEnvVariableMissingModal(true);
setUndefinedEnvironmentVariables(searchParams.get('undefinedEnvironmentVariables')!);
} else {
// only for request render error
const errorMessage = searchParams.get('error') || '';
// TODO: error type and more details for example which file are missing permission
const messages = errorMessage.includes('Insomnia cannot access')
? buildInteractiveMessage(errorMessage)
: [{ text: errorMessage }];
// TODO: better way to handle multiple files
const files = errorMessage
.split(/\s*<br\/>\s*/)
.slice(1)
.filter(Boolean);
const close = showModal(AlertModal, {
title: 'Unexpected Request Failure',
operationSlot: files.length ? (
<Button
onPress={() => {
close();
dataFoldersPatcher(prev => [...prev, ...files]);
}}
>
Add all to whitelist
</Button>
) : undefined,
message: (
<div>
<p>The request failed due to an unhandled error:</p>
<code className="wide selectable">
<div className="w-full overflow-y-auto text-wrap">
{messages.map(({ text, handler }, index) =>
{messages.map(({ text, handler, Component }, index) =>
handler ? (
<Link
className="cursor-pointer text-[--color-surprise]"
Expand All @@ -96,6 +113,8 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(
>
{text}
</Link>
) : Component ? (
<Component />
) : (
text
),
Expand Down
9 changes: 9 additions & 0 deletions packages/insomnia/src/ui/hooks/use-request.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useParams } from 'react-router';

import { useRootLoaderData } from '~/root';
import { useRequestUpdateActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.update';
import { useRequestUpdateMetaActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.update-meta';
import { useRequestUpdatePayloadActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.update-payload';
Expand Down Expand Up @@ -111,6 +112,14 @@ export const useSettingsPatcher = () => {
};
};

export const useDataFoldersPatcher = () => {
const settingsPatcher = useSettingsPatcher();
const { settings } = useRootLoaderData()!;
return (setter: (value: Settings['dataFolders']) => Settings['dataFolders']) => {
settingsPatcher({ dataFolders: setter(settings['dataFolders']) });
};
};

export const useWorkspaceMetaPatcher = () => {
const { organizationId, projectId } = useParams() as { organizationId: string; projectId: string };
const fetcher = useWorkspaceUpdateMetaActionFetcher();
Expand Down
Loading