mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
refactor(core): Refactor some of the external secrets related code (no-changelog) (#14791)
This commit is contained in:
committed by
GitHub
parent
e83a64b84a
commit
749f130d4f
@@ -10,7 +10,6 @@ import { EndpointsConfig } from './configs/endpoints.config';
|
||||
import { EventBusConfig } from './configs/event-bus.config';
|
||||
import { ExecutionsConfig } from './configs/executions.config';
|
||||
import { ExternalHooksConfig } from './configs/external-hooks.config';
|
||||
import { ExternalSecretsConfig } from './configs/external-secrets.config';
|
||||
import { ExternalStorageConfig } from './configs/external-storage.config';
|
||||
import { GenericConfig } from './configs/generic.config';
|
||||
import { LicenseConfig } from './configs/license.config';
|
||||
@@ -68,9 +67,6 @@ export class GlobalConfig {
|
||||
@Nested
|
||||
externalHooks: ExternalHooksConfig;
|
||||
|
||||
@Nested
|
||||
externalSecrets: ExternalSecretsConfig;
|
||||
|
||||
@Nested
|
||||
templates: TemplatesConfig;
|
||||
|
||||
|
||||
@@ -115,10 +115,6 @@ describe('GlobalConfig', () => {
|
||||
externalHooks: {
|
||||
files: [],
|
||||
},
|
||||
externalSecrets: {
|
||||
preferGet: false,
|
||||
updateInterval: 300,
|
||||
},
|
||||
nodes: {
|
||||
communityPackages: {
|
||||
enabled: true,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { UnexpectedError } from 'n8n-workflow';
|
||||
|
||||
export class ExternalSecretsProviderNotFoundError extends UnexpectedError {
|
||||
constructor(public providerName: string) {
|
||||
super(`External secrets provider not found: ${providerName}`);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { CredentialsHelper } from '@/credentials-helper';
|
||||
import * as SecretsHelpers from '@/external-secrets.ee/external-secrets-helper.ee';
|
||||
import { SecretsHelper } from '@/secrets-helpers.ee';
|
||||
|
||||
import { MessageEventBusDestination } from './message-event-bus-destination.ee';
|
||||
import { eventMessageGenericDestinationTestEvent } from '../event-message-classes/event-message-generic';
|
||||
@@ -102,7 +102,9 @@ export class MessageEventBusDestinationWebhook
|
||||
const foundCredential = Object.entries(this.credentials).find((e) => e[0] === credentialType);
|
||||
if (foundCredential) {
|
||||
const credentialsDecrypted = await this.credentialsHelper?.getDecrypted(
|
||||
{ secretsHelpers: SecretsHelpers } as unknown as IWorkflowExecuteAdditionalData,
|
||||
{
|
||||
secretsHelpers: Container.get(SecretsHelper),
|
||||
} as unknown as IWorkflowExecuteAdditionalData,
|
||||
foundCredential[1],
|
||||
foundCredential[0],
|
||||
'internal',
|
||||
|
||||
@@ -1,58 +1,59 @@
|
||||
import { Container } from '@n8n/di';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { Cipher } from 'n8n-core';
|
||||
|
||||
import { SettingsRepository } from '@/databases/repositories/settings.repository';
|
||||
import { ExternalSecretsManager } from '@/external-secrets.ee/external-secrets-manager.ee';
|
||||
import { ExternalSecretsProviders } from '@/external-secrets.ee/external-secrets-providers.ee';
|
||||
import type { ExternalSecretsSettings } from '@/interfaces';
|
||||
import { License } from '@/license';
|
||||
import type { SettingsRepository } from '@/databases/repositories/settings.repository';
|
||||
import type { License } from '@/license';
|
||||
import {
|
||||
AnotherDummyProvider,
|
||||
DummyProvider,
|
||||
ErrorProvider,
|
||||
FailedProvider,
|
||||
MockProviders,
|
||||
} from '@test/external-secrets/utils';
|
||||
import { mockInstance, mockLogger } from '@test/mocking';
|
||||
import { mockCipher, mockLogger } from '@test/mocking';
|
||||
|
||||
import { ExternalSecretsManager } from '../external-secrets-manager.ee';
|
||||
import type { ExternalSecretsSettings } from '../types';
|
||||
|
||||
describe('External Secrets Manager', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const connectedDate = '2023-08-01T12:32:29.000Z';
|
||||
let settings: string | null = null;
|
||||
const providerSettings = () => ({
|
||||
connected: true,
|
||||
connectedAt: new Date(connectedDate),
|
||||
settings: {},
|
||||
});
|
||||
|
||||
const settings: ExternalSecretsSettings = {
|
||||
dummy: providerSettings(),
|
||||
another_dummy: providerSettings(),
|
||||
failed: providerSettings(),
|
||||
};
|
||||
|
||||
const mockProvidersInstance = new MockProviders();
|
||||
const license = mockInstance(License);
|
||||
const settingsRepo = mockInstance(SettingsRepository);
|
||||
const cipher = Container.get(Cipher);
|
||||
const license = mock<License>();
|
||||
const settingsRepo = mock<SettingsRepository>();
|
||||
const cipher = mockCipher();
|
||||
|
||||
let providersMock: ExternalSecretsProviders;
|
||||
let manager: ExternalSecretsManager;
|
||||
|
||||
const createMockSettings = (settings: ExternalSecretsSettings): string => {
|
||||
return cipher.encrypt(settings);
|
||||
};
|
||||
|
||||
const decryptSettings = (settings: string) => {
|
||||
return JSON.parse(cipher.decrypt(settings));
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
providersMock = mockInstance(ExternalSecretsProviders, mockProvidersInstance);
|
||||
settings = createMockSettings({
|
||||
dummy: { connected: true, connectedAt: new Date(connectedDate), settings: {} },
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
settings.dummy.connected = true;
|
||||
mockProvidersInstance.setProviders({
|
||||
dummy: DummyProvider,
|
||||
});
|
||||
|
||||
license.isExternalSecretsEnabled.mockReturnValue(true);
|
||||
settingsRepo.getEncryptedSecretsProviderSettings.mockResolvedValue(settings);
|
||||
settingsRepo.getEncryptedSecretsProviderSettings.mockImplementation(async () =>
|
||||
JSON.stringify(settings),
|
||||
);
|
||||
|
||||
manager = new ExternalSecretsManager(
|
||||
mockLogger(),
|
||||
mock(),
|
||||
settingsRepo,
|
||||
license,
|
||||
providersMock,
|
||||
mockProvidersInstance,
|
||||
cipher,
|
||||
mock(),
|
||||
mock(),
|
||||
@@ -61,15 +62,9 @@ describe('External Secrets Manager', () => {
|
||||
|
||||
afterEach(() => {
|
||||
manager?.shutdown();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('should get secret', async () => {
|
||||
await manager.init();
|
||||
|
||||
expect(manager.getSecret('dummy', 'test1')).toBe('value1');
|
||||
});
|
||||
|
||||
describe('init / shutdown', () => {
|
||||
test('should not throw errors during init', async () => {
|
||||
mockProvidersInstance.setProviders({
|
||||
dummy: ErrorProvider,
|
||||
@@ -86,28 +81,7 @@ describe('External Secrets Manager', () => {
|
||||
expect(() => manager!.shutdown()).not.toThrow();
|
||||
});
|
||||
|
||||
test('should save provider settings', async () => {
|
||||
const settingsSpy = jest.spyOn(settingsRepo, 'saveEncryptedSecretsProviderSettings');
|
||||
|
||||
await manager.init();
|
||||
|
||||
await manager.setProviderSettings('dummy', {
|
||||
test: 'value',
|
||||
});
|
||||
|
||||
expect(decryptSettings(settingsSpy.mock.calls[0][0])).toEqual({
|
||||
dummy: {
|
||||
connected: true,
|
||||
connectedAt: connectedDate,
|
||||
settings: {
|
||||
test: 'value',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should call provider update functions on a timer', async () => {
|
||||
jest.useFakeTimers();
|
||||
await manager.init();
|
||||
|
||||
const updateSpy = jest.spyOn(manager.getProvider('dummy')!, 'update');
|
||||
@@ -120,8 +94,6 @@ describe('External Secrets Manager', () => {
|
||||
});
|
||||
|
||||
test('should not call provider update functions if the not licensed', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
license.isExternalSecretsEnabled.mockReturnValue(false);
|
||||
|
||||
await manager.init();
|
||||
@@ -136,8 +108,6 @@ describe('External Secrets Manager', () => {
|
||||
});
|
||||
|
||||
test('should not call provider update functions if the provider has an error', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
mockProvidersInstance.setProviders({
|
||||
dummy: FailedProvider,
|
||||
});
|
||||
@@ -164,4 +134,241 @@ describe('External Secrets Manager', () => {
|
||||
|
||||
expect(dummyInitSpy).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasProvider', () => {
|
||||
test('should check if provider exists', async () => {
|
||||
await manager.init();
|
||||
|
||||
expect(manager.hasProvider('dummy')).toBe(true);
|
||||
expect(manager.hasProvider('nonexistent')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProviderNames', () => {
|
||||
test('should get provider names', async () => {
|
||||
await manager.init();
|
||||
|
||||
expect(manager.getProviderNames()).toEqual(['dummy']);
|
||||
|
||||
// @ts-expect-error private property
|
||||
manager.providers = {};
|
||||
expect(manager.getProviderNames()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateProvider', () => {
|
||||
test('should update a specific provider and return true on success', async () => {
|
||||
await manager.init();
|
||||
|
||||
const result = await manager.updateProvider('dummy');
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test('should return false if provider is not connected', async () => {
|
||||
mockProvidersInstance.setProviders({
|
||||
dummy: ErrorProvider,
|
||||
});
|
||||
|
||||
await manager.init();
|
||||
|
||||
const result = await manager.updateProvider('dummy');
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test('should return false if external secrets are not licensed', async () => {
|
||||
license.isExternalSecretsEnabled.mockReturnValue(false);
|
||||
|
||||
await manager.init();
|
||||
|
||||
const result = await manager.updateProvider('dummy');
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reloadAllProviders', () => {
|
||||
test('should reload all providers', async () => {
|
||||
await manager.init();
|
||||
|
||||
const reloadSpy = jest.spyOn(manager, 'reloadProvider');
|
||||
|
||||
await manager.reloadAllProviders();
|
||||
|
||||
expect(reloadSpy).toHaveBeenCalledWith('dummy', undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProviderWithSettings', () => {
|
||||
test('should get provider with settings', async () => {
|
||||
await manager.init();
|
||||
|
||||
const result = manager.getProviderWithSettings('dummy');
|
||||
|
||||
expect(result).toEqual({
|
||||
provider: expect.any(DummyProvider),
|
||||
settings: expect.objectContaining({
|
||||
connected: true,
|
||||
connectedAt: connectedDate,
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProvidersWithSettings', () => {
|
||||
test('should return all providers with their settings', async () => {
|
||||
mockProvidersInstance.setProviders({
|
||||
dummy: DummyProvider,
|
||||
another_dummy: DummyProvider,
|
||||
});
|
||||
|
||||
settings.dummy.settings = { key: 'value' };
|
||||
settings.another_dummy.settings = { key2: 'value2' };
|
||||
|
||||
await manager.init();
|
||||
|
||||
const result = manager.getProvidersWithSettings();
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toEqual({
|
||||
provider: expect.any(DummyProvider),
|
||||
settings: expect.objectContaining({
|
||||
connected: true,
|
||||
settings: { key: 'value' },
|
||||
}),
|
||||
});
|
||||
expect(result[1]).toEqual({
|
||||
provider: expect.any(DummyProvider),
|
||||
settings: expect.objectContaining({
|
||||
connected: true,
|
||||
settings: { key2: 'value2' },
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setProviderSettings', () => {
|
||||
test('should save provider settings', async () => {
|
||||
const settingsSpy = jest.spyOn(settingsRepo, 'saveEncryptedSecretsProviderSettings');
|
||||
|
||||
await manager.init();
|
||||
|
||||
await manager.setProviderSettings('dummy', {
|
||||
test: 'value',
|
||||
});
|
||||
|
||||
expect(JSON.parse(settingsSpy.mock.calls[0][0])).toEqual(
|
||||
expect.objectContaining({
|
||||
dummy: {
|
||||
connected: true,
|
||||
connectedAt: connectedDate,
|
||||
settings: {
|
||||
test: 'value',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('testProviderSettings', () => {
|
||||
test('should test provider settings successfully', async () => {
|
||||
await manager.init();
|
||||
|
||||
const result = await manager.testProviderSettings('dummy', {});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
testState: 'connected',
|
||||
});
|
||||
});
|
||||
|
||||
test('should return tested state for successful but not connected provider', async () => {
|
||||
settings.dummy.connected = false;
|
||||
|
||||
await manager.init();
|
||||
|
||||
const result = await manager.testProviderSettings('dummy', {});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
testState: 'tested',
|
||||
});
|
||||
});
|
||||
|
||||
test('should return error state if provider test fails', async () => {
|
||||
mockProvidersInstance.setProviders({
|
||||
error: ErrorProvider,
|
||||
});
|
||||
|
||||
await manager.init();
|
||||
|
||||
const result = await manager.testProviderSettings('error', {});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
testState: 'error',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasSecret', () => {
|
||||
test('should return true when secret exists', async () => {
|
||||
await manager.init();
|
||||
|
||||
expect(manager.hasSecret('dummy', 'test1')).toBe(true);
|
||||
});
|
||||
|
||||
test('should return false when secret does not exist', async () => {
|
||||
await manager.init();
|
||||
|
||||
expect(manager.hasSecret('dummy', 'nonexistent')).toBe(false);
|
||||
});
|
||||
|
||||
test('should return false when provider does not exist', async () => {
|
||||
await manager.init();
|
||||
|
||||
expect(manager.hasSecret('nonexistent', 'test1')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSecret', () => {
|
||||
test('should get secret', async () => {
|
||||
await manager.init();
|
||||
|
||||
expect(manager.getSecret('dummy', 'test1')).toBe('value1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSecretNames', () => {
|
||||
test('should return list of secret names for a provider', async () => {
|
||||
await manager.init();
|
||||
|
||||
expect(manager.getSecretNames('dummy')).toEqual(['test1', 'test2']);
|
||||
});
|
||||
|
||||
test('should return undefined when provider does not exist', async () => {
|
||||
await manager.init();
|
||||
|
||||
expect(manager.getSecretNames('nonexistent')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllSecretNames', () => {
|
||||
test('should return secret names for all providers', async () => {
|
||||
mockProvidersInstance.setProviders({
|
||||
dummy: DummyProvider,
|
||||
another_dummy: AnotherDummyProvider,
|
||||
});
|
||||
|
||||
await manager.init();
|
||||
|
||||
expect(manager.getAllSecretNames()).toEqual({
|
||||
dummy: ['test1', 'test2'],
|
||||
another_dummy: ['test1', 'test2'],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
import { License } from '@/license';
|
||||
|
||||
export const updateIntervalTime = () =>
|
||||
Container.get(GlobalConfig).externalSecrets.updateInterval * 1000;
|
||||
export const preferGet = () => Container.get(GlobalConfig).externalSecrets.preferGet;
|
||||
|
||||
export function isExternalSecretsEnabled() {
|
||||
const license = Container.get(License);
|
||||
return license.isExternalSecretsEnabled();
|
||||
}
|
||||
@@ -3,18 +3,15 @@ import { Cipher, Logger } from 'n8n-core';
|
||||
import { jsonParse, type IDataObject, ensureError, UnexpectedError } from 'n8n-workflow';
|
||||
|
||||
import { SettingsRepository } from '@/databases/repositories/settings.repository';
|
||||
import { OnShutdown } from '@/decorators/on-shutdown';
|
||||
import { EventService } from '@/events/event.service';
|
||||
import type {
|
||||
ExternalSecretsSettings,
|
||||
SecretsProvider,
|
||||
SecretsProviderSettings,
|
||||
} from '@/interfaces';
|
||||
import { License } from '@/license';
|
||||
import { Publisher } from '@/scaling/pubsub/publisher.service';
|
||||
|
||||
import { EXTERNAL_SECRETS_INITIAL_BACKOFF, EXTERNAL_SECRETS_MAX_BACKOFF } from './constants';
|
||||
import { updateIntervalTime } from './external-secrets-helper.ee';
|
||||
import { ExternalSecretsProviders } from './external-secrets-providers.ee';
|
||||
import { ExternalSecretsConfig } from './external-secrets.config';
|
||||
import type { ExternalSecretsSettings, SecretsProvider, SecretsProviderSettings } from './types';
|
||||
|
||||
@Service()
|
||||
export class ExternalSecretsManager {
|
||||
@@ -32,6 +29,7 @@ export class ExternalSecretsManager {
|
||||
|
||||
constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly config: ExternalSecretsConfig,
|
||||
private readonly settingsRepo: SettingsRepository,
|
||||
private readonly license: License,
|
||||
private readonly secretsProviders: ExternalSecretsProviders,
|
||||
@@ -52,16 +50,17 @@ export class ExternalSecretsManager {
|
||||
this.initializingPromise = undefined;
|
||||
this.updateInterval = setInterval(
|
||||
async () => await this.updateSecrets(),
|
||||
updateIntervalTime(),
|
||||
this.config.updateInterval * 1000,
|
||||
);
|
||||
});
|
||||
}
|
||||
return await this.initializingPromise;
|
||||
await this.initializingPromise;
|
||||
}
|
||||
|
||||
this.logger.debug('External secrets manager initialized');
|
||||
}
|
||||
|
||||
@OnShutdown()
|
||||
shutdown() {
|
||||
clearInterval(this.updateInterval);
|
||||
Object.values(this.providers).forEach((p) => {
|
||||
@@ -86,14 +85,19 @@ export class ExternalSecretsManager {
|
||||
this.logger.debug('External secrets managed reloaded all providers');
|
||||
}
|
||||
|
||||
broadcastReloadExternalSecretsProviders() {
|
||||
private broadcastReloadExternalSecretsProviders() {
|
||||
void this.publisher.publishCommand({ command: 'reload-external-secrets-providers' });
|
||||
}
|
||||
|
||||
private decryptSecretsSettings(value: string): ExternalSecretsSettings {
|
||||
const decryptedData = this.cipher.decrypt(value);
|
||||
private async getDecryptedSettings(): Promise<ExternalSecretsSettings | null> {
|
||||
const encryptedSettings = await this.settingsRepo.getEncryptedSecretsProviderSettings();
|
||||
if (encryptedSettings === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const decryptedData = this.cipher.decrypt(encryptedSettings);
|
||||
try {
|
||||
return jsonParse(decryptedData);
|
||||
return jsonParse<ExternalSecretsSettings>(decryptedData);
|
||||
} catch (e) {
|
||||
throw new UnexpectedError(
|
||||
'External Secrets Settings could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data.',
|
||||
@@ -101,18 +105,8 @@ export class ExternalSecretsManager {
|
||||
}
|
||||
}
|
||||
|
||||
private async getDecryptedSettings(
|
||||
settingsRepo: SettingsRepository,
|
||||
): Promise<ExternalSecretsSettings | null> {
|
||||
const encryptedSettings = await settingsRepo.getEncryptedSecretsProviderSettings();
|
||||
if (encryptedSettings === null) {
|
||||
return null;
|
||||
}
|
||||
return this.decryptSecretsSettings(encryptedSettings);
|
||||
}
|
||||
|
||||
private async internalInit() {
|
||||
const settings = await this.getDecryptedSettings(this.settingsRepo);
|
||||
const settings = await this.getDecryptedSettings();
|
||||
if (!settings) {
|
||||
return;
|
||||
}
|
||||
@@ -245,16 +239,11 @@ export class ExternalSecretsManager {
|
||||
}));
|
||||
}
|
||||
|
||||
getProviderWithSettings(provider: string):
|
||||
| {
|
||||
getProviderWithSettings(provider: string): {
|
||||
provider: SecretsProvider;
|
||||
settings: SecretsProviderSettings;
|
||||
}
|
||||
| undefined {
|
||||
} {
|
||||
const providerConstructor = this.secretsProviders.getProvider(provider);
|
||||
if (!providerConstructor) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
provider: this.getProvider(provider) ?? new providerConstructor(),
|
||||
settings: this.cachedSettings[provider] ?? {},
|
||||
@@ -276,7 +265,7 @@ export class ExternalSecretsManager {
|
||||
|
||||
async setProviderSettings(provider: string, data: IDataObject, userId?: string) {
|
||||
let isNewProvider = false;
|
||||
let settings = await this.getDecryptedSettings(this.settingsRepo);
|
||||
let settings = await this.getDecryptedSettings();
|
||||
if (!settings) {
|
||||
settings = {};
|
||||
}
|
||||
@@ -289,7 +278,7 @@ export class ExternalSecretsManager {
|
||||
settings: data,
|
||||
};
|
||||
|
||||
await this.saveAndSetSettings(settings, this.settingsRepo);
|
||||
await this.saveAndSetSettings(settings);
|
||||
this.cachedSettings = settings;
|
||||
await this.reloadProvider(provider);
|
||||
this.broadcastReloadExternalSecretsProviders();
|
||||
@@ -298,7 +287,7 @@ export class ExternalSecretsManager {
|
||||
}
|
||||
|
||||
async setProviderConnected(provider: string, connected: boolean) {
|
||||
let settings = await this.getDecryptedSettings(this.settingsRepo);
|
||||
let settings = await this.getDecryptedSettings();
|
||||
if (!settings) {
|
||||
settings = {};
|
||||
}
|
||||
@@ -308,7 +297,7 @@ export class ExternalSecretsManager {
|
||||
settings: settings[provider]?.settings ?? {},
|
||||
};
|
||||
|
||||
await this.saveAndSetSettings(settings, this.settingsRepo);
|
||||
await this.saveAndSetSettings(settings);
|
||||
this.cachedSettings = settings;
|
||||
await this.reloadProvider(provider);
|
||||
await this.updateSecrets();
|
||||
@@ -333,9 +322,9 @@ export class ExternalSecretsManager {
|
||||
return this.cipher.encrypt(settings);
|
||||
}
|
||||
|
||||
async saveAndSetSettings(settings: ExternalSecretsSettings, settingsRepo: SettingsRepository) {
|
||||
async saveAndSetSettings(settings: ExternalSecretsSettings) {
|
||||
const encryptedSettings = this.encryptSecretsSettings(settings);
|
||||
await settingsRepo.saveEncryptedSecretsProviderSettings(encryptedSettings);
|
||||
await this.settingsRepo.saveEncryptedSecretsProviderSettings(encryptedSettings);
|
||||
}
|
||||
|
||||
async testProviderSettings(
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Service } from '@n8n/di';
|
||||
|
||||
import type { SecretsProvider } from '@/interfaces';
|
||||
|
||||
import { AwsSecretsManager } from './providers/aws-secrets/aws-secrets-manager';
|
||||
import { AzureKeyVault } from './providers/azure-key-vault/azure-key-vault';
|
||||
import { GcpSecretsManager } from './providers/gcp-secrets-manager/gcp-secrets-manager';
|
||||
import { InfisicalProvider } from './providers/infisical';
|
||||
import { VaultProvider } from './providers/vault';
|
||||
import type { SecretsProvider } from './types';
|
||||
|
||||
@Service()
|
||||
export class ExternalSecretsProviders {
|
||||
@@ -18,8 +17,8 @@ export class ExternalSecretsProviders {
|
||||
gcpSecretsManager: GcpSecretsManager,
|
||||
};
|
||||
|
||||
getProvider(name: string): { new (): SecretsProvider } | null {
|
||||
return this.providers[name] ?? null;
|
||||
getProvider(name: string): { new (): SecretsProvider } {
|
||||
return this.providers[name];
|
||||
}
|
||||
|
||||
hasProvider(name: string) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
import { Config, Env } from '@n8n/config';
|
||||
|
||||
@Config
|
||||
export class ExternalSecretsConfig {
|
||||
@@ -1,15 +1,29 @@
|
||||
import { Response } from 'express';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
import { Get, Post, RestController, GlobalScope } from '@/decorators';
|
||||
import { ExternalSecretsProviderNotFoundError } from '@/errors/external-secrets-provider-not-found.error';
|
||||
import { Get, Post, RestController, GlobalScope, Middleware } from '@/decorators';
|
||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||
import { ExternalSecretsRequest } from '@/requests';
|
||||
|
||||
import { ExternalSecretsProviders } from './external-secrets-providers.ee';
|
||||
import { ExternalSecretsService } from './external-secrets.service.ee';
|
||||
import { ExternalSecretsRequest } from './types';
|
||||
|
||||
@RestController('/external-secrets')
|
||||
export class ExternalSecretsController {
|
||||
constructor(private readonly secretsService: ExternalSecretsService) {}
|
||||
constructor(
|
||||
private readonly secretsService: ExternalSecretsService,
|
||||
private readonly secretsProviders: ExternalSecretsProviders,
|
||||
) {}
|
||||
|
||||
@Middleware()
|
||||
validateProviderName(req: Request, _: Response, next: NextFunction) {
|
||||
if ('provider' in req.params) {
|
||||
const { provider } = req.params;
|
||||
if (!this.secretsProviders.hasProvider(provider)) {
|
||||
throw new NotFoundError(`Could not find provider "${provider}"`);
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
@Get('/providers')
|
||||
@GlobalScope('externalSecretsProvider:list')
|
||||
@@ -21,21 +35,13 @@ export class ExternalSecretsController {
|
||||
@GlobalScope('externalSecretsProvider:read')
|
||||
async getProvider(req: ExternalSecretsRequest.GetProvider) {
|
||||
const providerName = req.params.provider;
|
||||
try {
|
||||
return this.secretsService.getProvider(providerName);
|
||||
} catch (e) {
|
||||
if (e instanceof ExternalSecretsProviderNotFoundError) {
|
||||
throw new NotFoundError(`Could not find provider "${e.providerName}"`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Post('/providers/:provider/test')
|
||||
@GlobalScope('externalSecretsProvider:read')
|
||||
async testProviderSettings(req: ExternalSecretsRequest.TestProviderSettings, res: Response) {
|
||||
const providerName = req.params.provider;
|
||||
try {
|
||||
const result = await this.secretsService.testProviderSettings(providerName, req.body);
|
||||
if (result.success) {
|
||||
res.statusCode = 200;
|
||||
@@ -43,26 +49,13 @@ export class ExternalSecretsController {
|
||||
res.statusCode = 400;
|
||||
}
|
||||
return result;
|
||||
} catch (e) {
|
||||
if (e instanceof ExternalSecretsProviderNotFoundError) {
|
||||
throw new NotFoundError(`Could not find provider "${e.providerName}"`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Post('/providers/:provider')
|
||||
@GlobalScope('externalSecretsProvider:create')
|
||||
async setProviderSettings(req: ExternalSecretsRequest.SetProviderSettings) {
|
||||
const providerName = req.params.provider;
|
||||
try {
|
||||
await this.secretsService.saveProviderSettings(providerName, req.body, req.user.id);
|
||||
} catch (e) {
|
||||
if (e instanceof ExternalSecretsProviderNotFoundError) {
|
||||
throw new NotFoundError(`Could not find provider "${e.providerName}"`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -70,14 +63,7 @@ export class ExternalSecretsController {
|
||||
@GlobalScope('externalSecretsProvider:update')
|
||||
async setProviderConnected(req: ExternalSecretsRequest.SetProviderConnected) {
|
||||
const providerName = req.params.provider;
|
||||
try {
|
||||
await this.secretsService.saveProviderConnected(providerName, req.body.connected);
|
||||
} catch (e) {
|
||||
if (e instanceof ExternalSecretsProviderNotFoundError) {
|
||||
throw new NotFoundError(`Could not find provider "${e.providerName}"`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -85,7 +71,6 @@ export class ExternalSecretsController {
|
||||
@GlobalScope('externalSecretsProvider:sync')
|
||||
async updateProvider(req: ExternalSecretsRequest.UpdateProvider, res: Response) {
|
||||
const providerName = req.params.provider;
|
||||
try {
|
||||
const resp = await this.secretsService.updateProvider(providerName);
|
||||
if (resp) {
|
||||
res.statusCode = 200;
|
||||
@@ -93,12 +78,6 @@ export class ExternalSecretsController {
|
||||
res.statusCode = 400;
|
||||
}
|
||||
return { updated: resp };
|
||||
} catch (e) {
|
||||
if (e instanceof ExternalSecretsProviderNotFoundError) {
|
||||
throw new NotFoundError(`Could not find provider "${e.providerName}"`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/secrets')
|
||||
|
||||
@@ -3,20 +3,15 @@ import type { IDataObject } from 'n8n-workflow';
|
||||
import { deepCopy } from 'n8n-workflow';
|
||||
|
||||
import { CREDENTIAL_BLANKING_VALUE } from '@/constants';
|
||||
import { ExternalSecretsProviderNotFoundError } from '@/errors/external-secrets-provider-not-found.error';
|
||||
import type { SecretsProvider } from '@/interfaces';
|
||||
import type { ExternalSecretsRequest } from '@/requests';
|
||||
|
||||
import { ExternalSecretsManager } from './external-secrets-manager.ee';
|
||||
import type { ExternalSecretsRequest, SecretsProvider } from './types';
|
||||
|
||||
@Service()
|
||||
export class ExternalSecretsService {
|
||||
getProvider(providerName: string): ExternalSecretsRequest.GetProviderResponse | null {
|
||||
const providerAndSettings =
|
||||
Container.get(ExternalSecretsManager).getProviderWithSettings(providerName);
|
||||
if (!providerAndSettings) {
|
||||
throw new ExternalSecretsProviderNotFoundError(providerName);
|
||||
}
|
||||
const { provider, settings } = providerAndSettings;
|
||||
return {
|
||||
displayName: provider.displayName,
|
||||
@@ -106,20 +101,12 @@ export class ExternalSecretsService {
|
||||
async saveProviderSettings(providerName: string, data: IDataObject, userId: string) {
|
||||
const providerAndSettings =
|
||||
Container.get(ExternalSecretsManager).getProviderWithSettings(providerName);
|
||||
if (!providerAndSettings) {
|
||||
throw new ExternalSecretsProviderNotFoundError(providerName);
|
||||
}
|
||||
const { settings } = providerAndSettings;
|
||||
const newData = this.unredact(data, settings.settings);
|
||||
await Container.get(ExternalSecretsManager).setProviderSettings(providerName, newData, userId);
|
||||
}
|
||||
|
||||
async saveProviderConnected(providerName: string, connected: boolean) {
|
||||
const providerAndSettings =
|
||||
Container.get(ExternalSecretsManager).getProviderWithSettings(providerName);
|
||||
if (!providerAndSettings) {
|
||||
throw new ExternalSecretsProviderNotFoundError(providerName);
|
||||
}
|
||||
await Container.get(ExternalSecretsManager).setProviderConnected(providerName, connected);
|
||||
return this.getProvider(providerName);
|
||||
}
|
||||
@@ -131,20 +118,12 @@ export class ExternalSecretsService {
|
||||
async testProviderSettings(providerName: string, data: IDataObject) {
|
||||
const providerAndSettings =
|
||||
Container.get(ExternalSecretsManager).getProviderWithSettings(providerName);
|
||||
if (!providerAndSettings) {
|
||||
throw new ExternalSecretsProviderNotFoundError(providerName);
|
||||
}
|
||||
const { settings } = providerAndSettings;
|
||||
const newData = this.unredact(data, settings.settings);
|
||||
return await Container.get(ExternalSecretsManager).testProviderSettings(providerName, newData);
|
||||
}
|
||||
|
||||
async updateProvider(providerName: string) {
|
||||
const providerAndSettings =
|
||||
Container.get(ExternalSecretsManager).getProviderWithSettings(providerName);
|
||||
if (!providerAndSettings) {
|
||||
throw new ExternalSecretsProviderNotFoundError(providerName);
|
||||
}
|
||||
return await Container.get(ExternalSecretsManager).updateProvider(providerName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,11 @@ import { Container } from '@n8n/di';
|
||||
import { Logger } from 'n8n-core';
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { UnknownAuthTypeError } from '@/errors/unknown-auth-type.error';
|
||||
import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '@/external-secrets.ee/constants';
|
||||
import type { SecretsProvider, SecretsProviderState } from '@/interfaces';
|
||||
|
||||
import { AwsSecretsClient } from './aws-secrets-client';
|
||||
import type { AwsSecretsManagerContext } from './types';
|
||||
import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '../../constants';
|
||||
import { UnknownAuthTypeError } from '../../errors/unknown-auth-type.error';
|
||||
import type { SecretsProvider, SecretsProviderState } from '../../types';
|
||||
|
||||
export class AwsSecretsManager implements SecretsProvider {
|
||||
name = 'awsSecretsManager';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SecretsProviderSettings } from '@/interfaces';
|
||||
import type { SecretsProviderSettings } from '../../types';
|
||||
|
||||
export type SecretsNamesPage = {
|
||||
NextToken?: string;
|
||||
|
||||
@@ -4,10 +4,9 @@ import { Logger } from 'n8n-core';
|
||||
import { ensureError } from 'n8n-workflow';
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '@/external-secrets.ee/constants';
|
||||
import type { SecretsProvider, SecretsProviderState } from '@/interfaces';
|
||||
|
||||
import type { AzureKeyVaultContext } from './types';
|
||||
import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '../../constants';
|
||||
import type { SecretsProvider, SecretsProviderState } from '../../types';
|
||||
|
||||
export class AzureKeyVault implements SecretsProvider {
|
||||
name = 'azureKeyVault';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SecretsProviderSettings } from '@/interfaces';
|
||||
import type { SecretsProviderSettings } from '../../types';
|
||||
|
||||
export type AzureKeyVaultContext = SecretsProviderSettings<{
|
||||
vaultName: string;
|
||||
|
||||
@@ -3,14 +3,13 @@ import { Container } from '@n8n/di';
|
||||
import { Logger } from 'n8n-core';
|
||||
import { ensureError, jsonParse, type INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '@/external-secrets.ee/constants';
|
||||
import type { SecretsProvider, SecretsProviderState } from '@/interfaces';
|
||||
|
||||
import type {
|
||||
GcpSecretsManagerContext,
|
||||
GcpSecretAccountKey,
|
||||
RawGcpSecretAccountKey,
|
||||
} from './types';
|
||||
import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '../../constants';
|
||||
import type { SecretsProvider, SecretsProviderState } from '../../types';
|
||||
|
||||
export class GcpSecretsManager implements SecretsProvider {
|
||||
name = 'gcpSecretsManager';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SecretsProviderSettings } from '@/interfaces';
|
||||
import type { SecretsProviderSettings } from '../../types';
|
||||
|
||||
type JsonString = string;
|
||||
|
||||
|
||||
@@ -3,9 +3,8 @@ import { getServiceTokenData } from 'infisical-node/lib/api/serviceTokenData';
|
||||
import { populateClientWorkspaceConfigsHelper } from 'infisical-node/lib/helpers/key';
|
||||
import { UnexpectedError, type IDataObject, type INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import type { SecretsProvider, SecretsProviderSettings, SecretsProviderState } from '@/interfaces';
|
||||
|
||||
import { EXTERNAL_SECRETS_NAME_REGEX } from '../constants';
|
||||
import type { SecretsProvider, SecretsProviderSettings, SecretsProviderState } from '../types';
|
||||
|
||||
export interface InfisicalSettings {
|
||||
token: string;
|
||||
|
||||
@@ -4,11 +4,10 @@ import axios from 'axios';
|
||||
import { Logger } from 'n8n-core';
|
||||
import type { IDataObject, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import type { SecretsProviderSettings, SecretsProviderState } from '@/interfaces';
|
||||
import { SecretsProvider } from '@/interfaces';
|
||||
|
||||
import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '../constants';
|
||||
import { preferGet } from '../external-secrets-helper.ee';
|
||||
import { ExternalSecretsConfig } from '../external-secrets.config';
|
||||
import type { SecretsProviderSettings, SecretsProviderState } from '../types';
|
||||
import { SecretsProvider } from '../types';
|
||||
|
||||
type VaultAuthMethod = 'token' | 'usernameAndPassword' | 'appRole';
|
||||
|
||||
@@ -419,7 +418,7 @@ export class VaultProvider extends SecretsProvider {
|
||||
listPath += path;
|
||||
let listResp: AxiosResponse<VaultResponse<VaultSecretList>>;
|
||||
try {
|
||||
const shouldPreferGet = preferGet();
|
||||
const shouldPreferGet = Container.get(ExternalSecretsConfig).preferGet;
|
||||
const url = `${listPath}${shouldPreferGet ? '?list=true' : ''}`;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const method = shouldPreferGet ? 'GET' : ('LIST' as any);
|
||||
|
||||
56
packages/cli/src/external-secrets.ee/types.ts
Normal file
56
packages/cli/src/external-secrets.ee/types.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { IDataObject, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import type { AuthenticatedRequest } from '@/requests';
|
||||
|
||||
export interface SecretsProviderSettings<T = IDataObject> {
|
||||
connected: boolean;
|
||||
connectedAt: Date | null;
|
||||
settings: T;
|
||||
}
|
||||
|
||||
export interface ExternalSecretsSettings {
|
||||
[key: string]: SecretsProviderSettings;
|
||||
}
|
||||
|
||||
export type SecretsProviderState = 'initializing' | 'connected' | 'error';
|
||||
|
||||
export abstract class SecretsProvider {
|
||||
displayName: string;
|
||||
|
||||
name: string;
|
||||
|
||||
properties: INodeProperties[];
|
||||
|
||||
state: SecretsProviderState;
|
||||
|
||||
abstract init(settings: SecretsProviderSettings): Promise<void>;
|
||||
abstract connect(): Promise<void>;
|
||||
abstract disconnect(): Promise<void>;
|
||||
abstract update(): Promise<void>;
|
||||
abstract test(): Promise<[boolean] | [boolean, string]>;
|
||||
abstract getSecret(name: string): unknown;
|
||||
abstract hasSecret(name: string): boolean;
|
||||
abstract getSecretNames(): string[];
|
||||
}
|
||||
|
||||
export declare namespace ExternalSecretsRequest {
|
||||
type GetProviderResponse = Pick<SecretsProvider, 'displayName' | 'name' | 'properties'> & {
|
||||
icon: string;
|
||||
connected: boolean;
|
||||
connectedAt: Date | null;
|
||||
state: SecretsProviderState;
|
||||
data: IDataObject;
|
||||
};
|
||||
|
||||
type GetProviders = AuthenticatedRequest;
|
||||
type GetProvider = AuthenticatedRequest<{ provider: string }, GetProviderResponse>;
|
||||
type SetProviderSettings = AuthenticatedRequest<{ provider: string }, {}, IDataObject>;
|
||||
type TestProviderSettings = SetProviderSettings;
|
||||
type SetProviderConnected = AuthenticatedRequest<
|
||||
{ provider: string },
|
||||
{},
|
||||
{ connected: boolean }
|
||||
>;
|
||||
|
||||
type UpdateProvider = AuthenticatedRequest<{ provider: string }>;
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import type {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialsDecrypted,
|
||||
ICredentialsEncrypted,
|
||||
IDataObject,
|
||||
IDeferredPromise,
|
||||
IExecuteResponsePromiseData,
|
||||
IRun,
|
||||
@@ -17,7 +16,6 @@ import type {
|
||||
ExecutionStatus,
|
||||
ExecutionSummary,
|
||||
FeatureFlags,
|
||||
INodeProperties,
|
||||
IUserSettings,
|
||||
IWorkflowExecutionDataProcess,
|
||||
DeduplicationMode,
|
||||
@@ -364,34 +362,3 @@ export interface N8nApp {
|
||||
}
|
||||
|
||||
export type UserSettings = Pick<User, 'id' | 'settings'>;
|
||||
|
||||
export interface SecretsProviderSettings<T = IDataObject> {
|
||||
connected: boolean;
|
||||
connectedAt: Date | null;
|
||||
settings: T;
|
||||
}
|
||||
|
||||
export interface ExternalSecretsSettings {
|
||||
[key: string]: SecretsProviderSettings;
|
||||
}
|
||||
|
||||
export type SecretsProviderState = 'initializing' | 'connected' | 'error';
|
||||
|
||||
export abstract class SecretsProvider {
|
||||
displayName: string;
|
||||
|
||||
name: string;
|
||||
|
||||
properties: INodeProperties[];
|
||||
|
||||
state: SecretsProviderState;
|
||||
|
||||
abstract init(settings: SecretsProviderSettings): Promise<void>;
|
||||
abstract connect(): Promise<void>;
|
||||
abstract disconnect(): Promise<void>;
|
||||
abstract update(): Promise<void>;
|
||||
abstract test(): Promise<[boolean] | [boolean, string]>;
|
||||
abstract getSecret(name: string): unknown;
|
||||
abstract hasSecret(name: string): boolean;
|
||||
abstract getSecretNames(): string[];
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { AssignableRole, GlobalRole, Scope } from '@n8n/permissions';
|
||||
import type express from 'express';
|
||||
import type {
|
||||
ICredentialDataDecryptedObject,
|
||||
IDataObject,
|
||||
INodeCredentialTestRequest,
|
||||
IPersonalizationSurveyAnswersV4,
|
||||
IUser,
|
||||
@@ -15,9 +14,7 @@ import type { User } from '@/databases/entities/user';
|
||||
import type { Variables } from '@/databases/entities/variables';
|
||||
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
|
||||
import type { WorkflowHistory } from '@/databases/entities/workflow-history';
|
||||
import type { SecretsProvider, SecretsProviderState } from '@/interfaces';
|
||||
|
||||
import type { ScopesField } from './services/role.service';
|
||||
import type { ScopesField } from '@/services/role.service';
|
||||
|
||||
export type APIRequest<
|
||||
RouteParams = {},
|
||||
@@ -310,28 +307,6 @@ export declare namespace VariablesRequest {
|
||||
type Delete = Get;
|
||||
}
|
||||
|
||||
export declare namespace ExternalSecretsRequest {
|
||||
type GetProviderResponse = Pick<SecretsProvider, 'displayName' | 'name' | 'properties'> & {
|
||||
icon: string;
|
||||
connected: boolean;
|
||||
connectedAt: Date | null;
|
||||
state: SecretsProviderState;
|
||||
data: IDataObject;
|
||||
};
|
||||
|
||||
type GetProviders = AuthenticatedRequest;
|
||||
type GetProvider = AuthenticatedRequest<{ provider: string }, GetProviderResponse>;
|
||||
type SetProviderSettings = AuthenticatedRequest<{ provider: string }, {}, IDataObject>;
|
||||
type TestProviderSettings = SetProviderSettings;
|
||||
type SetProviderConnected = AuthenticatedRequest<
|
||||
{ provider: string },
|
||||
{},
|
||||
{ connected: boolean }
|
||||
>;
|
||||
|
||||
type UpdateProvider = AuthenticatedRequest<{ provider: string }>;
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
// /workflow-history
|
||||
// ----------------------------------
|
||||
|
||||
@@ -9,7 +9,7 @@ import { SettingsRepository } from '@/databases/repositories/settings.repository
|
||||
import type { EventService } from '@/events/event.service';
|
||||
import { ExternalSecretsManager } from '@/external-secrets.ee/external-secrets-manager.ee';
|
||||
import { ExternalSecretsProviders } from '@/external-secrets.ee/external-secrets-providers.ee';
|
||||
import type { ExternalSecretsSettings, SecretsProviderState } from '@/interfaces';
|
||||
import type { ExternalSecretsSettings, SecretsProviderState } from '@/external-secrets.ee/types';
|
||||
import { License } from '@/license';
|
||||
|
||||
import {
|
||||
@@ -60,6 +60,7 @@ const resetManager = async () => {
|
||||
ExternalSecretsManager,
|
||||
new ExternalSecretsManager(
|
||||
logger,
|
||||
mock(),
|
||||
Container.get(SettingsRepository),
|
||||
Container.get(License),
|
||||
mockProvidersInstance,
|
||||
@@ -114,6 +115,7 @@ beforeAll(async () => {
|
||||
ExternalSecretsManager,
|
||||
new ExternalSecretsManager(
|
||||
logger,
|
||||
mock(),
|
||||
Container.get(SettingsRepository),
|
||||
Container.get(License),
|
||||
mockProvidersInstance,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Container } from '@n8n/di';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import express from 'express';
|
||||
import { Logger } from 'n8n-core';
|
||||
import type superagent from 'superagent';
|
||||
import request from 'supertest';
|
||||
import { URL } from 'url';
|
||||
@@ -18,7 +17,7 @@ import { Push } from '@/push';
|
||||
import type { APIRequest } from '@/requests';
|
||||
import { Telemetry } from '@/telemetry';
|
||||
|
||||
import { mockInstance } from '../../../shared/mocking';
|
||||
import { mockInstance, mockLogger } from '../../../shared/mocking';
|
||||
import { PUBLIC_API_REST_PATH_SEGMENT, REST_PATH_SEGMENT } from '../constants';
|
||||
import { LicenseMocker } from '../license';
|
||||
import * as testDb from '../test-db';
|
||||
@@ -101,7 +100,7 @@ export const setupTestServer = ({
|
||||
});
|
||||
|
||||
// Mock all telemetry and logging
|
||||
mockInstance(Logger);
|
||||
mockLogger();
|
||||
mockInstance(PostHogClient);
|
||||
mockInstance(Push);
|
||||
mockInstance(Telemetry);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { IDataObject, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { SecretsProvider } from '@/interfaces';
|
||||
import type { SecretsProviderSettings, SecretsProviderState } from '@/interfaces';
|
||||
import { SecretsProvider } from '@/external-secrets.ee/types';
|
||||
import type { SecretsProviderSettings, SecretsProviderState } from '@/external-secrets.ee/types';
|
||||
|
||||
export class MockProviders {
|
||||
providers: Record<string, { new (): SecretsProvider }> = {
|
||||
@@ -12,8 +12,8 @@ export class MockProviders {
|
||||
this.providers = providers;
|
||||
}
|
||||
|
||||
getProvider(name: string): { new (): SecretsProvider } | null {
|
||||
return this.providers[name] ?? null;
|
||||
getProvider(name: string): { new (): SecretsProvider } {
|
||||
return this.providers[name];
|
||||
}
|
||||
|
||||
hasProvider(name: string) {
|
||||
@@ -93,6 +93,10 @@ export class DummyProvider extends SecretsProvider {
|
||||
}
|
||||
}
|
||||
|
||||
export class AnotherDummyProvider extends DummyProvider {
|
||||
name = 'another_dummy';
|
||||
}
|
||||
|
||||
export class ErrorProvider extends SecretsProvider {
|
||||
secrets: Record<string, string> = {};
|
||||
|
||||
@@ -112,7 +116,7 @@ export class ErrorProvider extends SecretsProvider {
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
throw new Error();
|
||||
// no-op
|
||||
}
|
||||
|
||||
async update(): Promise<void> {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Container } from '@n8n/di';
|
||||
import { DataSource, EntityManager, type EntityMetadata } from '@n8n/typeorm';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { Class } from 'n8n-core';
|
||||
import type { Logger } from 'n8n-core';
|
||||
import type { Cipher, Class, Logger } from 'n8n-core';
|
||||
import type { DeepPartial } from 'ts-essentials';
|
||||
|
||||
export const mockInstance = <T>(
|
||||
@@ -25,3 +24,9 @@ export const mockEntityManager = (entityClass: Class) => {
|
||||
};
|
||||
|
||||
export const mockLogger = () => mock<Logger>({ scoped: jest.fn().mockReturnValue(mock<Logger>()) });
|
||||
|
||||
export const mockCipher = () =>
|
||||
mock<Cipher>({
|
||||
encrypt: (data) => (typeof data === 'string' ? data : JSON.stringify(data)),
|
||||
decrypt: (data) => data,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user