Skip to content

Commit c738f7f

Browse files
authored
feat: Improve wording for Security Whitelist Error Message [INS-1277] (#9076)
* feat: Improve wording for Security Whitelist Error Message [INS-1277] * refactor: interactive message * fix: fix process not being defined in worker
1 parent b0345b6 commit c738f7f

File tree

11 files changed

+144
-24
lines changed

11 files changed

+144
-24
lines changed

packages/insomnia-smoke-test/tests/smoke/after-response-script-features.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ test.describe('after-response script features tests', () => {
9393
__fromAfterScript: 'environment',
9494
base_url: 'http://localhost:4010',
9595
});
96-
await page.getByRole('button', { name: 'Close' }).click();
96+
await page.getByRole('button', { name: 'Close', exact: true }).click();
9797

9898
// globals and baseGlobals can be persisted
9999
await page.getByTestId('underlay').click();
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { showSettingsModal } from '~/ui/components/modals/settings-modal';
2+
3+
const PREF_SECURITY = 'Insomnia’s Preferences → Security';
4+
5+
const interactives = [{ text: PREF_SECURITY, handler: () => showSettingsModal({ tab: 'general' }) }];
6+
7+
export function buildInteractiveMessage(message: string) {
8+
const parts = [];
9+
let prev = '';
10+
for (let i = 0; i < message.length; i++) {
11+
let matched = false;
12+
for (const { text, handler } of interactives) {
13+
matched = message.startsWith(text, i);
14+
if (matched) {
15+
if (prev) {
16+
parts.push({ text: prev });
17+
prev = '';
18+
}
19+
parts.push({ text, handler });
20+
i += text.length - 1;
21+
break;
22+
}
23+
}
24+
if (!matched) {
25+
prev += message[i];
26+
}
27+
}
28+
if (prev) {
29+
parts.push({ text: prev });
30+
}
31+
return parts;
32+
}

packages/insomnia/src/common/validators.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import type { CaCertificate } from "../models/ca-certificate";
2-
import type { ClientCertificate } from "../models/client-certificate";
3-
import type { Settings } from "../models/settings";
4-
import type { RenderedRequest } from "../templating/types";
1+
import type { CaCertificate } from '../models/ca-certificate';
2+
import type { ClientCertificate } from '../models/client-certificate';
3+
import type { Settings } from '../models/settings';
4+
import type { RenderedRequest } from '../templating/types';
5+
6+
const PREF_SECURITY = 'Insomnia’s Preferences → Security';
57

68
export function isFsAccessingAllowed(
79
renderedRequest: RenderedRequest,
@@ -14,13 +16,15 @@ export function isFsAccessingAllowed(
1416
if (fromCli) {
1517
throw `Insomnia cannot access the file ‘${fileName}’. You can specify paths with one or more "--dataFolders <directory>" or "-f <directory>" to allow accessing.`;
1618
} else {
17-
throw `Insomnia cannot access the file ‘${fileName}’. You can adjust this in Preferences → Security.`;
19+
throw `Insomnia cannot access the file ‘${fileName}’. You must specify which directories Insomnia can access in ${PREF_SECURITY}.`;
1820
}
19-
}
21+
};
2022

2123
// case1: check request body (set by scripts or request body editor)
2224
if (renderedRequest.body.fileName !== undefined && renderedRequest.body.fileName !== '') {
23-
const allowed = settings?.dataFolders.some(folder => folder !== '' && renderedRequest.body.fileName?.startsWith(folder));
25+
const allowed = settings?.dataFolders.some(
26+
folder => folder !== '' && renderedRequest.body.fileName?.startsWith(folder),
27+
);
2428
if (!allowed) {
2529
throwError(renderedRequest.body.fileName);
2630
}
@@ -29,7 +33,7 @@ export function isFsAccessingAllowed(
2933
// case2: check the body form data - "file" type params
3034
if (Array.isArray(renderedRequest.body.params)) {
3135
renderedRequest.body.params.forEach(param => {
32-
if (param.type === "file" && !param.disabled) {
36+
if (param.type === 'file' && !param.disabled) {
3337
const allowed = settings?.dataFolders.some(folder => folder !== '' && param.fileName?.startsWith(folder));
3438
if (!allowed) {
3539
throwError(param.fileName || param.value);
@@ -56,7 +60,9 @@ export function isFsAccessingAllowed(
5660

5761
[cert.key, cert.cert, cert.pfx].forEach(targetPath => {
5862
if (targetPath) {
59-
const allowed = settings?.dataFolders.some(folder => folder !== '' && targetPath !== "" && targetPath?.startsWith(folder));
63+
const allowed = settings?.dataFolders.some(
64+
folder => folder !== '' && targetPath !== '' && targetPath?.startsWith(folder),
65+
);
6066
if (!allowed) {
6167
throwError(targetPath);
6268
}

packages/insomnia/src/templating/base-extension-worker.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import type { NodeCurlRequestOptions } from '../plugins/context/network';
88
import type { Plugin } from '../plugins/index';
99
import type { BaseRenderContext, PluginTemplateTag, PluginTemplateTagContext, PluginToMainAPIPaths } from './types';
1010
import * as templating from './worker';
11+
12+
const PREF_SECURITY = 'Insomnia’s Preferences → Security';
13+
1114
export function decodeEncoding<T>(value: T) {
1215
if (typeof value !== 'string') {
1316
return value;
@@ -188,7 +191,7 @@ export default class BaseExtension {
188191
?.getSettings()
189192
.dataFolders.some((folder: string) => folder !== '' && path.startsWith(folder));
190193
if (!allowed) {
191-
throw `Insomnia cannot access the file ‘${path}’. You can adjust this in Preferences → Security.`;
194+
throw `Insomnia cannot access the file ‘${path}’. You must specify which directories Insomnia can access in ${PREF_SECURITY}.`;
192195
}
193196
return fetchFromTemplateWorkerDatabase('readFile', { path, encoding });
194197
},

packages/insomnia/src/templating/base-extension.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type { BaseRenderContext, PluginTemplateTag, PluginTemplateTagContext } f
1919
import { decodeEncoding } from './utils';
2020

2121
const EMPTY_ARG = '__EMPTY_NUNJUCKS_ARG__';
22+
const PREF_SECURITY = 'Insomnia’s Preferences → Security';
2223

2324
export default class BaseExtension {
2425
_ext: PluginTemplateTag | null = null;
@@ -124,7 +125,7 @@ export default class BaseExtension {
124125
?.getSettings()
125126
.dataFolders.some((folder: string) => folder !== '' && path.startsWith(folder));
126127
if (!allowed) {
127-
throw `Insomnia cannot access the file ‘${path}’. You can adjust this in Preferences → Security.`;
128+
throw `Insomnia cannot access the file ‘${path}’. You must specify which directories Insomnia can access in ${PREF_SECURITY}.`;
128129
}
129130

130131
const content = await fs.promises.readFile(path);

packages/insomnia/src/ui/components/modals/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export function showModal<
3434
TModalProps extends ModalProps &
3535
React.RefAttributes<{
3636
show: (options: any) => void;
37+
hide: () => void;
3738
}>,
3839
>(modalComponent: ModalComponent<TModalProps>, config?: ModalHandleShowOptions<GetRefHandleFromProps<TModalProps>>) {
3940
const name = modalComponent.name || modalComponent.displayName;
@@ -42,12 +43,18 @@ export function showModal<
4243

4344
const modalHandle = getModalComponentHandle(name) as unknown as GetRefHandleFromProps<TModalProps>;
4445

45-
return modalHandle.show(config);
46+
modalHandle.show(config);
47+
return () => {
48+
const modalHandle = getModalComponentHandle(name) as unknown as GetRefHandleFromProps<TModalProps>;
49+
if (modalHandle) {
50+
modalHandle.hide();
51+
}
52+
};
4653
}
4754

4855
export function showError(config: ErrorModalOptions) {
4956
try {
50-
return showModal(ErrorModal, config);
57+
showModal(ErrorModal, config);
5158
} catch (err) {
5259
console.log('[modal] Cannot show modal', err, config);
5360
}

packages/insomnia/src/ui/components/modals/nunjucks-modal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export const NunjucksModal = forwardRef<NunjucksModalHandle, ModalProps & Props>
7575
defaultValue={template}
7676
workspace={workspace}
7777
editorId={state.editorId}
78+
close={() => modalRef.current?.hide()}
7879
/>
7980
);
8081
} else {

packages/insomnia/src/ui/components/rendered-query-string.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import classNames from 'classnames';
22
import React, { type FC, useCallback, useEffect, useState } from 'react';
3+
import { Link } from 'react-aria-components';
4+
5+
import { buildInteractiveMessage } from '~/common/interactive-messages';
36

47
import { database as db } from '../../common/database';
58
import * as models from '../../models';
@@ -74,6 +77,7 @@ export const RenderedQueryString: FC<Props> = ({ request }) => {
7477
});
7578

7679
if (!result) {
80+
setTooLong(false);
7781
return;
7882
}
7983

@@ -137,10 +141,29 @@ export const RenderedQueryString: FC<Props> = ({ request }) => {
137141
}, [tooLong]);
138142

139143
const className = previewString === defaultPreview ? 'super-duper-faint' : 'selectable force-wrap';
144+
// naive way to detect the access file error
145+
const messages = previewString.includes('Insomnia cannot access')
146+
? buildInteractiveMessage(previewString)
147+
: [{ text: previewString }];
140148

141149
return (
142150
<div className="relative flex h-full w-full justify-between gap-[var(--padding-sm)] overflow-auto">
143-
<span className={classNames('my-auto', className)}>{previewString}</span>
151+
<span className={classNames('my-auto', className)}>
152+
{messages.map(({ text, handler }, index) =>
153+
handler ? (
154+
<Link
155+
className="cursor-pointer text-[--color-surprise]"
156+
// eslint-disable-next-line react/no-array-index-key
157+
key={index}
158+
onPress={handler}
159+
>
160+
{text}
161+
</Link>
162+
) : (
163+
text
164+
),
165+
)}
166+
</span>
144167

145168
<CopyButton
146169
size="small"

packages/insomnia/src/ui/components/request-url-bar.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
2-
import { Button } from 'react-aria-components';
2+
import { Button, Link } from 'react-aria-components';
33
import { useParams, useSearchParams } from 'react-router';
44
import * as reactUse from 'react-use';
55

6+
import { buildInteractiveMessage } from '~/common/interactive-messages';
67
import { useRootLoaderData } from '~/root';
78
import {
89
type ConnectActionParams,
@@ -71,13 +72,35 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(
7172
setUndefinedEnvironmentVariables(searchParams.get('undefinedEnvironmentVariables')!);
7273
} else {
7374
// only for request render error
74-
showModal(AlertModal, {
75+
const errorMessage = searchParams.get('error') || '';
76+
const messages = errorMessage.includes('Insomnia cannot access')
77+
? buildInteractiveMessage(errorMessage)
78+
: [{ text: errorMessage }];
79+
const close = showModal(AlertModal, {
7580
title: 'Unexpected Request Failure',
7681
message: (
7782
<div>
7883
<p>The request failed due to an unhandled error:</p>
7984
<code className="wide selectable">
80-
<pre className="w-full overflow-y-auto text-wrap" >{searchParams.get('error')}</pre>
85+
<div className="w-full overflow-y-auto text-wrap">
86+
{messages.map(({ text, handler }, index) =>
87+
handler ? (
88+
<Link
89+
className="cursor-pointer text-[--color-surprise]"
90+
// eslint-disable-next-line react/no-array-index-key
91+
key={index}
92+
onPress={() => {
93+
close();
94+
handler();
95+
}}
96+
>
97+
{text}
98+
</Link>
99+
) : (
100+
text
101+
),
102+
)}
103+
</div>
81104
</code>
82105
</div>
83106
),

packages/insomnia/src/ui/components/settings/general.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,7 @@ export const General: FC = () => {
225225
help="If checked, clears the OAuth session every time Insomnia is relaunched."
226226
/>
227227
<button
228-
className="pointer h-[--line-height-xs] rounded-[--radius-md] border border-solid border-[--hl-lg] px-[--padding-md] hover:bg-[--hl-xs]"
229-
style={{
230-
padding: 0,
231-
}}
228+
className="pointer h-[--line-height-xs] rounded-[--radius-md] border border-solid border-[--hl-lg] px-[--padding-sm] hover:bg-[--hl-xs]"
232229
onClick={initNewOAuthSession}
233230
>
234231
Clear OAuth 2 session

0 commit comments

Comments
 (0)