diff --git a/packages/core/src/InstanceSettings.ts b/packages/core/src/InstanceSettings.ts index d8ba2e616c0d0..9332212edae9f 100644 --- a/packages/core/src/InstanceSettings.ts +++ b/packages/core/src/InstanceSettings.ts @@ -6,7 +6,6 @@ import { jsonParse } from 'n8n-workflow'; interface ReadOnlySettings { encryptionKey: string; - instanceId: string; } interface WritableSettings { @@ -32,14 +31,12 @@ export class InstanceSettings { private settings = this.loadOrCreate(); + readonly instanceId = this.generateInstanceId(); + get encryptionKey() { return this.settings.encryptionKey; } - get instanceId() { - return this.settings.instanceId; - } - get tunnelSubdomain() { return this.settings.tunnelSubdomain; } @@ -58,25 +55,31 @@ export class InstanceSettings { } private loadOrCreate(): Settings { + let settings: Settings; const { settingsFile } = this; if (existsSync(settingsFile)) { const content = readFileSync(settingsFile, 'utf8'); - return jsonParse(content, { + settings = jsonParse(content, { errorMessage: `Error parsing n8n-config file "${settingsFile}". It does not seem to be valid JSON.`, }); + } else { + // If file doesn't exist, create new settings + const encryptionKey = process.env.N8N_ENCRYPTION_KEY ?? randomBytes(24).toString('base64'); + settings = { encryptionKey }; + mkdirSync(path.dirname(settingsFile)); + this.save(settings); + // console.info(`UserSettings were generated and saved to: ${settingsFile}`); } - // If file doesn't exist, create new settings - const encryptionKey = process.env.N8N_ENCRYPTION_KEY ?? randomBytes(24).toString('base64'); - const instanceId = createHash('sha256') + const { encryptionKey, tunnelSubdomain } = settings; + return { encryptionKey, tunnelSubdomain }; + } + + private generateInstanceId() { + const { encryptionKey } = this; + return createHash('sha256') .update(encryptionKey.slice(Math.round(encryptionKey.length / 2))) .digest('hex'); - - const settings = { encryptionKey, instanceId }; - mkdirSync(path.dirname(settingsFile)); - this.save(settings); - console.log(`UserSettings were generated and saved to: ${settingsFile}`); - return settings; } private save(settings: Settings) { diff --git a/packages/core/test/InstanceSettings.test.ts b/packages/core/test/InstanceSettings.test.ts new file mode 100644 index 0000000000000..6d6e46ab9f883 --- /dev/null +++ b/packages/core/test/InstanceSettings.test.ts @@ -0,0 +1,61 @@ +import fs from 'fs'; +import { InstanceSettings } from '@/InstanceSettings'; + +describe('InstanceSettings', () => { + process.env.N8N_USER_FOLDER = '/test'; + + describe('If the settings file exists', () => { + beforeEach(() => { + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + }); + + it('should load settings from the file', () => { + jest.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({ encryptionKey: 'test_key' })); + const settings = new InstanceSettings(); + expect(settings.encryptionKey).toEqual('test_key'); + expect(settings.instanceId).toEqual( + '6ce26c63596f0cc4323563c529acfca0cccb0e57f6533d79a60a42c9ff862ae7', + ); + }); + + it('should throw error if settings file is not valid JSON', () => { + jest.spyOn(fs, 'readFileSync').mockReturnValue('{"encryptionKey":"test_key"'); + expect(() => new InstanceSettings()).toThrowError(); + }); + }); + + describe('If the settings file does not exist', () => { + it('should create a new settings file', () => { + jest.spyOn(fs, 'existsSync').mockReturnValue(false); + const mkdirSpy = jest.spyOn(fs, 'mkdirSync').mockReturnValue(''); + const writeFileSpy = jest.spyOn(fs, 'writeFileSync').mockReturnValue(); + const settings = new InstanceSettings(); + expect(settings.encryptionKey).not.toEqual('test_key'); + expect(mkdirSpy).toHaveBeenCalledWith('/test/.n8n'); + expect(writeFileSpy).toHaveBeenCalledWith( + '/test/.n8n/config', + expect.stringContaining('"encryptionKey":'), + 'utf-8', + ); + }); + + it('should pick up the encryption key from env var N8N_ENCRYPTION_KEY', () => { + process.env.N8N_ENCRYPTION_KEY = 'env_key'; + jest.spyOn(fs, 'existsSync').mockReturnValue(false); + const mkdirSpy = jest.spyOn(fs, 'mkdirSync').mockReturnValue(''); + const writeFileSpy = jest.spyOn(fs, 'writeFileSync').mockReturnValue(); + const settings = new InstanceSettings(); + expect(settings.encryptionKey).toEqual('env_key'); + expect(settings.instanceId).toEqual( + '2c70e12b7a0646f92279f427c7b38e7334d8e5389cff167a1dc30e73f826b683', + ); + expect(settings.encryptionKey).not.toEqual('test_key'); + expect(mkdirSpy).toHaveBeenCalledWith('/test/.n8n'); + expect(writeFileSpy).toHaveBeenCalledWith( + '/test/.n8n/config', + expect.stringContaining('"encryptionKey":'), + 'utf-8', + ); + }); + }); +});