mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(n8n Form Trigger Node): Sanitize HTML for formNode (#13595)
This commit is contained in:
parent
c1fe785174
commit
20dfaa3be6
|
@ -6,7 +6,7 @@ import {
|
||||||
type IWebhookResponseData,
|
type IWebhookResponseData,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import { renderForm } from './utils';
|
import { renderForm, sanitizeHtml } from './utils';
|
||||||
|
|
||||||
export const renderFormNode = async (
|
export const renderFormNode = async (
|
||||||
context: IWebhookFunctions,
|
context: IWebhookFunctions,
|
||||||
|
@ -42,6 +42,12 @@ export const renderFormNode = async (
|
||||||
) as string) || 'Submit';
|
) as string) || 'Submit';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const field of fields) {
|
||||||
|
if (field.fieldType === 'html') {
|
||||||
|
field.html = sanitizeHtml(field.html as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const appendAttribution = context.evaluateExpression(
|
const appendAttribution = context.evaluateExpression(
|
||||||
`{{ $('${trigger?.name}').params.options?.appendAttribution === false ? false : true }}`,
|
`{{ $('${trigger?.name}').params.options?.appendAttribution === false ? false : true }}`,
|
||||||
) as boolean;
|
) as boolean;
|
||||||
|
|
113
packages/nodes-base/nodes/Form/test/formNodeUtils.test.ts
Normal file
113
packages/nodes-base/nodes/Form/test/formNodeUtils.test.ts
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import { type Response } from 'express';
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import {
|
||||||
|
type FormFieldsParameter,
|
||||||
|
type IWebhookFunctions,
|
||||||
|
type NodeTypeAndVersion,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { renderFormNode } from '../formNodeUtils';
|
||||||
|
|
||||||
|
describe('formNodeUtils', () => {
|
||||||
|
it('should sanitize custom html', async () => {
|
||||||
|
const executeFunctions = mock<IWebhookFunctions>();
|
||||||
|
executeFunctions.getNode.mockReturnValue({ typeVersion: 2.1 } as any);
|
||||||
|
executeFunctions.getNodeParameter.calledWith('options').mockReturnValue({
|
||||||
|
formTitle: 'Test Title',
|
||||||
|
formDescription: 'Test Description',
|
||||||
|
buttonLabel: 'Test Button Label',
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockRender = jest.fn();
|
||||||
|
|
||||||
|
const formFields: FormFieldsParameter = [
|
||||||
|
{
|
||||||
|
fieldLabel: 'Custom HTML',
|
||||||
|
fieldType: 'html',
|
||||||
|
html: '<div>Test HTML</div>',
|
||||||
|
requiredField: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldLabel: 'Custom HTML',
|
||||||
|
fieldType: 'html',
|
||||||
|
html: '<script>Test HTML</script>',
|
||||||
|
requiredField: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldLabel: 'Custom HTML',
|
||||||
|
fieldType: 'html',
|
||||||
|
html: '<style>Test HTML</style>',
|
||||||
|
requiredField: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldLabel: 'Custom HTML',
|
||||||
|
fieldType: 'html',
|
||||||
|
html: '<style>Test HTML</style><div>hihihi</div><script>Malicious script here</script>',
|
||||||
|
requiredField: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
executeFunctions.getNodeParameter.calledWith('formFields.values').mockReturnValue(formFields);
|
||||||
|
|
||||||
|
const responseMock = mock<Response>({ render: mockRender } as any);
|
||||||
|
const triggerMock = mock<NodeTypeAndVersion>({ name: 'triggerName' } as any);
|
||||||
|
|
||||||
|
await renderFormNode(executeFunctions, responseMock, triggerMock, formFields, 'test');
|
||||||
|
|
||||||
|
expect(mockRender).toHaveBeenCalledWith('form-trigger', {
|
||||||
|
appendAttribution: true,
|
||||||
|
buttonLabel: 'Test Button Label',
|
||||||
|
formDescription: 'Test Description',
|
||||||
|
formDescriptionMetadata: 'Test Description',
|
||||||
|
formFields: [
|
||||||
|
{
|
||||||
|
defaultValue: '',
|
||||||
|
errorId: 'error-field-0',
|
||||||
|
html: '<div>Test HTML</div>',
|
||||||
|
id: 'field-0',
|
||||||
|
inputRequired: '',
|
||||||
|
isHtml: true,
|
||||||
|
label: 'Custom HTML',
|
||||||
|
placeholder: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultValue: '',
|
||||||
|
errorId: 'error-field-1',
|
||||||
|
html: '',
|
||||||
|
id: 'field-1',
|
||||||
|
inputRequired: '',
|
||||||
|
isHtml: true,
|
||||||
|
label: 'Custom HTML',
|
||||||
|
placeholder: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultValue: '',
|
||||||
|
errorId: 'error-field-2',
|
||||||
|
html: '',
|
||||||
|
id: 'field-2',
|
||||||
|
inputRequired: '',
|
||||||
|
isHtml: true,
|
||||||
|
label: 'Custom HTML',
|
||||||
|
placeholder: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultValue: '',
|
||||||
|
errorId: 'error-field-3',
|
||||||
|
html: '<div>hihihi</div>',
|
||||||
|
id: 'field-3',
|
||||||
|
inputRequired: '',
|
||||||
|
isHtml: true,
|
||||||
|
label: 'Custom HTML',
|
||||||
|
placeholder: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
formSubmittedHeader: undefined,
|
||||||
|
formSubmittedText: 'Your response has been recorded',
|
||||||
|
formTitle: 'Test Title',
|
||||||
|
n8nWebsiteLink: 'https://n8n.io/?utm_source=n8n-internal&utm_medium=form-trigger',
|
||||||
|
testRun: true,
|
||||||
|
useResponseData: true,
|
||||||
|
validForm: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue