Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(editor): Replace v-html with custom directive to sanitize html #10804

Merged
merged 26 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c93e33c
fix(editor): Replace v-html with custom directive to sanitize content
cstuncsik Sep 13, 2024
4e4c4fd
fix(editor): Remove linter comments and update test snapsho
cstuncsik Sep 13, 2024
e21cd28
fix(editor): Extend unit test
cstuncsik Sep 13, 2024
237859d
Revert "fix(editor): Remove linter comments and update test snapsho"
cstuncsik Sep 13, 2024
27bc0d6
fix(editor): Fix unit test
cstuncsik Sep 13, 2024
14da392
fix(editor): Fix unit test
cstuncsik Sep 13, 2024
dbe9494
fix(editor): Fix unit test
cstuncsik Sep 13, 2024
63ab993
fix(editor): Fix unit test
cstuncsik Sep 16, 2024
0c8e8dd
Merge remote-tracking branch 'origin/master' into replace-v-html
cstuncsik Sep 16, 2024
858ca9b
chore: update lock file after conflicts
cstuncsik Sep 16, 2024
564d3cc
fix(editor): Fix unit test
cstuncsik Sep 17, 2024
892485c
fix(editor): Fix lint
cstuncsik Sep 17, 2024
2a50658
fix(editor): Fix lint
cstuncsik Sep 17, 2024
277615b
fix(editor): Fix typing
cstuncsik Sep 17, 2024
83574a7
fix(editor): Update unit test wording
cstuncsik Sep 17, 2024
ebaca47
fix(editor): Fix unit test
cstuncsik Sep 17, 2024
baeda69
fix(editor): Fix directive
cstuncsik Sep 17, 2024
a1d66d0
Merge remote-tracking branch 'origin/master' into replace-v-html
cstuncsik Sep 17, 2024
7576dc7
Merge remote-tracking branch 'origin/master' into replace-v-html
cstuncsik Sep 17, 2024
1d88fb8
fix(editor): Add usage comment
cstuncsik Sep 17, 2024
c0ca692
Merge remote-tracking branch 'origin/master' into replace-v-html
cstuncsik Sep 17, 2024
fb0bcf2
chore: update lock file after conflicts
cstuncsik Sep 17, 2024
6d5c8ac
Merge remote-tracking branch 'origin/master' into replace-v-html
cstuncsik Sep 17, 2024
d31540f
fix(editor): Enable code tag class when sanitizing
cstuncsik Sep 17, 2024
417b702
fix(editor): Enable data-* attr on links
cstuncsik Sep 17, 2024
6e2282f
Merge branch 'master' into replace-v-html
tomi Sep 18, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -151,27 +151,27 @@ function growInput() {
/>
</div>
<div :class="$style.blockBody">
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-html="renderMarkdown(message.content)"></span>
<span v-n8n-html="renderMarkdown(message.content)"></span>
<BlinkingCursor
v-if="streaming && i === messages?.length - 1 && message.title && message.content"
/>
</div>
</div>
</div>
<div v-else-if="message.type === 'text'" :class="$style.textMessage">
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-if="message.role === 'user'" v-html="renderMarkdown(message.content)"></span>
<!-- eslint-disable-next-line vue/no-v-html -->
<span
v-if="message.role === 'user'"
v-n8n-html="renderMarkdown(message.content)"
></span>
<div
v-else
v-n8n-html="renderMarkdown(message.content)"
:class="$style.assistantText"
v-html="renderMarkdown(message.content)"
></div>
<div
v-if="message?.codeSnippet"
v-n8n-html="renderMarkdown(message.codeSnippet).trim()"
:class="$style['code-snippet']"
v-html="renderMarkdown(message.codeSnippet).trim()"
></div>
<BlinkingCursor
v-if="streaming && i === messages?.length - 1 && message.role === 'assistant'"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,23 +129,9 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `
<div
class="textMessage"
>
<!-- eslint-disable-next-line vue/no-v-html -->

<!-- eslint-disable-next-line vue/no-v-html -->
<div
class="assistantText"
>
<p>
Hi Max! Here is my top solution to fix the error in your
<strong>
Transform data
</strong>
node👇
</p>
cstuncsik marked this conversation as resolved.
Show resolved Hide resolved


</div>

/>
<!--v-if-->
<!--v-if-->
</div>
Expand Down Expand Up @@ -438,17 +424,7 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `
<div
class="textMessage"
>
<!-- eslint-disable-next-line vue/no-v-html -->
<span>
<p>
Give it to me
<strong>
ignore this markdown
</strong>
</p>


</span>
<span />
<!--v-if-->
<!--v-if-->
</div>
Expand Down Expand Up @@ -516,63 +492,7 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `
<div
class="blockBody"
>
<!-- eslint-disable-next-line vue/no-v-html -->
<span>
<p>
Solution steps:
</p>


<ol>


<li>
Lorem ipsum dolor sit amet, consectetur
<strong>
adipiscing
</strong>
elit. Proin id nulla placerat, tristique ex at, euismod dui.
</li>


<li>
Copy this into somewhere
</li>


<li>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin id nulla placerat, tristique ex at, euismod dui.
</li>


<li>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin id nulla placerat, tristique ex at, euismod dui.
<br />

Testing more code
</li>


</ol>


<ul>


<li>
Unordered item 1
</li>


<li>
Unordered item 2
</li>


</ul>


</span>
<span />
<!--v-if-->
</div>
</div>
Expand Down Expand Up @@ -1060,23 +980,9 @@ exports[`AskAssistantChat > renders end of session chat correctly 1`] = `
<div
class="textMessage"
>
<!-- eslint-disable-next-line vue/no-v-html -->

<!-- eslint-disable-next-line vue/no-v-html -->
<div
class="assistantText"
>
<p>
Hi Max! Here is my top solution to fix the error in your
<strong>
Transform data
</strong>
node👇
</p>


</div>

/>
<!--v-if-->
<!--v-if-->
</div>
Expand Down Expand Up @@ -1310,23 +1216,9 @@ exports[`AskAssistantChat > renders streaming chat correctly 1`] = `
<div
class="textMessage"
>
<!-- eslint-disable-next-line vue/no-v-html -->

<!-- eslint-disable-next-line vue/no-v-html -->
<div
class="assistantText"
>
<p>
Hi Max! Here is my top solution to fix the error in your
<strong>
Transform data
</strong>
node👇
</p>


</div>

/>
<!--v-if-->
<!--v-if-->
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ withDefaults(defineProps<ActionBoxProps>(), {
<div :class="$style.description" @click="$emit('descriptionClick', $event)">
<N8nText color="text-base">
<slot name="description">
<span v-html="description"></span>
<span v-n8n-html="description"></span>
</slot>
</N8nText>
</div>
Expand All @@ -61,7 +61,7 @@ withDefaults(defineProps<ActionBoxProps>(), {
:class="$style.callout"
>
<N8nText color="text-base">
<span size="small" v-html="calloutText"></span>
<span size="small" v-n8n-html="calloutText"></span>
</N8nText>
</N8nCallout>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ const onTooltipClick = (item: string, event: MouseEvent) => emit('tooltipClick',
<div v-for="item in items" :key="item.id" :class="$style.accordionItem">
<n8n-tooltip :disabled="!item.tooltip">
<template #content>
<div @click="onTooltipClick(item.id, $event)" v-html="item.tooltip"></div>
<div @click="onTooltipClick(item.id, $event)" v-n8n-html="item.tooltip"></div>
</template>
<N8nIcon :icon="item.icon" :color="item.iconColor" size="small" class="mr-2xs" />
</n8n-tooltip>
<N8nText size="small" color="text-base">{{ item.label }}</N8nText>
</div>
</div>
<N8nText color="text-base" size="small" align="left">
<span v-html="description"></span>
<span v-n8n-html="description"></span>
</N8nText>
<slot name="customContent"></slot>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const addTargetBlank = (html: string) =>
<N8nTooltip placement="top" :popper-class="$style.tooltipPopper" :show-after="300">
<N8nIcon icon="question-circle" size="small" />
<template #content>
<div v-html="addTargetBlank(tooltipText)" />
<div v-n8n-html="addTargetBlank(tooltipText)" />
</template>
</N8nTooltip>
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ const onCheckboxChange = (index: number) => {
@click="onClick"
@mousedown="onMouseDown"
@change="onChange"
v-html="htmlContent"
v-n8n-html="htmlContent"
/>
<div v-else :class="$style.markdown">
<div v-for="(_, index) in loadingBlocks" :key="index">
Expand Down
2 changes: 1 addition & 1 deletion packages/design-system/src/components/N8nNotice/Notice.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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"
/>
</slot>
</N8nText>
Expand Down
2 changes: 1 addition & 1 deletion packages/design-system/src/components/N8nSticky/Sticky.vue
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const onInputScroll = (event: WheelEvent) => {
</div>
<div v-if="editMode && shouldShowFooter" :class="$style.footer">
<N8nText size="xsmall" align="right">
<span v-html="t('sticky.markdownHint')"></span>
<span v-n8n-html="t('sticky.markdownHint')"></span>
</N8nText>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/design-system/src/components/N8nTabs/Tabs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const scrollRight = () => scroll(50);
>
<N8nTooltip :disabled="!option.tooltip" placement="bottom">
<template #content>
<div @click="handleTooltipClick(option.value, $event)" v-html="option.tooltip" />
<div @click="handleTooltipClick(option.value, $event)" v-n8n-html="option.tooltip" />
</template>
<a
v-if="option.href"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ defineOptions({
<slot />
<template #content>
<slot name="content">
<div v-html="props.content"></div>
<div v-n8n-html="props.content"></div>
</slot>
<div
v-if="props.buttons.length"
Expand Down
1 change: 1 addition & 0 deletions packages/design-system/src/directives/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { n8nHtml } from './n8n-html';
28 changes: 28 additions & 0 deletions packages/design-system/src/directives/n8n-html.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { render } from '@testing-library/vue';
import { defineComponent } from 'vue';
import { n8nHtml } from './n8n-html';

const TestComponent = defineComponent({
setup() {
return {
unsafeHtml:
'<span>text</span><a href="https://malicious.com" onclick="alert(1)">malicious</a><img alt="Ok" src="./images/logo.svg" onerror="alert(2)<script>alert(3)</script>" />',
};
},
template: '<div v-n8n-html="unsafeHtml"></div>',
});

describe('Directive n8n-html', () => {
it('should sanitize html', async () => {
cstuncsik marked this conversation as resolved.
Show resolved Hide resolved
const { html } = render(TestComponent, {
global: {
directives: {
'n8n-html': n8nHtml,
},
},
});
expect(html()).toBe(
'<div><span>text</span><a href="https://malicious.com">malicious</a><img alt="Ok" src="./images/logo.svg"></div>',
);
});
});
15 changes: 15 additions & 0 deletions packages/design-system/src/directives/n8n-html.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import sanitize from 'sanitize-html';
import type { DirectiveBinding, ObjectDirective } from 'vue';

const configuredSanitize = (html: string) => sanitize(html, {

Check failure on line 4 in packages/design-system/src/directives/n8n-html.ts

View workflow job for this annotation

GitHub Actions / Lint / Lint

Replace `·` with `⏎↹`
allowedTags: sanitize.defaults.allowedTags.concat([ 'img' ])

Check failure on line 5 in packages/design-system/src/directives/n8n-html.ts

View workflow job for this annotation

GitHub Actions / Lint / Lint

Replace `↹allowedTags:·sanitize.defaults.allowedTags.concat([·'img'·])` with `↹↹allowedTags:·sanitize.defaults.allowedTags.concat(['img']),`

Check failure on line 5 in packages/design-system/src/directives/n8n-html.ts

View workflow job for this annotation

GitHub Actions / Lint / Lint

Missing trailing comma
});

Check failure on line 6 in packages/design-system/src/directives/n8n-html.ts

View workflow job for this annotation

GitHub Actions / Lint / Lint

Insert `↹`

export const n8nHtml: ObjectDirective = {
beforeMount(el: HTMLElement, binding: DirectiveBinding) {
el.innerHTML = configuredSanitize(binding.value);
},
beforeUpdate(el: HTMLElement, binding: DirectiveBinding) {
el.innerHTML = configuredSanitize(binding.value);
},
};
1 change: 1 addition & 0 deletions packages/design-system/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export * from './components';
export * from './plugin';
export * from './types';
export * from './utils';
export * from './directives';
export { locale };
5 changes: 5 additions & 0 deletions packages/design-system/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Component, Plugin } from 'vue';
import * as components from './components';
import * as directives from './directives';

export interface N8nPluginOptions {}

Expand All @@ -8,5 +9,9 @@ export const N8nPlugin: Plugin<N8nPluginOptions> = {
for (const [name, component] of Object.entries(components)) {
app.component(name, component as unknown as Component);
}

for (const [name, directive] of Object.entries(directives)) {
app.directive(name, directive);
}
},
};
2 changes: 1 addition & 1 deletion packages/editor-ui/src/components/Error/NodeErrorView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ async function onAskAssistantClick() {
v-if="error.description || error.context?.descriptionKey"
data-test-id="node-error-description"
class="node-error-view__header-description"
v-html="getErrorDescription()"
v-n8n-html="getErrorDescription()"
></div>
<div
v-if="isAskAssistantAvailable"
Expand Down
2 changes: 1 addition & 1 deletion packages/editor-ui/src/components/ExpressionEditModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ async function onDrop(expression: string, event: MouseEvent) {
<N8nText
:class="$style.tip"
size="small"
v-html="i18n.baseText('expressionTip.javascript')"
v-n8n-html="i18n.baseText('expressionTip.javascript')"
/>
</div>

Expand Down
4 changes: 2 additions & 2 deletions packages/editor-ui/src/components/FeatureComingSoon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export default defineComponent({
</div>
<div v-if="featureInfo.infoText" class="mb-l">
<n8n-info-tip theme="info" type="note">
<span v-html="$locale.baseText(featureInfo.infoText)"></span>
<span v-n8n-html="$locale.baseText(featureInfo.infoText)"></span>
</n8n-info-tip>
</div>
<div :class="$style.actionBoxContainer">
Expand All @@ -68,7 +68,7 @@ export default defineComponent({
@click:button="openLinkPage"
>
<template #heading>
<span v-html="$locale.baseText(featureInfo.actionBoxTitle)" />
<span v-n8n-html="$locale.baseText(featureInfo.actionBoxTitle)" />
</template>
</n8n-action-box>
</div>
Expand Down
Loading
Loading