mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🔐 refactor: MCP User Variable Description Rendering (#10769)
* refactor: Add back user variable descriptions for MCP under input and not as Tooltips
- Integrated DOMPurify to sanitize HTML content in user variable descriptions, ensuring safe rendering of links and formatting.
- Updated the AuthField component to display sanitized descriptions, enhancing security and user experience.
- Removed TooltipAnchor in favor of direct label rendering for improved clarity.
* 📦 chore: Update `dompurify` to v3.3.0 in package dependencies
- Added `dompurify` version 3.3.0 to `package.json` and `package-lock.json` for improved HTML sanitization.
- Updated existing references to `dompurify` to ensure consistency across the project.
* refactor: Update tooltip styles for sanitized description in AuthField component
This commit is contained in:
parent
745c299563
commit
ef5540f278
4 changed files with 47 additions and 20 deletions
|
|
@ -64,6 +64,7 @@
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"date-fns": "^3.3.1",
|
"date-fns": "^3.3.1",
|
||||||
|
"dompurify": "^3.3.0",
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"export-from-json": "^1.7.2",
|
"export-from-json": "^1.7.2",
|
||||||
"filenamify": "^6.0.0",
|
"filenamify": "^6.0.0",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
import { useForm, Controller } from 'react-hook-form';
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
import { Input, Label, Button, TooltipAnchor, CircleHelpIcon } from '@librechat/client';
|
import { Input, Label, Button } from '@librechat/client';
|
||||||
import { useMCPAuthValuesQuery } from '~/data-provider/Tools/queries';
|
import { useMCPAuthValuesQuery } from '~/data-provider/Tools/queries';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
|
|
@ -27,21 +28,40 @@ interface AuthFieldProps {
|
||||||
function AuthField({ name, config, hasValue, control, errors }: AuthFieldProps) {
|
function AuthField({ name, config, hasValue, control, errors }: AuthFieldProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
|
||||||
|
const sanitizer = useMemo(() => {
|
||||||
|
const instance = DOMPurify();
|
||||||
|
instance.addHook('afterSanitizeAttributes', (node) => {
|
||||||
|
if (node.tagName && node.tagName === 'A') {
|
||||||
|
node.setAttribute('target', '_blank');
|
||||||
|
node.setAttribute('rel', 'noopener noreferrer');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return instance;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const sanitizedDescription = useMemo(() => {
|
||||||
|
if (!config.description) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return sanitizer.sanitize(config.description, {
|
||||||
|
ALLOWED_TAGS: ['a', 'strong', 'b', 'em', 'i', 'br', 'code'],
|
||||||
|
ALLOWED_ATTR: ['href', 'class', 'target', 'rel'],
|
||||||
|
ALLOW_DATA_ATTR: false,
|
||||||
|
ALLOW_ARIA_ATTR: false,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Sanitization failed', error);
|
||||||
|
return config.description;
|
||||||
|
}
|
||||||
|
}, [config.description, sanitizer]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<TooltipAnchor
|
<Label htmlFor={name} className="text-sm font-medium">
|
||||||
enableHTML={true}
|
{config.title}
|
||||||
description={config.description || ''}
|
</Label>
|
||||||
render={
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Label htmlFor={name} className="text-sm font-medium">
|
|
||||||
{config.title}
|
|
||||||
</Label>
|
|
||||||
<CircleHelpIcon className="h-6 w-6 cursor-help text-text-secondary transition-colors hover:text-text-primary" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{hasValue ? (
|
{hasValue ? (
|
||||||
<div className="flex min-w-fit items-center gap-2 whitespace-nowrap rounded-full border border-border-light px-2 py-0.5 text-xs font-medium text-text-secondary">
|
<div className="flex min-w-fit items-center gap-2 whitespace-nowrap rounded-full border border-border-light px-2 py-0.5 text-xs font-medium text-text-secondary">
|
||||||
<div className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
<div className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
||||||
|
|
@ -66,12 +86,18 @@ function AuthField({ name, config, hasValue, control, errors }: AuthFieldProps)
|
||||||
placeholder={
|
placeholder={
|
||||||
hasValue
|
hasValue
|
||||||
? localize('com_ui_mcp_update_var', { 0: config.title })
|
? localize('com_ui_mcp_update_var', { 0: config.title })
|
||||||
: `${localize('com_ui_mcp_enter_var', { 0: config.title })} ${localize('com_ui_optional')}`
|
: localize('com_ui_mcp_enter_var', { 0: config.title })
|
||||||
}
|
}
|
||||||
className="w-full rounded border border-border-medium bg-transparent px-2 py-1 text-text-primary placeholder:text-text-secondary focus:outline-none sm:text-sm"
|
className="w-full rounded border border-border-medium bg-transparent px-2 py-1 text-text-primary placeholder:text-text-secondary focus:outline-none sm:text-sm"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{sanitizedDescription && (
|
||||||
|
<p
|
||||||
|
className="text-xs text-text-secondary [&_a]:text-blue-500 [&_a]:hover:underline"
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitizedDescription }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{errors[name] && <p className="text-xs text-red-500">{errors[name]?.message}</p>}
|
{errors[name] && <p className="text-xs text-red-500">{errors[name]?.message}</p>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -484,6 +484,7 @@
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"date-fns": "^3.3.1",
|
"date-fns": "^3.3.1",
|
||||||
|
"dompurify": "^3.3.0",
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"export-from-json": "^1.7.2",
|
"export-from-json": "^1.7.2",
|
||||||
"filenamify": "^6.0.0",
|
"filenamify": "^6.0.0",
|
||||||
|
|
@ -28441,11 +28442,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dompurify": {
|
"node_modules/dompurify": {
|
||||||
"version": "3.2.6",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz",
|
||||||
"integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
|
"integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==",
|
||||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
"peer": true,
|
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@types/trusted-types": "^2.0.7"
|
"@types/trusted-types": "^2.0.7"
|
||||||
}
|
}
|
||||||
|
|
@ -46398,7 +46398,7 @@
|
||||||
"@tanstack/react-virtual": "^3.0.0",
|
"@tanstack/react-virtual": "^3.0.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dompurify": "^3.2.6",
|
"dompurify": "^3.3.0",
|
||||||
"framer-motion": "^12.23.6",
|
"framer-motion": "^12.23.6",
|
||||||
"i18next": "^24.2.2 || ^25.3.2",
|
"i18next": "^24.2.2 || ^25.3.2",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
"@tanstack/react-virtual": "^3.0.0",
|
"@tanstack/react-virtual": "^3.0.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dompurify": "^3.2.6",
|
"dompurify": "^3.3.0",
|
||||||
"framer-motion": "^12.23.6",
|
"framer-motion": "^12.23.6",
|
||||||
"i18next": "^24.2.2 || ^25.3.2",
|
"i18next": "^24.2.2 || ^25.3.2",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue