F: Improve error handling security

This commit is contained in:
Ivan 2025-11-06 14:53:41 +03:00
parent 20af4666cf
commit 56c2f758f8
4 changed files with 39 additions and 4 deletions

View File

@ -30,6 +30,7 @@ This readme file is used mostly to document project dependencies and conventions
<pre> <pre>
- axios - axios
- clsx - clsx
- dompurify
- react-icons - react-icons
- react-router - react-router
- react-toastify - react-toastify

View File

@ -22,6 +22,7 @@
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"dompurify": "^3.3.0",
"global": "^4.4.0", "global": "^4.4.0",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
"lucide-react": "^0.548.0", "lucide-react": "^0.548.0",
@ -4498,6 +4499,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
},
"node_modules/@types/yargs": { "node_modules/@types/yargs": {
"version": "17.0.34", "version": "17.0.34",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz",
@ -6479,6 +6487,15 @@
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
}, },
"node_modules/dompurify": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz",
"integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",

View File

@ -28,6 +28,7 @@
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"dompurify": "^3.3.0",
"global": "^4.4.0", "global": "^4.4.0",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
"lucide-react": "^0.548.0", "lucide-react": "^0.548.0",

View File

@ -1,4 +1,5 @@
import clsx from 'clsx'; import clsx from 'clsx';
import DOMPurify from 'dompurify';
import { ZodError } from 'zod'; import { ZodError } from 'zod';
import { type AxiosError, isAxiosError } from '@/backend/api-transport'; import { type AxiosError, isAxiosError } from '@/backend/api-transport';
@ -18,11 +19,17 @@ export function DescribeError({ error }: { error: ErrorData }) {
} else if (typeof error === 'string') { } else if (typeof error === 'string') {
return <p>{error}</p>; return <p>{error}</p>;
} else if (error instanceof ZodError) { } else if (error instanceof ZodError) {
let errorData: unknown;
try {
/* eslint-disable-next-line @typescript-eslint/no-base-to-string */
errorData = JSON.parse(error.toString());
} catch {
errorData = { message: error.message, issues: error.issues };
}
return ( return (
<div> <div>
<p>Ошибка валидации данных</p> <p>Ошибка валидации данных</p>
{/* eslint-disable-next-line @typescript-eslint/no-base-to-string */} <PrettyJson data={errorData} />
<PrettyJson data={JSON.parse(error.toString()) as unknown} />;
</div> </div>
); );
} else if (!isAxiosError(error)) { } else if (!isAxiosError(error)) {
@ -60,6 +67,12 @@ export function DescribeError({ error }: { error: ErrorData }) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const isHtml = isResponseHtml(error.response); const isHtml = isResponseHtml(error.response);
let sanitizedHtml: string | null = null;
if (isHtml) {
sanitizedHtml = DOMPurify.sanitize(error.response.data as string, {
USE_PROFILES: { html: true }
});
}
return ( return (
<div> <div>
<p className='underline'>Ошибка</p> <p className='underline'>Ошибка</p>
@ -67,8 +80,11 @@ export function DescribeError({ error }: { error: ErrorData }) {
{error.response.data && ( {error.response.data && (
<> <>
<p className='mt-2 underline'>Описание</p> <p className='mt-2 underline'>Описание</p>
{isHtml ? <div dangerouslySetInnerHTML={{ __html: error.response.data as TrustedHTML }} /> : null} {isHtml && sanitizedHtml ? (
{!isHtml ? <PrettyJson data={error.response.data as object} /> : null} <div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />
) : (
<PrettyJson data={error.response.data as object} />
)}
</> </>
)} )}
</div> </div>