From 44e5fb9b06c794033204ef1744b54b3b87160082 Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Wed, 18 Sep 2024 08:49:41 +0200 Subject: [PATCH 1/5] fix(editor): Replace v-html with custom directive to sanitize html (#10804) Co-authored-by: Tomi Turtiainen <10324676+tomi@users.noreply.github.com> --- packages/design-system/package.json | 1 - .../AskAssistantChat/AskAssistantChat.vue | 14 +- .../__tests__/AskAssistantChat.spec.ts | 21 ++ .../AskAssistantChat.spec.ts.snap | 18 -- .../src/components/N8nActionBox/ActionBox.vue | 4 +- .../N8nInfoAccordion/InfoAccordion.vue | 4 +- .../components/N8nInputLabel/InputLabel.vue | 2 +- .../src/components/N8nMarkdown/Markdown.vue | 2 +- .../N8nMarkdown/__tests__/Markdown.spec.ts | 21 ++ .../src/components/N8nNotice/Notice.vue | 2 +- .../N8nNotice/__tests__/Notice.spec.ts | 4 + .../src/components/N8nSticky/Sticky.vue | 2 +- .../src/components/N8nTabs/Tabs.vue | 2 +- .../src/components/N8nTooltip/Tooltip.vue | 2 +- .../design-system/src/directives/index.ts | 1 + .../src/directives/n8n-html.test.ts | 45 ++++ .../design-system/src/directives/n8n-html.ts | 37 +++ .../src/components/Error/NodeErrorView.vue | 2 +- .../src/components/ExpressionEditModal.vue | 2 +- .../src/components/FeatureComingSoon.vue | 4 +- .../InlineExpressionTip.vue | 6 +- .../editor-ui/src/components/InputPanel.vue | 2 +- .../editor-ui/src/components/MappingPill.vue | 2 +- packages/editor-ui/src/components/Node.vue | 2 +- .../Node/NodeCreator/ItemTypes/NodeItem.vue | 2 +- .../Node/NodeCreator/Modes/ActionsMode.vue | 12 +- .../Renderers/CategorizedItemsRenderer.vue | 2 +- .../src/components/ParameterInput.vue | 4 +- .../src/components/ParameterInputHint.vue | 4 +- .../src/components/PushConnectionTracker.vue | 2 +- .../ResourceMapper/MappingModeSelect.vue | 2 +- packages/editor-ui/src/components/RunData.vue | 4 +- packages/editor-ui/src/components/RunInfo.vue | 2 +- .../editor-ui/src/components/TitledList.vue | 2 +- .../editor-ui/src/components/TriggerPanel.vue | 2 +- .../editor-ui/src/components/VersionCard.vue | 4 +- .../src/components/WorkflowActivator.vue | 2 +- .../src/components/WorkflowSettings.vue | 2 +- .../src/components/banners/V1Banner.vue | 4 +- .../banners/__tests__/V1Banner.spec.ts | 8 +- .../__snapshots__/V1Banner.spec.ts.snap | 226 ++++++++++++++---- .../nodes/render-types/CanvasNodeDefault.vue | 2 +- .../WorkflowExecutionAnnotationPanel.vue | 2 +- .../editor-ui/src/views/SettingsLdapView.vue | 2 +- .../src/views/SettingsLogStreamingView.vue | 8 +- .../AppsRequiringCredsNotice.vue | 2 +- .../SetupTemplateFormStep.vue | 2 +- .../src/views/TemplatesSearchView.vue | 2 +- pnpm-lock.yaml | 3 - 49 files changed, 379 insertions(+), 130 deletions(-) create mode 100644 packages/design-system/src/directives/n8n-html.test.ts create mode 100644 packages/design-system/src/directives/n8n-html.ts diff --git a/packages/design-system/package.json b/packages/design-system/package.json index d2cbb0678b301..e8f1f99196412 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -29,7 +29,6 @@ "@types/sanitize-html": "^2.11.0", "@vitejs/plugin-vue": "^5.0.4", "@vitest/coverage-v8": "catalog:frontend", - "@vue/test-utils": "^2.4.3", "autoprefixer": "^10.4.19", "postcss": "^8.4.38", "sass": "^1.64.1", diff --git a/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue b/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue index de2f5e17685ea..63f4bf9a865aa 100644 --- a/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue +++ b/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue @@ -151,8 +151,7 @@ function growInput() { />
- - + @@ -160,19 +159,20 @@ function growInput() {
- - - +
{ it('renders default placeholder chat correctly', () => { @@ -12,6 +13,11 @@ describe('AskAssistantChat', () => { }); it('renders chat with messages correctly', () => { const { container } = render(AskAssistantChat, { + global: { + directives: { + n8nHtml, + }, + }, props: { user: { firstName: 'Kobi', lastName: 'Dog' }, messages: [ @@ -86,6 +92,11 @@ describe('AskAssistantChat', () => { }); it('renders streaming chat correctly', () => { const { container } = render(AskAssistantChat, { + global: { + directives: { + n8nHtml, + }, + }, props: { user: { firstName: 'Kobi', lastName: 'Dog' }, messages: [ @@ -105,6 +116,11 @@ describe('AskAssistantChat', () => { }); it('renders end of session chat correctly', () => { const { container } = render(AskAssistantChat, { + global: { + directives: { + n8nHtml, + }, + }, props: { user: { firstName: 'Kobi', lastName: 'Dog' }, messages: [ @@ -130,6 +146,11 @@ describe('AskAssistantChat', () => { }); it('renders message with code snippet', () => { const { container } = render(AskAssistantChat, { + global: { + directives: { + n8nHtml, + }, + }, props: { user: { firstName: 'Kobi', lastName: 'Dog' }, messages: [ diff --git a/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap b/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap index 8baba648f9f74..3a587f4fe75f1 100644 --- a/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap +++ b/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap @@ -129,9 +129,6 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `
- - -
@@ -145,7 +142,6 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `
-
@@ -438,7 +434,6 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `
-

Give it to me @@ -516,7 +511,6 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `

-

Solution steps: @@ -1060,9 +1054,6 @@ exports[`AskAssistantChat > renders end of session chat correctly 1`] = `

- - -
@@ -1076,7 +1067,6 @@ exports[`AskAssistantChat > renders end of session chat correctly 1`] = `
-
@@ -1309,9 +1299,6 @@ exports[`AskAssistantChat > renders message with code snippet 1`] = `
- - -
@@ -1325,7 +1312,6 @@ exports[`AskAssistantChat > renders message with code snippet 1`] = `
-
renders streaming chat correctly 1`] = `
- - -
@@ -1568,7 +1551,6 @@ exports[`AskAssistantChat > renders streaming chat correctly 1`] = `
-
diff --git a/packages/design-system/src/components/N8nActionBox/ActionBox.vue b/packages/design-system/src/components/N8nActionBox/ActionBox.vue index c048c3f6438c5..edb877b3d650f 100644 --- a/packages/design-system/src/components/N8nActionBox/ActionBox.vue +++ b/packages/design-system/src/components/N8nActionBox/ActionBox.vue @@ -37,7 +37,7 @@ withDefaults(defineProps(), {
- +
@@ -61,7 +61,7 @@ withDefaults(defineProps(), { :class="$style.callout" > - +
diff --git a/packages/design-system/src/components/N8nInfoAccordion/InfoAccordion.vue b/packages/design-system/src/components/N8nInfoAccordion/InfoAccordion.vue index 71830b02dcac6..e500c73cd65f1 100644 --- a/packages/design-system/src/components/N8nInfoAccordion/InfoAccordion.vue +++ b/packages/design-system/src/components/N8nInfoAccordion/InfoAccordion.vue @@ -75,7 +75,7 @@ const onTooltipClick = (item: string, event: MouseEvent) => emit('tooltipClick',
@@ -83,7 +83,7 @@ const onTooltipClick = (item: string, event: MouseEvent) => emit('tooltipClick',
- +
diff --git a/packages/design-system/src/components/N8nInputLabel/InputLabel.vue b/packages/design-system/src/components/N8nInputLabel/InputLabel.vue index 6c527549ed0f3..bda96c2e21feb 100644 --- a/packages/design-system/src/components/N8nInputLabel/InputLabel.vue +++ b/packages/design-system/src/components/N8nInputLabel/InputLabel.vue @@ -58,7 +58,7 @@ const addTargetBlank = (html: string) =>
diff --git a/packages/design-system/src/components/N8nMarkdown/Markdown.vue b/packages/design-system/src/components/N8nMarkdown/Markdown.vue index c953ff9d657e5..45de891020bb3 100644 --- a/packages/design-system/src/components/N8nMarkdown/Markdown.vue +++ b/packages/design-system/src/components/N8nMarkdown/Markdown.vue @@ -202,7 +202,7 @@ const onCheckboxChange = (index: number) => { @click="onClick" @mousedown="onMouseDown" @change="onChange" - v-html="htmlContent" + v-n8n-html="htmlContent" />
diff --git a/packages/design-system/src/components/N8nMarkdown/__tests__/Markdown.spec.ts b/packages/design-system/src/components/N8nMarkdown/__tests__/Markdown.spec.ts index 2c826f5192800..ac2faac7b308f 100644 --- a/packages/design-system/src/components/N8nMarkdown/__tests__/Markdown.spec.ts +++ b/packages/design-system/src/components/N8nMarkdown/__tests__/Markdown.spec.ts @@ -1,10 +1,16 @@ import { render, fireEvent } from '@testing-library/vue'; import N8nMarkdown from '../Markdown.vue'; +import { n8nHtml } from 'n8n-design-system/directives'; describe('components', () => { describe('N8nMarkdown', () => { it('should render unchecked checkboxes', () => { const wrapper = render(N8nMarkdown, { + global: { + directives: { + n8nHtml, + }, + }, props: { content: '__TODO__\n- [ ] Buy milk\n- [ ] Buy socks\n', }, @@ -18,6 +24,11 @@ describe('components', () => { it('should render checked checkboxes', () => { const wrapper = render(N8nMarkdown, { + global: { + directives: { + n8nHtml, + }, + }, props: { content: '__TODO__\n- [X] Buy milk\n- [X] Buy socks\n', }, @@ -31,6 +42,11 @@ describe('components', () => { it('should toggle checkboxes when clicked', async () => { const wrapper = render(N8nMarkdown, { + global: { + directives: { + n8nHtml, + }, + }, props: { content: '__TODO__\n- [ ] Buy milk\n- [ ] Buy socks\n', }, @@ -50,6 +66,11 @@ describe('components', () => { it('should render inputs as plain text', () => { const wrapper = render(N8nMarkdown, { + global: { + directives: { + n8nHtml, + }, + }, props: { content: '__TODO__\n- [X] Buy milk\n- \n', diff --git a/packages/design-system/src/components/N8nNotice/Notice.vue b/packages/design-system/src/components/N8nNotice/Notice.vue index eb27aa2efdf8f..f630e95e59c77 100644 --- a/packages/design-system/src/components/N8nNotice/Notice.vue +++ b/packages/design-system/src/components/N8nNotice/Notice.vue @@ -73,7 +73,7 @@ const onClick = (event: MouseEvent) => { :id="`${id}-content`" :class="showFullContent ? $style['expanded'] : $style['truncated']" role="region" - v-html="displayContent" + v-n8n-html="displayContent" /> diff --git a/packages/design-system/src/components/N8nNotice/__tests__/Notice.spec.ts b/packages/design-system/src/components/N8nNotice/__tests__/Notice.spec.ts index 84617dab515ec..180815e5367b0 100644 --- a/packages/design-system/src/components/N8nNotice/__tests__/Notice.spec.ts +++ b/packages/design-system/src/components/N8nNotice/__tests__/Notice.spec.ts @@ -1,6 +1,7 @@ import { render } from '@testing-library/vue'; import N8nNotice from '../Notice.vue'; import { N8nText } from 'n8n-design-system/components'; +import { n8nHtml } from 'n8n-design-system/directives'; describe('components', () => { describe('N8nNotice', () => { @@ -41,6 +42,9 @@ describe('components', () => { content: 'Hello world! This is a notice.', }, global: { + directives: { + n8nHtml, + }, components: { 'n8n-text': N8nText, }, diff --git a/packages/design-system/src/components/N8nSticky/Sticky.vue b/packages/design-system/src/components/N8nSticky/Sticky.vue index 4b7dca944acb0..9e258b4afa75a 100644 --- a/packages/design-system/src/components/N8nSticky/Sticky.vue +++ b/packages/design-system/src/components/N8nSticky/Sticky.vue @@ -116,7 +116,7 @@ const onInputScroll = (event: WheelEvent) => {
- +
diff --git a/packages/design-system/src/components/N8nTabs/Tabs.vue b/packages/design-system/src/components/N8nTabs/Tabs.vue index 64277ca8630e9..ff1b142e516e2 100644 --- a/packages/design-system/src/components/N8nTabs/Tabs.vue +++ b/packages/design-system/src/components/N8nTabs/Tabs.vue @@ -89,7 +89,7 @@ const scrollRight = () => scroll(50); > diff --git a/packages/editor-ui/src/components/InputPanel.vue b/packages/editor-ui/src/components/InputPanel.vue index 7bc38c811ed56..a9a6dd3909c7f 100644 --- a/packages/editor-ui/src/components/InputPanel.vue +++ b/packages/editor-ui/src/components/InputPanel.vue @@ -406,7 +406,7 @@ export default defineComponent({ @@ -293,13 +293,13 @@ onMounted(() => { slim data-test-id="actions-panel-activation-callout" > - + @@ -320,7 +320,7 @@ onMounted(() => {
diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue index b5ca14dd887fd..df63a8b3228f4 100644 --- a/packages/editor-ui/src/components/ParameterInput.vue +++ b/packages/editor-ui/src/components/ParameterInput.vue @@ -1391,7 +1391,7 @@ onUpdated(async () => {
@@ -1424,7 +1424,7 @@ onUpdated(async () => {
diff --git a/packages/editor-ui/src/components/ParameterInputHint.vue b/packages/editor-ui/src/components/ParameterInputHint.vue index 87ce1a92ab992..1c28e4e44a937 100644 --- a/packages/editor-ui/src/components/ParameterInputHint.vue +++ b/packages/editor-ui/src/components/ParameterInputHint.vue @@ -46,13 +46,13 @@ const simplyText = computed(() => { [$style.highlight]: highlight, }" > - +
diff --git a/packages/editor-ui/src/components/PushConnectionTracker.vue b/packages/editor-ui/src/components/PushConnectionTracker.vue index bd4ecc865494b..1be8fb5b6aaef 100644 --- a/packages/editor-ui/src/components/PushConnectionTracker.vue +++ b/packages/editor-ui/src/components/PushConnectionTracker.vue @@ -16,7 +16,7 @@ export default defineComponent({
  diff --git a/packages/editor-ui/src/components/ResourceMapper/MappingModeSelect.vue b/packages/editor-ui/src/components/ResourceMapper/MappingModeSelect.vue index 2c69e1555086c..ab250a07d3989 100644 --- a/packages/editor-ui/src/components/ResourceMapper/MappingModeSelect.vue +++ b/packages/editor-ui/src/components/ResourceMapper/MappingModeSelect.vue @@ -131,7 +131,7 @@ defineExpose({
{{ option.name }}
-
+
diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index f85800fa160f1..348e3a455b9db 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -1350,7 +1350,7 @@ export default defineComponent({ :class="$style.hintCallout" :theme="hint.type || 'info'" > - +
{{ tooMuchDataTitle }} { data-test-id="node-run-info-stale" >

    -
  • +
diff --git a/packages/editor-ui/src/components/TriggerPanel.vue b/packages/editor-ui/src/components/TriggerPanel.vue index db8a64736a2df..0702f34c49979 100644 --- a/packages/editor-ui/src/components/TriggerPanel.vue +++ b/packages/editor-ui/src/components/TriggerPanel.vue @@ -441,7 +441,7 @@ export default defineComponent({
-   +   { {{ `${$locale.baseText('versionCard.version')} ${version.name}` }}
- + {
diff --git a/packages/editor-ui/src/components/WorkflowSettings.vue b/packages/editor-ui/src/components/WorkflowSettings.vue index 2d915611ff07a..1ecac538a6a70 100644 --- a/packages/editor-ui/src/components/WorkflowSettings.vue +++ b/packages/editor-ui/src/components/WorkflowSettings.vue @@ -573,7 +573,7 @@ export default defineComponent({ {{ $locale.baseText('workflowSettings.errorWorkflow') + ':' }} diff --git a/packages/editor-ui/src/components/banners/V1Banner.vue b/packages/editor-ui/src/components/banners/V1Banner.vue index 72077f1a3c55c..6ea90c712c148 100644 --- a/packages/editor-ui/src/components/banners/V1Banner.vue +++ b/packages/editor-ui/src/components/banners/V1Banner.vue @@ -17,14 +17,14 @@ const hasOwnerPermission = computed(() => hasPermission(['instanceOwner']));