diff --git a/cypress/e2e/1858-PAY-can-use-context-menu.ts b/cypress/e2e/1858-PAY-can-use-context-menu.ts new file mode 100644 index 0000000000000..6727df41667e1 --- /dev/null +++ b/cypress/e2e/1858-PAY-can-use-context-menu.ts @@ -0,0 +1,21 @@ +import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; + +const WorkflowPage = new WorkflowPageClass(); + +describe('PAY-1858 context menu', () => { + it('can use context menu on saved workflow', () => { + WorkflowPage.actions.visit(); + cy.createFixtureWorkflow('Test_workflow_filter.json', 'test'); + + WorkflowPage.getters.canvasNodes().should('have.length', 5); + WorkflowPage.actions.deleteNodeFromContextMenu('Then'); + WorkflowPage.getters.canvasNodes().should('have.length', 4); + + WorkflowPage.actions.hitSaveWorkflow(); + + cy.reload(); + WorkflowPage.getters.canvasNodes().should('have.length', 4); + WorkflowPage.actions.deleteNodeFromContextMenu('Code'); + WorkflowPage.getters.canvasNodes().should('have.length', 3); + }); +}); diff --git a/cypress/e2e/33-settings-personal.cy.ts b/cypress/e2e/33-settings-personal.cy.ts index 73dc7476b8ae0..372f14d231cd4 100644 --- a/cypress/e2e/33-settings-personal.cy.ts +++ b/cypress/e2e/33-settings-personal.cy.ts @@ -35,7 +35,8 @@ describe('Personal Settings', () => { successToast().find('.el-notification__closeBtn').click(); }); }); - it('not allow malicious values for personal data', () => { + // eslint-disable-next-line n8n-local-rules/no-skipped-tests + it.skip('not allow malicious values for personal data', () => { cy.visit('/settings/personal'); INVALID_NAMES.forEach((name) => { cy.getByTestId('personal-data-form').find('input[name="firstName"]').clear().type(name); diff --git a/packages/cli/package.json b/packages/cli/package.json index 54ca084465300..80bf0c4e62ab5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -155,6 +155,7 @@ "reflect-metadata": "0.2.2", "replacestream": "4.0.3", "samlify": "2.8.9", + "sanitize-html": "2.12.1", "semver": "7.5.4", "shelljs": "0.8.5", "simple-git": "3.17.0", diff --git a/packages/cli/src/GenericHelpers.ts b/packages/cli/src/GenericHelpers.ts index 300f24f9f9355..57dcb60207f73 100644 --- a/packages/cli/src/GenericHelpers.ts +++ b/packages/cli/src/GenericHelpers.ts @@ -9,7 +9,7 @@ import type { UserUpdatePayload, } from '@/requests'; import { BadRequestError } from './errors/response-errors/bad-request.error'; -import { NoXss } from './databases/utils/customValidators'; +import { NoXss } from '@/validators/no-xss.validator'; export async function validateEntity( entity: diff --git a/packages/cli/src/databases/entities/User.ts b/packages/cli/src/databases/entities/User.ts index d24af19742e6a..dad8bbbe8000c 100644 --- a/packages/cli/src/databases/entities/User.ts +++ b/packages/cli/src/databases/entities/User.ts @@ -13,7 +13,7 @@ import { IsEmail, IsString, Length } from 'class-validator'; import type { IUser, IUserSettings } from 'n8n-workflow'; import type { SharedWorkflow } from './SharedWorkflow'; import type { SharedCredentials } from './SharedCredentials'; -import { NoXss } from '../utils/customValidators'; +import { NoXss } from '@/validators/no-xss.validator'; import { objectRetriever, lowerCaser } from '../utils/transformers'; import { WithTimestamps, jsonColumnType } from './AbstractEntity'; import type { IPersonalizationSurveyAnswers } from '@/Interfaces'; @@ -25,6 +25,7 @@ import { } from '@/permissions/global-roles'; import { hasScope, type ScopeOptions, type Scope } from '@n8n/permissions'; import type { ProjectRelation } from './ProjectRelation'; +import { NoUrl } from '@/validators/no-url.validator'; export type GlobalRole = 'global:owner' | 'global:admin' | 'global:member'; export type AssignableRole = Exclude; @@ -51,12 +52,14 @@ export class User extends WithTimestamps implements IUser { @Column({ length: 32, nullable: true }) @NoXss() + @NoUrl() @IsString({ message: 'First name must be of type string.' }) @Length(1, 32, { message: 'First name must be $constraint1 to $constraint2 characters long.' }) firstName: string; @Column({ length: 32, nullable: true }) @NoXss() + @NoUrl() @IsString({ message: 'Last name must be of type string.' }) @Length(1, 32, { message: 'Last name must be $constraint1 to $constraint2 characters long.' }) lastName: string; diff --git a/packages/cli/src/databases/utils/__tests__/customValidators.test.ts b/packages/cli/src/databases/utils/__tests__/customValidators.test.ts deleted file mode 100644 index df906b36d6a52..0000000000000 --- a/packages/cli/src/databases/utils/__tests__/customValidators.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { NoXss } from '@db/utils/customValidators'; -import { validate } from 'class-validator'; - -describe('customValidators', () => { - describe('NoXss', () => { - class Person { - @NoXss() - name: string; - } - const person = new Person(); - - const invalidNames = ['http://google.com', '"]; + + for (const str of XSS_STRINGS) { + test(`should block ${str}`, async () => { + entity.name = str; + const errors = await validate(entity); + expect(errors).toHaveLength(1); + const [error] = errors; + expect(error.property).toEqual('name'); + expect(error.constraints).toEqual({ NoXss: 'Potentially malicious string' }); + }); + } + }); + + describe('Names', () => { + const VALID_NAMES = [ + 'Johann Strauß', + 'Вагиф Сәмәдоғлу', + 'René Magritte', + 'সুকুমার রায়', + 'མགོན་པོ་རྡོ་རྗེ།', + 'عبدالحليم حافظ', + ]; + + for (const name of VALID_NAMES) { + test(`should allow ${name}`, async () => { + entity.name = name; + expect(await validate(entity)).toBeEmptyArray(); + }); + } + }); + + describe('ISO-8601 timestamps', () => { + const VALID_TIMESTAMPS = ['2022-01-01T00:00:00.000Z', '2022-01-01T00:00:00.000+02:00']; + + for (const timestamp of VALID_TIMESTAMPS) { + test(`should allow ${timestamp}`, async () => { + entity.timestamp = timestamp; + await expect(validate(entity)).resolves.toBeEmptyArray(); + }); + } + }); + + describe('Semver versions', () => { + const VALID_VERSIONS = ['1.0.0', '1.0.0-alpha.1']; + + for (const version of VALID_VERSIONS) { + test(`should allow ${version}`, async () => { + entity.version = version; + await expect(validate(entity)).resolves.toBeEmptyArray(); + }); + } + }); +}); diff --git a/packages/cli/src/validators/no-url.validator.ts b/packages/cli/src/validators/no-url.validator.ts new file mode 100644 index 0000000000000..1df05fed5fa0d --- /dev/null +++ b/packages/cli/src/validators/no-url.validator.ts @@ -0,0 +1,27 @@ +import type { ValidationOptions, ValidatorConstraintInterface } from 'class-validator'; +import { registerDecorator, ValidatorConstraint } from 'class-validator'; + +const URL_REGEX = /^(https?:\/\/|www\.)/i; + +@ValidatorConstraint({ name: 'NoUrl', async: false }) +class NoUrlConstraint implements ValidatorConstraintInterface { + validate(value: string) { + return !URL_REGEX.test(value); + } + + defaultMessage() { + return 'Potentially malicious string'; + } +} + +export function NoUrl(options?: ValidationOptions) { + return function (object: object, propertyName: string) { + registerDecorator({ + name: 'NoUrl', + target: object.constructor, + propertyName, + options, + validator: NoUrlConstraint, + }); + }; +} diff --git a/packages/cli/src/validators/no-xss.validator.ts b/packages/cli/src/validators/no-xss.validator.ts new file mode 100644 index 0000000000000..8075309df9923 --- /dev/null +++ b/packages/cli/src/validators/no-xss.validator.ts @@ -0,0 +1,26 @@ +import type { ValidationOptions, ValidatorConstraintInterface } from 'class-validator'; +import { registerDecorator, ValidatorConstraint } from 'class-validator'; +import sanitizeHtml from 'sanitize-html'; + +@ValidatorConstraint({ name: 'NoXss', async: false }) +class NoXssConstraint implements ValidatorConstraintInterface { + validate(value: string) { + return value === sanitizeHtml(value, { allowedTags: [], allowedAttributes: {} }); + } + + defaultMessage() { + return 'Potentially malicious string'; + } +} + +export function NoXss(options?: ValidationOptions) { + return function (object: object, propertyName: string) { + registerDecorator({ + name: 'NoXss', + target: object.constructor, + propertyName, + options, + validator: NoXssConstraint, + }); + }; +} diff --git a/packages/editor-ui/src/App.vue b/packages/editor-ui/src/App.vue index 835ddd11c052e..cc9c1415003eb 100644 --- a/packages/editor-ui/src/App.vue +++ b/packages/editor-ui/src/App.vue @@ -47,7 +47,7 @@ import AskAssistantFloatingButton from '@/components/AskAssistant/AskAssistantFl import { HIRING_BANNER, VIEWS } from '@/constants'; import { loadLanguage } from '@/plugins/i18n'; -import useGlobalLinkActions from '@/composables/useGlobalLinkActions'; +import { useGlobalLinkActions } from '@/composables/useGlobalLinkActions'; import { useExternalHooks } from '@/composables/useExternalHooks'; import { useToast } from '@/composables/useToast'; import { useCloudPlanStore } from '@/stores/cloudPlan.store'; diff --git a/packages/editor-ui/src/__tests__/mocks.ts b/packages/editor-ui/src/__tests__/mocks.ts index dad814f6eb533..dac9c9c4789d5 100644 --- a/packages/editor-ui/src/__tests__/mocks.ts +++ b/packages/editor-ui/src/__tests__/mocks.ts @@ -25,7 +25,7 @@ import { SET_NODE_TYPE, STICKY_NODE_TYPE, } from '@/constants'; -import type { INodeUi } from '@/Interface'; +import type { INodeUi, IWorkflowDb } from '@/Interface'; export const mockNode = ({ id = uuid(), @@ -147,6 +147,35 @@ export function createTestWorkflowObject({ }); } +export function createTestWorkflow({ + id = uuid(), + name = 'Test Workflow', + nodes = [], + connections = {}, + active = false, + settings = { + timezone: 'DEFAULT', + executionOrder: 'v1', + }, + pinData = {}, + ...rest +}: Partial = {}): IWorkflowDb { + return { + createdAt: '', + updatedAt: '', + id, + name, + nodes, + connections, + active, + settings, + versionId: '1', + meta: {}, + pinData, + ...rest, + }; +} + export function createTestNode(node: Partial = {}): INode { return { id: uuid(), diff --git a/packages/editor-ui/src/api/assistant.ts b/packages/editor-ui/src/api/assistant.ts index d9e9ec0cded21..4b1d127ef15c6 100644 --- a/packages/editor-ui/src/api/assistant.ts +++ b/packages/editor-ui/src/api/assistant.ts @@ -9,7 +9,14 @@ export function chatWithAssistant( onDone: () => void, onError: (e: Error) => void, ): void { - void streamRequest(ctx, '/ai-assistant/chat', payload, onMessageUpdated, onDone, onError); + void streamRequest( + ctx, + '/ai-assistant/chat', + payload, + onMessageUpdated, + onDone, + onError, + ); } export async function replaceCode( diff --git a/packages/editor-ui/src/api/users.ts b/packages/editor-ui/src/api/users.ts index a27faea7b788a..aedbd0b594337 100644 --- a/packages/editor-ui/src/api/users.ts +++ b/packages/editor-ui/src/api/users.ts @@ -90,7 +90,6 @@ export async function changePassword( } export type UpdateCurrentUserParams = { - id?: string; firstName?: string; lastName?: string; email: string; diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 2b1516aa514a9..ec26ef996402d 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -50,15 +50,17 @@ data-test-id="run-data-pane-header" @click.stop > - + + + {nodeName} node to see the changes", "banners.confirmEmail.message.1": "To secure your account and prevent future access issues, please confirm your", "banners.confirmEmail.message.2": "email address.", diff --git a/packages/editor-ui/src/stores/sso.store.ts b/packages/editor-ui/src/stores/sso.store.ts index 63c71c3899a92..d9d522274ce6f 100644 --- a/packages/editor-ui/src/stores/sso.store.ts +++ b/packages/editor-ui/src/stores/sso.store.ts @@ -70,7 +70,6 @@ export const useSSOStore = defineStore('sso', () => { const updateUser = async (params: { firstName: string; lastName: string }) => await updateCurrentUser(rootStore.restApiContext, { - id: usersStore.currentUser!.id, email: usersStore.currentUser!.email!, ...params, }); diff --git a/packages/editor-ui/src/stores/workflows.store.ts b/packages/editor-ui/src/stores/workflows.store.ts index 18fc0261bfa36..d60e7db763728 100644 --- a/packages/editor-ui/src/stores/workflows.store.ts +++ b/packages/editor-ui/src/stores/workflows.store.ts @@ -699,6 +699,10 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { }; } + function setWorkflowScopes(scopes: IWorkflowDb['scopes']): void { + workflow.value.scopes = scopes; + } + function setWorkflowMetadata(metadata: WorkflowMetadata | undefined): void { workflow.value.meta = metadata; } @@ -1634,6 +1638,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { setWorkflowTagIds, addWorkflowTagIds, removeWorkflowTagId, + setWorkflowScopes, setWorkflowMetadata, addToWorkflowMetadata, setWorkflow, diff --git a/packages/editor-ui/src/utils/__tests__/apiUtils.spec.ts b/packages/editor-ui/src/utils/__tests__/apiUtils.spec.ts new file mode 100644 index 0000000000000..cefb6f3389bb7 --- /dev/null +++ b/packages/editor-ui/src/utils/__tests__/apiUtils.spec.ts @@ -0,0 +1,112 @@ +import { STREAM_SEPERATOR, streamRequest } from '../apiUtils'; + +describe('streamRequest', () => { + it('should stream data from the API endpoint', async () => { + const encoder = new TextEncoder(); + const mockResponse = new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode(`${JSON.stringify({ chunk: 1 })}${STREAM_SEPERATOR}`)); + controller.enqueue(encoder.encode(`${JSON.stringify({ chunk: 2 })}${STREAM_SEPERATOR}`)); + controller.enqueue(encoder.encode(`${JSON.stringify({ chunk: 3 })}${STREAM_SEPERATOR}`)); + controller.close(); + }, + }); + + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + body: mockResponse, + }); + + global.fetch = mockFetch; + + const onChunkMock = vi.fn(); + const onDoneMock = vi.fn(); + const onErrorMock = vi.fn(); + + await streamRequest( + { + baseUrl: 'https://api.example.com', + pushRef: '', + }, + '/data', + { key: 'value' }, + onChunkMock, + onDoneMock, + onErrorMock, + ); + + expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/data', { + method: 'POST', + body: JSON.stringify({ key: 'value' }), + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'browser-id': expect.stringContaining('-'), + }, + }); + + expect(onChunkMock).toHaveBeenCalledTimes(3); + expect(onChunkMock).toHaveBeenNthCalledWith(1, { chunk: 1 }); + expect(onChunkMock).toHaveBeenNthCalledWith(2, { chunk: 2 }); + expect(onChunkMock).toHaveBeenNthCalledWith(3, { chunk: 3 }); + + expect(onDoneMock).toHaveBeenCalledTimes(1); + expect(onErrorMock).not.toHaveBeenCalled(); + }); + + it('should handle broken stream data', async () => { + const encoder = new TextEncoder(); + const mockResponse = new ReadableStream({ + start(controller) { + controller.enqueue( + encoder.encode(`${JSON.stringify({ chunk: 1 })}${STREAM_SEPERATOR}{"chunk": `), + ); + controller.enqueue(encoder.encode(`2}${STREAM_SEPERATOR}{"ch`)); + controller.enqueue(encoder.encode('unk":')); + controller.enqueue(encoder.encode(`3}${STREAM_SEPERATOR}`)); + controller.close(); + }, + }); + + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + body: mockResponse, + }); + + global.fetch = mockFetch; + + const onChunkMock = vi.fn(); + const onDoneMock = vi.fn(); + const onErrorMock = vi.fn(); + + await streamRequest( + { + baseUrl: 'https://api.example.com', + pushRef: '', + }, + '/data', + { key: 'value' }, + onChunkMock, + onDoneMock, + onErrorMock, + ); + + expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/data', { + method: 'POST', + body: JSON.stringify({ key: 'value' }), + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'browser-id': expect.stringContaining('-'), + }, + }); + + expect(onChunkMock).toHaveBeenCalledTimes(3); + expect(onChunkMock).toHaveBeenNthCalledWith(1, { chunk: 1 }); + expect(onChunkMock).toHaveBeenNthCalledWith(2, { chunk: 2 }); + expect(onChunkMock).toHaveBeenNthCalledWith(3, { chunk: 3 }); + + expect(onDoneMock).toHaveBeenCalledTimes(1); + expect(onErrorMock).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/editor-ui/src/utils/apiUtils.ts b/packages/editor-ui/src/utils/apiUtils.ts index e9201b7c331ea..b07f8910b90c8 100644 --- a/packages/editor-ui/src/utils/apiUtils.ts +++ b/packages/editor-ui/src/utils/apiUtils.ts @@ -3,7 +3,6 @@ import axios from 'axios'; import { ApplicationError, jsonParse, type GenericValue, type IDataObject } from 'n8n-workflow'; import type { IExecutionFlattedResponse, IExecutionResponse, IRestApiContext } from '@/Interface'; import { parse } from 'flatted'; -import type { ChatRequest } from '@/types/assistant.types'; import { assert } from '@/utils/assert'; const BROWSER_ID_STORAGE_KEY = 'n8n-browserId'; @@ -14,6 +13,7 @@ if (!browserId && 'randomUUID' in crypto) { } export const NO_NETWORK_ERROR_CODE = 999; +export const STREAM_SEPERATOR = '⧉⇋⇋➽⌑⧉§§\n'; export class ResponseError extends ApplicationError { // The HTTP status code of response @@ -194,15 +194,15 @@ export function unflattenExecutionData(fullExecutionData: IExecutionFlattedRespo return returnData; } -export const streamRequest = async ( +export async function streamRequest( context: IRestApiContext, apiEndpoint: string, - payload: ChatRequest.RequestPayload, - onChunk?: (chunk: ChatRequest.ResponsePayload) => void, + payload: object, + onChunk?: (chunk: T) => void, onDone?: () => void, onError?: (e: Error) => void, - separator = '⧉⇋⇋➽⌑⧉§§\n', -): Promise => { + separator = STREAM_SEPERATOR, +): Promise { const headers: Record = { 'Content-Type': 'application/json', }; @@ -223,23 +223,36 @@ export const streamRequest = async ( const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); + let buffer = ''; + async function readStream() { const { done, value } = await reader.read(); if (done) { onDone?.(); return; } - const chunk = decoder.decode(value); - const splitChunks = chunk.split(separator); + buffer += chunk; + const splitChunks = buffer.split(separator); + + buffer = ''; for (const splitChunk of splitChunks) { - if (splitChunk && onChunk) { + if (splitChunk) { + let data: T; + try { + data = jsonParse(splitChunk, { errorMessage: 'Invalid json' }); + } catch (e) { + // incomplete json. append to buffer to complete + buffer += splitChunk; + + continue; + } + try { - onChunk(jsonParse(splitChunk, { errorMessage: 'Invalid json chunk in stream' })); + onChunk?.(data); } catch (e: unknown) { if (e instanceof Error) { - console.log(`${e.message}: ${splitChunk}`); onError?.(e); } } @@ -257,4 +270,4 @@ export const streamRequest = async ( assert(e instanceof Error); onError?.(e); } -}; +} diff --git a/packages/editor-ui/src/views/NodeView.v2.vue b/packages/editor-ui/src/views/NodeView.v2.vue index 465505cc438d3..3a8e2411cd985 100644 --- a/packages/editor-ui/src/views/NodeView.v2.vue +++ b/packages/editor-ui/src/views/NodeView.v2.vue @@ -18,6 +18,7 @@ import CanvasRunWorkflowButton from '@/components/canvas/elements/buttons/Canvas import { useI18n } from '@/composables/useI18n'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { useRunWorkflow } from '@/composables/useRunWorkflow'; +import { useGlobalLinkActions } from '@/composables/useGlobalLinkActions'; import type { AddedNodesAndConnections, IExecutionResponse, @@ -135,6 +136,7 @@ const templatesStore = useTemplatesStore(); const canvasEventBus = createEventBus(); +const { registerCustomAction } = useGlobalLinkActions(); const { runWorkflow, stopCurrentExecution, stopWaitingForWebhook } = useRunWorkflow({ router }); const { updateNodePosition, @@ -844,6 +846,10 @@ async function onOpenSelectiveNodeCreator(node: string, connectionType: NodeConn nodeCreatorStore.openSelectiveNodeCreator({ node, connectionType }); } +async function onOpenNodeCreatorForTriggerNodes(source: NodeCreatorOpenSource) { + nodeCreatorStore.openNodeCreatorForTriggerNodes(source); +} + function onOpenNodeCreatorFromCanvas(source: NodeCreatorOpenSource) { onOpenNodeCreator({ createNodeActive: true, source }); } @@ -1347,29 +1353,36 @@ function onClickPane(position: CanvasNode['position']) { */ function registerCustomActions() { - // @TODO Implement these - // this.registerCustomAction({ - // key: 'openNodeDetail', - // action: ({ node }: { node: string }) => { - // this.nodeSelectedByName(node, true); - // }, - // }); - // - // this.registerCustomAction({ - // key: 'openSelectiveNodeCreator', - // action: this.openSelectiveNodeCreator, - // }); - // - // this.registerCustomAction({ - // key: 'showNodeCreator', - // action: () => { - // this.ndvStore.activeNodeName = null; - // - // void this.$nextTick(() => { - // this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.TAB); - // }); - // }, - // }); + registerCustomAction({ + key: 'openNodeDetail', + action: ({ node }: { node: string }) => { + setNodeActiveByName(node); + }, + }); + + registerCustomAction({ + key: 'openSelectiveNodeCreator', + action: ({ + connectiontype: connectionType, + node, + }: { + connectiontype: NodeConnectionType; + node: string; + }) => { + void onOpenSelectiveNodeCreator(node, connectionType); + }, + }); + + registerCustomAction({ + key: 'showNodeCreator', + action: () => { + ndvStore.activeNodeName = null; + + void nextTick(() => { + void onOpenNodeCreatorForTriggerNodes(NODE_CREATOR_OPEN_SOURCES.TAB); + }); + }, + }); } /** diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 4ef91b2b1a875..23fb4e034653e 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -261,7 +261,7 @@ import { VALID_WORKFLOW_IMPORT_URL_REGEX, } from '@/constants'; -import useGlobalLinkActions from '@/composables/useGlobalLinkActions'; +import { useGlobalLinkActions } from '@/composables/useGlobalLinkActions'; import { useNodeHelpers } from '@/composables/useNodeHelpers'; import useCanvasMouseSelect from '@/composables/useCanvasMouseSelect'; import { useExecutionDebugging } from '@/composables/useExecutionDebugging'; diff --git a/packages/editor-ui/src/views/SettingsPersonalView.vue b/packages/editor-ui/src/views/SettingsPersonalView.vue index 60b40fb6863ff..0ddbe6e5a5469 100644 --- a/packages/editor-ui/src/views/SettingsPersonalView.vue +++ b/packages/editor-ui/src/views/SettingsPersonalView.vue @@ -173,7 +173,6 @@ async function updateUserBasicInfo(userBasicInfo: UserBasicDetailsWithMfa) { } await usersStore.updateUser({ - id: usersStore.currentUserId, firstName: userBasicInfo.firstName, lastName: userBasicInfo.lastName, email: userBasicInfo.email, diff --git a/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts b/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts index d9de41cc15ba8..fbb1a6af5622f 100644 --- a/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts +++ b/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts @@ -290,7 +290,11 @@ export class RespondToWebhook implements INodeType { const items = this.getInputData(); const nodeVersion = this.getNode().typeVersion; - const WEBHOOK_NODE_TYPES = ['n8n-nodes-base.webhook', 'n8n-nodes-base.formTrigger']; + const WEBHOOK_NODE_TYPES = [ + 'n8n-nodes-base.webhook', + 'n8n-nodes-base.formTrigger', + '@n8n/n8n-nodes-langchain.chatTrigger', + ]; try { if (nodeVersion >= 1.1) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5980c8e23a522..b051fc2714b45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -824,6 +824,9 @@ importers: samlify: specifier: 2.8.9 version: 2.8.9 + sanitize-html: + specifier: 2.12.1 + version: 2.12.1 semver: specifier: ^7.5.4 version: 7.6.0 @@ -22429,7 +22432,7 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 domutils: 3.0.1 - entities: 4.4.0 + entities: 4.5.0 http-cache-semantics@4.1.1: optional: true