diff --git a/packages/cli/src/commands/worker.ts b/packages/cli/src/commands/worker.ts index d1fe4e935c..2988108855 100644 --- a/packages/cli/src/commands/worker.ts +++ b/packages/cli/src/commands/worker.ts @@ -259,6 +259,13 @@ export class Worker extends BaseCommand { constructor(argv: string[], cmdConfig: IConfig) { super(argv, cmdConfig); + + if (!process.env.N8N_ENCRYPTION_KEY) { + throw new ApplicationError( + 'Missing encryption key. Worker started without the required N8N_ENCRYPTION_KEY env var. More information: https://docs.n8n.io/hosting/environment-variables/configuration-methods/#encryption-key', + ); + } + this.setInstanceType('worker'); this.setInstanceQueueModeId(); } diff --git a/packages/cli/src/config/index.ts b/packages/cli/src/config/index.ts index 44d341afcc..59962ff774 100644 --- a/packages/cli/src/config/index.ts +++ b/packages/cli/src/config/index.ts @@ -14,7 +14,7 @@ if (inE2ETests) { process.env.N8N_AI_ENABLED = 'true'; } else if (inTest) { process.env.N8N_LOG_LEVEL = 'silent'; - process.env.N8N_ENCRYPTION_KEY = 'test-encryption-key'; + process.env.N8N_ENCRYPTION_KEY = 'test_key'; process.env.N8N_PUBLIC_API_DISABLED = 'true'; process.env.SKIP_STATISTICS_EVENTS = 'true'; } else { diff --git a/packages/cli/test/setup-test-folder.ts b/packages/cli/test/setup-test-folder.ts index 07a8095373..3709ed250b 100644 --- a/packages/cli/test/setup-test-folder.ts +++ b/packages/cli/test/setup-test-folder.ts @@ -11,6 +11,6 @@ process.env.N8N_USER_FOLDER = testDir; writeFileSync( join(testDir, '.n8n/config'), - JSON.stringify({ encryptionKey: 'testkey', instanceId: '123' }), + JSON.stringify({ encryptionKey: 'test_key', instanceId: '123' }), 'utf-8', ); diff --git a/packages/core/src/Credentials.ts b/packages/core/src/Credentials.ts index 9bf58284df..7714df6898 100644 --- a/packages/core/src/Credentials.ts +++ b/packages/core/src/Credentials.ts @@ -41,9 +41,9 @@ export class Credentials extends ICredentials { throw new ApplicationError('No data is set so nothing can be returned.'); } - const decryptedData = this.cipher.decrypt(this.data); - try { + const decryptedData = this.cipher.decrypt(this.data); + return jsonParse(decryptedData); } catch (e) { throw new ApplicationError( diff --git a/packages/core/src/InstanceSettings.ts b/packages/core/src/InstanceSettings.ts index 4731ad91f2..e8ab9aa553 100644 --- a/packages/core/src/InstanceSettings.ts +++ b/packages/core/src/InstanceSettings.ts @@ -2,7 +2,7 @@ import path from 'path'; import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; import { createHash, randomBytes } from 'crypto'; import { Service } from 'typedi'; -import { jsonParse } from 'n8n-workflow'; +import { ApplicationError, jsonParse } from 'n8n-workflow'; interface ReadOnlySettings { encryptionKey: string; @@ -14,6 +14,8 @@ interface WritableSettings { type Settings = ReadOnlySettings & WritableSettings; +const inTest = process.env.NODE_ENV === 'test'; + @Service() export class InstanceSettings { private readonly userHome = this.getUserHome(); @@ -57,26 +59,44 @@ export class InstanceSettings { return process.env.N8N_USER_FOLDER ?? process.env[homeVarName] ?? process.cwd(); } + /** + * Load instance settings from the settings file. If missing, create a new + * settings file with an auto-generated encryption key. + */ private loadOrCreate(): Settings { - let settings: Settings; - const { settingsFile } = this; - if (existsSync(settingsFile)) { - const content = readFileSync(settingsFile, 'utf8'); - settings = jsonParse(content, { - errorMessage: `Error parsing n8n-config file "${settingsFile}". It does not seem to be valid JSON.`, + if (existsSync(this.settingsFile)) { + const content = readFileSync(this.settingsFile, 'utf8'); + + const settings = jsonParse(content, { + errorMessage: `Error parsing n8n-config file "${this.settingsFile}". It does not seem to be valid JSON.`, }); - } else { - // Ensure that the `.n8n` folder exists - mkdirSync(this.n8nFolder, { recursive: true }); - // If file doesn't exist, create new settings - const encryptionKey = process.env.N8N_ENCRYPTION_KEY ?? randomBytes(24).toString('base64'); - settings = { encryptionKey }; - this.save(settings); - // console.info(`UserSettings were generated and saved to: ${settingsFile}`); + + if (!inTest) console.info(`User settings loaded from: ${this.settingsFile}`); + + const { encryptionKey, tunnelSubdomain } = settings; + + if (process.env.N8N_ENCRYPTION_KEY && encryptionKey !== process.env.N8N_ENCRYPTION_KEY) { + throw new ApplicationError( + `Mismatching encryption keys. The encryption key in the settings file ${this.settingsFile} does not match the N8N_ENCRYPTION_KEY env var. Please make sure both keys match. More information: https://docs.n8n.io/hosting/environment-variables/configuration-methods/#encryption-key`, + ); + } + + return { encryptionKey, tunnelSubdomain }; } - const { encryptionKey, tunnelSubdomain } = settings; - return { encryptionKey, tunnelSubdomain }; + mkdirSync(this.n8nFolder, { recursive: true }); + + const encryptionKey = process.env.N8N_ENCRYPTION_KEY ?? randomBytes(24).toString('base64'); + + const settings: Settings = { encryptionKey }; + + this.save(settings); + + if (!inTest && !process.env.N8N_ENCRYPTION_KEY) { + console.info(`No encryption key found - Auto-generated and saved to: ${this.settingsFile}`); + } + + return settings; } private generateInstanceId() { diff --git a/packages/core/test/InstanceSettings.test.ts b/packages/core/test/InstanceSettings.test.ts index 05899e9c89..414f875274 100644 --- a/packages/core/test/InstanceSettings.test.ts +++ b/packages/core/test/InstanceSettings.test.ts @@ -24,6 +24,12 @@ describe('InstanceSettings', () => { readSpy.mockReturnValue('{"encryptionKey":"test_key"'); expect(() => new InstanceSettings()).toThrowError(); }); + + it('should throw if the env and file keys do not match', () => { + readSpy.mockReturnValue(JSON.stringify({ encryptionKey: 'key_1' })); + process.env.N8N_ENCRYPTION_KEY = 'key_2'; + expect(() => new InstanceSettings()).toThrowError(); + }); }); describe('If the settings file does not exist', () => { diff --git a/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts b/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts index c6fabfa41e..38412b1d8a 100644 --- a/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts +++ b/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts @@ -1259,38 +1259,22 @@ export class HttpRequestV3 implements INodeType { genericCredentialType = this.getNodeParameter('genericAuthType', 0) as string; if (genericCredentialType === 'httpBasicAuth') { - try { - httpBasicAuth = await this.getCredentials('httpBasicAuth', itemIndex); - } catch {} + httpBasicAuth = await this.getCredentials('httpBasicAuth', itemIndex); } else if (genericCredentialType === 'httpDigestAuth') { - try { - httpDigestAuth = await this.getCredentials('httpDigestAuth', itemIndex); - } catch {} + httpDigestAuth = await this.getCredentials('httpDigestAuth', itemIndex); } else if (genericCredentialType === 'httpHeaderAuth') { - try { - httpHeaderAuth = await this.getCredentials('httpHeaderAuth', itemIndex); - } catch {} + httpHeaderAuth = await this.getCredentials('httpHeaderAuth', itemIndex); } else if (genericCredentialType === 'httpQueryAuth') { - try { - httpQueryAuth = await this.getCredentials('httpQueryAuth', itemIndex); - } catch {} + httpQueryAuth = await this.getCredentials('httpQueryAuth', itemIndex); } else if (genericCredentialType === 'httpCustomAuth') { - try { - httpCustomAuth = await this.getCredentials('httpCustomAuth', itemIndex); - } catch {} + httpCustomAuth = await this.getCredentials('httpCustomAuth', itemIndex); } else if (genericCredentialType === 'oAuth1Api') { - try { - oAuth1Api = await this.getCredentials('oAuth1Api', itemIndex); - } catch {} + oAuth1Api = await this.getCredentials('oAuth1Api', itemIndex); } else if (genericCredentialType === 'oAuth2Api') { - try { - oAuth2Api = await this.getCredentials('oAuth2Api', itemIndex); - } catch {} + oAuth2Api = await this.getCredentials('oAuth2Api', itemIndex); } } else if (authentication === 'predefinedCredentialType') { - try { - nodeCredentialType = this.getNodeParameter('nodeCredentialType', 0) as string; - } catch {} + nodeCredentialType = this.getNodeParameter('nodeCredentialType', 0) as string; } const requestMethod = this.getNodeParameter('method', itemIndex) as string;