diff --git a/CHANGELOG.md b/CHANGELOG.md index ddaf945357ace..c80391b320036 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## [1.14.1](https://github.com/n8n-io/n8n/compare/n8n@1.14.0...n8n@1.14.1) (2023-10-26) + + +### Bug Fixes + +* **core:** Ensure execution deletion in worker lifecycle hook ([#7481](https://github.com/n8n-io/n8n/issues/7481)) ([b81e5b9](https://github.com/n8n-io/n8n/commit/b81e5b92ddb96e952b2586b06d9e89c0767b9e58)) +* **core:** Upgrade crypto-js to address CVE-2023-46233 ([#7519](https://github.com/n8n-io/n8n/issues/7519)) ([0c3ee47](https://github.com/n8n-io/n8n/commit/0c3ee47787f1f9c4858fa10b6923c9a6ca54ac8e)) +* **editor:** Fixes the issue that Switch Node can not be created ([#7516](https://github.com/n8n-io/n8n/issues/7516)) ([b64146b](https://github.com/n8n-io/n8n/commit/b64146b3405436d728bba5e7b1c73fcd20a466c2)) + + + # [1.14.0](https://github.com/n8n-io/n8n/compare/n8n@1.13.0...n8n@1.14.0) (2023-10-25) diff --git a/cypress/e2e/12-canvas.cy.ts b/cypress/e2e/12-canvas.cy.ts index 9e2b8abe06c48..94900b82065ad 100644 --- a/cypress/e2e/12-canvas.cy.ts +++ b/cypress/e2e/12-canvas.cy.ts @@ -30,6 +30,7 @@ describe('Canvas Node Manipulation and Navigation', () => { it('should add switch node and test connections', () => { const desiredOutputs = 4; + WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true); WorkflowPage.actions.addNodeToCanvas(SWITCH_NODE_NAME, true, true); for (let i = 0; i < desiredOutputs; i++) { @@ -43,9 +44,14 @@ describe('Canvas Node Manipulation and Navigation', () => { WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, false); WorkflowPage.actions.zoomToFit(); } + WorkflowPage.getters.nodeViewBackground().click({ force: true }); + WorkflowPage.getters.canvasNodePlusEndpointByName(`${EDIT_FIELDS_SET_NODE_NAME}3`).click(); + WorkflowPage.actions.addNodeToCanvas(SWITCH_NODE_NAME, false); WorkflowPage.actions.saveWorkflowOnButtonClick(); cy.reload(); cy.waitForLoad(); + // Make sure outputless switch was connected correctly + cy.get(`[data-target-node="${SWITCH_NODE_NAME}1"][data-source-node="${EDIT_FIELDS_SET_NODE_NAME}3"]`).should('be.visible'); // Make sure all connections are there after reload for (let i = 0; i < desiredOutputs; i++) { const setName = `${EDIT_FIELDS_SET_NODE_NAME}${i > 0 ? i : ''}`; diff --git a/package.json b/package.json index 2ce4e34681dd2..6f2cab2efa996 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "1.14.0", + "version": "1.14.1", "private": true, "homepage": "https://n8n.io/", "engines": { diff --git a/packages/cli/package.json b/packages/cli/package.json index d68f5c3798260..c8673f9fa7040 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "1.14.0", + "version": "1.14.1", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io/", diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index edbc93561c507..cf0896c8a5ab9 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -1122,6 +1122,36 @@ export function getWorkflowHooksWorkerMain( // So to avoid confusion, we are removing other hooks. hookFunctions.nodeExecuteBefore = []; hookFunctions.nodeExecuteAfter = []; + hookFunctions.workflowExecuteAfter = [ + async function ( + this: WorkflowHooks, + fullRunData: IRun, + newStaticData: IDataObject, + ): Promise { + // Check config to know if execution should be saved or not + let saveDataErrorExecution = config.getEnv('executions.saveDataOnError') as string; + let saveDataSuccessExecution = config.getEnv('executions.saveDataOnSuccess') as string; + if (this.workflowData.settings !== undefined) { + saveDataErrorExecution = + (this.workflowData.settings.saveDataErrorExecution as string) || saveDataErrorExecution; + saveDataSuccessExecution = + (this.workflowData.settings.saveDataSuccessExecution as string) || + saveDataSuccessExecution; + } + + const workflowStatusFinal = determineFinalExecutionStatus(fullRunData); + + if ( + (workflowStatusFinal === 'success' && saveDataSuccessExecution === 'none') || + (workflowStatusFinal !== 'success' && saveDataErrorExecution === 'none') + ) { + await Container.get(ExecutionRepository).hardDelete({ + workflowId: this.workflowData.id as string, + executionId: this.executionId, + }); + } + }, + ]; return new WorkflowHooks(hookFunctions, mode, executionId, workflowData, optionalParameters); } diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js index a066fe093cf0f..b23dc2442639a 100644 --- a/packages/core/jest.config.js +++ b/packages/core/jest.config.js @@ -2,4 +2,5 @@ module.exports = { ...require('../../jest.config'), globalSetup: '/test/setup.ts', + setupFilesAfterEnv: ['/test/setup-mocks.ts'], }; diff --git a/packages/core/package.json b/packages/core/package.json index deaf1281b38f2..fdafa6d8eadc9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "1.14.0", + "version": "1.14.1", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io/", @@ -54,7 +54,7 @@ "axios": "^0.21.1", "concat-stream": "^2.0.0", "cron": "~1.7.2", - "crypto-js": "^4.1.1", + "crypto-js": "^4.2.0", "fast-glob": "^3.2.5", "file-type": "^16.5.4", "flatted": "^3.2.4", diff --git a/packages/core/src/Cipher.ts b/packages/core/src/Cipher.ts index 08af32a26ced7..88e25e00aba5d 100644 --- a/packages/core/src/Cipher.ts +++ b/packages/core/src/Cipher.ts @@ -7,13 +7,15 @@ export class Cipher { constructor(private readonly instanceSettings: InstanceSettings) {} encrypt(data: string | object) { + const { encryptionKey } = this.instanceSettings; return AES.encrypt( typeof data === 'string' ? data : JSON.stringify(data), - this.instanceSettings.encryptionKey, + encryptionKey, ).toString(); } decrypt(data: string) { - return AES.decrypt(data, this.instanceSettings.encryptionKey).toString(enc.Utf8); + const { encryptionKey } = this.instanceSettings; + return AES.decrypt(data, encryptionKey).toString(enc.Utf8); } } diff --git a/packages/core/test/Cipher.test.ts b/packages/core/test/Cipher.test.ts new file mode 100644 index 0000000000000..23e0bf4ab42c9 --- /dev/null +++ b/packages/core/test/Cipher.test.ts @@ -0,0 +1,30 @@ +import Container from 'typedi'; +import { InstanceSettings } from '@/InstanceSettings'; +import { Cipher } from '@/Cipher'; +import { mockInstance } from './utils'; + +describe('Cipher', () => { + mockInstance(InstanceSettings, { encryptionKey: 'test_key' }); + const cipher = Container.get(Cipher); + + describe('encrypt', () => { + it('should encrypt strings', () => { + const encrypted = cipher.encrypt('random-string'); + const decrypted = cipher.decrypt(encrypted); + expect(decrypted).toEqual('random-string'); + }); + + it('should encrypt objects', () => { + const encrypted = cipher.encrypt({ key: 'value' }); + const decrypted = cipher.decrypt(encrypted); + expect(decrypted).toEqual('{"key":"value"}'); + }); + }); + + describe('decrypt', () => { + it('should decrypt string', () => { + const decrypted = cipher.decrypt('U2FsdGVkX194VEoX27o3+y5jUd1JTTmVwkOKjVhB6Jg='); + expect(decrypted).toEqual('random-string'); + }); + }); +}); diff --git a/packages/core/test/setup-mocks.ts b/packages/core/test/setup-mocks.ts new file mode 100644 index 0000000000000..d2c9bc6e645e1 --- /dev/null +++ b/packages/core/test/setup-mocks.ts @@ -0,0 +1 @@ +import 'reflect-metadata'; diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index e00204c67bf44..e44710fb4a096 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "1.14.0", + "version": "1.14.1", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io/", diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 76fac04a15772..0d9459f3abc33 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -265,6 +265,7 @@ import type { IWorkflowBase, Workflow, ConnectionTypes, + INodeOutputConfiguration, } from 'n8n-workflow'; import { deepCopy, @@ -1970,29 +1971,47 @@ export default defineComponent({ this.canvasStore.newNodeInsertPosition = null; } else { let yOffset = 0; + const workflow = this.getCurrentWorkflow(); if (lastSelectedConnection) { const sourceNodeType = this.nodeTypesStore.getNodeType( lastSelectedNode.type, lastSelectedNode.typeVersion, ); - const offsets = [ - [-100, 100], - [-140, 0, 140], - [-240, -100, 100, 240], - ]; - if (sourceNodeType && sourceNodeType.outputs.length > 1) { - const offset = offsets[sourceNodeType.outputs.length - 2]; - const sourceOutputIndex = lastSelectedConnection.__meta - ? lastSelectedConnection.__meta.sourceOutputIndex - : 0; - yOffset = offset[sourceOutputIndex]; + if (sourceNodeType) { + const offsets = [ + [-100, 100], + [-140, 0, 140], + [-240, -100, 100, 240], + ]; + const sourceNodeOutputs = NodeHelpers.getNodeOutputs( + workflow, + lastSelectedNode!, + sourceNodeType, + ); + const sourceNodeOutputTypes = NodeHelpers.getConnectionTypes(sourceNodeOutputs); + const sourceNodeOutputMainOutputs = sourceNodeOutputTypes.filter( + (output) => output === NodeConnectionType.Main, + ); + if (sourceNodeOutputMainOutputs.length > 1) { + const offset = offsets[sourceNodeOutputMainOutputs.length - 2]; + const sourceOutputIndex = lastSelectedConnection.__meta + ? lastSelectedConnection.__meta.sourceOutputIndex + : 0; + yOffset = offset[sourceOutputIndex]; + } } } - const workflow = this.getCurrentWorkflow(); - const workflowNode = workflow.getNode(newNodeData.name); - const outputs = NodeHelpers.getNodeOutputs(workflow, workflowNode!, nodeTypeData); + let outputs: Array = []; + try { + // It fails when the outputs are an expression. As those node have + // normally no outputs by default and the only reason we need the + // outputs here is to calculate the position it is fine to assume + // that they have no outputs and are so treated as a regular node + // with only "main" outputs. + outputs = NodeHelpers.getNodeOutputs(workflow, newNodeData!, nodeTypeData); + } catch (e) {} const outputTypes = NodeHelpers.getConnectionTypes(outputs); const lastSelectedNodeType = this.nodeTypesStore.getNodeType( lastSelectedNode.type, @@ -2000,7 +2019,10 @@ export default defineComponent({ ); // If node has only scoped outputs, position it below the last selected node - if (outputTypes.every((outputName) => outputName !== NodeConnectionType.Main)) { + if ( + outputTypes.length > 0 && + outputTypes.every((outputName) => outputName !== NodeConnectionType.Main) + ) { const lastSelectedNodeWorkflow = workflow.getNode(lastSelectedNode.name); const lastSelectedInputs = NodeHelpers.getNodeInputs( workflow, @@ -2025,6 +2047,7 @@ export default defineComponent({ [100, 0], ); } else { + // Has only main outputs or no outputs at all const inputs = NodeHelpers.getNodeInputs( workflow, lastSelectedNode, diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index 3970c78a2cd17..9821a2489210b 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -1,6 +1,6 @@ { "name": "n8n-node-dev", - "version": "1.14.0", + "version": "1.14.1", "description": "CLI to simplify n8n credentials/node development", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io/", diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index bc8ce8528f951..389a17a2ba763 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "1.14.0", + "version": "1.14.1", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io/", diff --git a/packages/workflow/package.json b/packages/workflow/package.json index 3c3ada756aa76..885f72c69af8e 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,6 +1,6 @@ { "name": "n8n-workflow", - "version": "1.14.0", + "version": "1.14.1", "description": "Workflow base code of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io/", @@ -51,7 +51,7 @@ "@n8n/tournament": "^1.0.2", "@n8n_io/riot-tmpl": "^4.0.0", "ast-types": "0.15.2", - "crypto-js": "^4.1.1", + "crypto-js": "^4.2.0", "deep-equal": "^2.2.0", "esprima-next": "5.8.4", "form-data": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 342f6c71df515..d844ae60a9032 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -583,8 +583,8 @@ importers: specifier: ~1.7.2 version: 1.7.2 crypto-js: - specifier: ^4.1.1 - version: 4.1.1 + specifier: ^4.2.0 + version: 4.2.0 fast-glob: specifier: ^3.2.5 version: 3.2.12 @@ -1311,8 +1311,8 @@ importers: specifier: 0.15.2 version: 0.15.2 crypto-js: - specifier: ^4.1.1 - version: 4.1.1 + specifier: ^4.2.0 + version: 4.2.0 deep-equal: specifier: ^2.2.0 version: 2.2.0 @@ -6824,7 +6824,7 @@ packages: ts-dedent: 2.2.0 type-fest: 3.13.1 vue: 3.3.4 - vue-component-type-helpers: 1.8.19 + vue-component-type-helpers: 1.8.21 transitivePeerDependencies: - encoding - supports-color @@ -10544,6 +10544,10 @@ packages: resolution: {integrity: sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==} dev: false + /crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + dev: false + /crypto-random-string@2.0.0: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} @@ -21799,8 +21803,8 @@ packages: vue: 3.3.4 dev: false - /vue-component-type-helpers@1.8.19: - resolution: {integrity: sha512-1OANGSZK4pzHF4uc86usWi+o5Y0zgoDtqWkPg6Am6ot+jHSAmpOah59V/4N82So5xRgivgCxGgK09lBy1XNUfQ==} + /vue-component-type-helpers@1.8.21: + resolution: {integrity: sha512-XL37QbmiqqbKrAFHPxqryMXpNgO0KMKd5bIo7LO9QABPMNEysd8xmYRIjwZhh0t2abveXjAJ//ZcAzwdxp/S3Q==} dev: true /vue-component-type-helpers@1.8.4: