mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
refactor(core): Abstract away InstanceSettings and encryptionKey into injectable services (no-changelog) (#7471)
This change ensures that things like `encryptionKey` and `instanceId` are always available directly where they are needed, instead of passing them around throughout the code.
This commit is contained in:
committed by
GitHub
parent
519680c2cf
commit
b6de910cbe
@@ -6,7 +6,11 @@ module.exports = {
|
||||
},
|
||||
globalSetup: '<rootDir>/test/setup.ts',
|
||||
globalTeardown: '<rootDir>/test/teardown.ts',
|
||||
setupFilesAfterEnv: ['<rootDir>/test/setup-mocks.ts', '<rootDir>/test/extend-expect.ts'],
|
||||
setupFilesAfterEnv: [
|
||||
'<rootDir>/test/setup-test-folder.ts',
|
||||
'<rootDir>/test/setup-mocks.ts',
|
||||
'<rootDir>/test/extend-expect.ts',
|
||||
],
|
||||
coveragePathIgnorePatterns: ['/src/databases/migrations/'],
|
||||
testTimeout: 10_000,
|
||||
};
|
||||
|
||||
@@ -121,7 +121,6 @@
|
||||
"connect-history-api-fallback": "^1.6.0",
|
||||
"convict": "^6.2.4",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"crypto-js": "~4.1.1",
|
||||
"csrf": "^3.1.0",
|
||||
"curlconverter": "3.21.0",
|
||||
"dotenv": "^8.0.0",
|
||||
|
||||
@@ -45,8 +45,6 @@ export abstract class AbstractServer {
|
||||
|
||||
protected endpointWebhookWaiting: string;
|
||||
|
||||
protected instanceId = '';
|
||||
|
||||
protected webhooksEnabled = true;
|
||||
|
||||
protected testWebhooksEnabled = false;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { existsSync } from 'fs';
|
||||
import { mkdir, utimes, open, rm } from 'fs/promises';
|
||||
import { join, dirname } from 'path';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { Container } from 'typedi';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import { LoggerProxy, sleep } from 'n8n-workflow';
|
||||
import { inProduction } from '@/constants';
|
||||
|
||||
@@ -16,7 +17,8 @@ export const touchFile = async (filePath: string): Promise<void> => {
|
||||
}
|
||||
};
|
||||
|
||||
const journalFile = join(UserSettings.getUserN8nFolderPath(), 'crash.journal');
|
||||
const { n8nFolder } = Container.get(InstanceSettings);
|
||||
const journalFile = join(n8nFolder, 'crash.journal');
|
||||
|
||||
export const init = async () => {
|
||||
if (!inProduction) return;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
|
||||
import { Credentials, NodeExecuteFunctions } from 'n8n-core';
|
||||
import get from 'lodash/get';
|
||||
|
||||
@@ -53,7 +52,7 @@ import { CredentialTypes } from '@/CredentialTypes';
|
||||
import { CredentialsOverwrites } from '@/CredentialsOverwrites';
|
||||
import { whereClause } from './UserManagement/UserManagementHelper';
|
||||
import { RESPONSE_ERROR_MESSAGES } from './constants';
|
||||
import { Container } from 'typedi';
|
||||
import { Service } from 'typedi';
|
||||
import { isObjectLiteral } from './utils';
|
||||
|
||||
const { OAUTH2_CREDENTIAL_TEST_SUCCEEDED, OAUTH2_CREDENTIAL_TEST_FAILED } = RESPONSE_ERROR_MESSAGES;
|
||||
@@ -87,12 +86,15 @@ const mockNodeTypes: INodeTypes = {
|
||||
},
|
||||
};
|
||||
|
||||
@Service()
|
||||
export class CredentialsHelper extends ICredentialsHelper {
|
||||
private credentialTypes = Container.get(CredentialTypes);
|
||||
|
||||
private nodeTypes = Container.get(NodeTypes);
|
||||
|
||||
private credentialsOverwrites = Container.get(CredentialsOverwrites);
|
||||
constructor(
|
||||
private readonly credentialTypes: CredentialTypes,
|
||||
private readonly nodeTypes: NodeTypes,
|
||||
private readonly credentialsOverwrites: CredentialsOverwrites,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the required authentication information to the request
|
||||
@@ -349,7 +351,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||
expressionResolveValues?: ICredentialsExpressionResolveValues,
|
||||
): Promise<ICredentialDataDecryptedObject> {
|
||||
const credentials = await this.getCredentials(nodeCredentials, type);
|
||||
const decryptedDataOriginal = credentials.getData(this.encryptionKey);
|
||||
const decryptedDataOriginal = credentials.getData();
|
||||
|
||||
if (raw === true) {
|
||||
return decryptedDataOriginal;
|
||||
@@ -469,7 +471,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||
): Promise<void> {
|
||||
const credentials = await this.getCredentials(nodeCredentials, type);
|
||||
|
||||
credentials.setData(data, this.encryptionKey);
|
||||
credentials.setData(data);
|
||||
const newCredentialsData = credentials.getDataToSave() as ICredentialsDb;
|
||||
|
||||
// Add special database related data
|
||||
|
||||
@@ -5,13 +5,12 @@ import type {
|
||||
SecretsProviderSettings,
|
||||
} from '@/Interfaces';
|
||||
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { Cipher } from 'n8n-core';
|
||||
import Container, { Service } from 'typedi';
|
||||
|
||||
import { AES, enc } from 'crypto-js';
|
||||
import { getLogger } from '@/Logger';
|
||||
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
import { jsonParse, type IDataObject } from 'n8n-workflow';
|
||||
import {
|
||||
EXTERNAL_SECRETS_INITIAL_BACKOFF,
|
||||
EXTERNAL_SECRETS_MAX_BACKOFF,
|
||||
@@ -42,6 +41,7 @@ export class ExternalSecretsManager {
|
||||
private settingsRepo: SettingsRepository,
|
||||
private license: License,
|
||||
private secretsProviders: ExternalSecretsProviders,
|
||||
private cipher: Cipher,
|
||||
) {}
|
||||
|
||||
async init(): Promise<void> {
|
||||
@@ -86,15 +86,10 @@ export class ExternalSecretsManager {
|
||||
await Container.get(OrchestrationMainService).broadcastReloadExternalSecretsProviders();
|
||||
}
|
||||
|
||||
private async getEncryptionKey(): Promise<string> {
|
||||
return UserSettings.getEncryptionKey();
|
||||
}
|
||||
|
||||
private decryptSecretsSettings(value: string, encryptionKey: string): ExternalSecretsSettings {
|
||||
const decryptedData = AES.decrypt(value, encryptionKey);
|
||||
|
||||
private decryptSecretsSettings(value: string): ExternalSecretsSettings {
|
||||
const decryptedData = this.cipher.decrypt(value);
|
||||
try {
|
||||
return JSON.parse(decryptedData.toString(enc.Utf8)) as ExternalSecretsSettings;
|
||||
return jsonParse(decryptedData);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
'External Secrets Settings could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data.',
|
||||
@@ -109,8 +104,7 @@ export class ExternalSecretsManager {
|
||||
if (encryptedSettings === null) {
|
||||
return null;
|
||||
}
|
||||
const encryptionKey = await this.getEncryptionKey();
|
||||
return this.decryptSecretsSettings(encryptedSettings, encryptionKey);
|
||||
return this.decryptSecretsSettings(encryptedSettings);
|
||||
}
|
||||
|
||||
private async internalInit() {
|
||||
@@ -327,13 +321,12 @@ export class ExternalSecretsManager {
|
||||
});
|
||||
}
|
||||
|
||||
encryptSecretsSettings(settings: ExternalSecretsSettings, encryptionKey: string): string {
|
||||
return AES.encrypt(JSON.stringify(settings), encryptionKey).toString();
|
||||
private encryptSecretsSettings(settings: ExternalSecretsSettings): string {
|
||||
return this.cipher.encrypt(settings);
|
||||
}
|
||||
|
||||
async saveAndSetSettings(settings: ExternalSecretsSettings, settingsRepo: SettingsRepository) {
|
||||
const encryptionKey = await this.getEncryptionKey();
|
||||
const encryptedSettings = this.encryptSecretsSettings(settings, encryptionKey);
|
||||
const encryptedSettings = this.encryptSecretsSettings(settings);
|
||||
await settingsRepo.saveEncryptedSecretsProviderSettings(encryptedSettings);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import { ExecutionRepository } from '@db/repositories';
|
||||
import { RoleService } from './services/role.service';
|
||||
import type { EventPayloadWorkflow } from './eventbus/EventMessageClasses/EventMessageWorkflow';
|
||||
import { determineFinalExecutionStatus } from './executionLifecycleHooks/shared/sharedHookFunctions';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
|
||||
function userToPayload(user: User): {
|
||||
userId: string;
|
||||
@@ -50,22 +51,13 @@ function userToPayload(user: User): {
|
||||
|
||||
@Service()
|
||||
export class InternalHooks implements IInternalHooksClass {
|
||||
private instanceId: string;
|
||||
|
||||
public get telemetryInstanceId(): string {
|
||||
return this.instanceId;
|
||||
}
|
||||
|
||||
public get telemetryInstance(): Telemetry {
|
||||
return this.telemetry;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private telemetry: Telemetry,
|
||||
private nodeTypes: NodeTypes,
|
||||
private roleService: RoleService,
|
||||
private executionRepository: ExecutionRepository,
|
||||
eventsService: EventsService,
|
||||
private readonly instanceSettings: InstanceSettings,
|
||||
) {
|
||||
eventsService.on('telemetry.onFirstProductionWorkflowSuccess', async (metrics) =>
|
||||
this.onFirstProductionWorkflowSuccess(metrics),
|
||||
@@ -75,9 +67,7 @@ export class InternalHooks implements IInternalHooksClass {
|
||||
);
|
||||
}
|
||||
|
||||
async init(instanceId: string) {
|
||||
this.instanceId = instanceId;
|
||||
this.telemetry.setInstanceId(instanceId);
|
||||
async init() {
|
||||
await this.telemetry.init();
|
||||
}
|
||||
|
||||
@@ -813,7 +803,7 @@ export class InternalHooks implements IInternalHooksClass {
|
||||
user_id: userCreatedCredentialsData.user.id,
|
||||
credential_type: userCreatedCredentialsData.credential_type,
|
||||
credential_id: userCreatedCredentialsData.credential_id,
|
||||
instance_id: this.instanceId,
|
||||
instance_id: this.instanceSettings.instanceId,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
@@ -847,7 +837,7 @@ export class InternalHooks implements IInternalHooksClass {
|
||||
user_id_sharer: userSharedCredentialsData.user_id_sharer,
|
||||
user_ids_sharees_added: userSharedCredentialsData.user_ids_sharees_added,
|
||||
sharees_removed: userSharedCredentialsData.sharees_removed,
|
||||
instance_id: this.instanceId,
|
||||
instance_id: this.instanceSettings.instanceId,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
import { AES, enc } from 'crypto-js';
|
||||
import type { Entry as LdapUser } from 'ldapts';
|
||||
import { Filter } from 'ldapts/filters/Filter';
|
||||
import { Container } from 'typedi';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { Cipher } from 'n8n-core';
|
||||
import { validate } from 'jsonschema';
|
||||
import * as Db from '@/Db';
|
||||
import config from '@/config';
|
||||
@@ -110,22 +109,6 @@ export const validateLdapConfigurationSchema = (
|
||||
return { valid, message };
|
||||
};
|
||||
|
||||
/**
|
||||
* Encrypt password using the instance's encryption key
|
||||
*/
|
||||
export const encryptPassword = async (password: string): Promise<string> => {
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
return AES.encrypt(password, encryptionKey).toString();
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt password using the instance's encryption key
|
||||
*/
|
||||
export const decryptPassword = async (password: string): Promise<string> => {
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
return AES.decrypt(password, encryptionKey).toString(enc.Utf8);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the LDAP configuration (decrypted) form the database
|
||||
*/
|
||||
@@ -134,7 +117,7 @@ export const getLdapConfig = async (): Promise<LdapConfig> => {
|
||||
key: LDAP_FEATURE_NAME,
|
||||
});
|
||||
const configurationData = jsonParse<LdapConfig>(configuration.value);
|
||||
configurationData.bindingAdminPassword = await decryptPassword(
|
||||
configurationData.bindingAdminPassword = Container.get(Cipher).decrypt(
|
||||
configurationData.bindingAdminPassword,
|
||||
);
|
||||
return configurationData;
|
||||
@@ -173,7 +156,7 @@ export const updateLdapConfig = async (ldapConfig: LdapConfig): Promise<void> =>
|
||||
|
||||
LdapManager.updateConfig({ ...ldapConfig });
|
||||
|
||||
ldapConfig.bindingAdminPassword = await encryptPassword(ldapConfig.bindingAdminPassword);
|
||||
ldapConfig.bindingAdminPassword = Container.get(Cipher).encrypt(ldapConfig.bindingAdminPassword);
|
||||
|
||||
if (!ldapConfig.loginEnabled) {
|
||||
ldapConfig.synchronizationEnabled = false;
|
||||
|
||||
@@ -15,7 +15,7 @@ import Container, { Service } from 'typedi';
|
||||
import type { BooleanLicenseFeature, N8nInstanceType, NumericLicenseFeature } from './Interfaces';
|
||||
import type { RedisServicePubSubPublisher } from './services/redis/RedisServicePubSubPublisher';
|
||||
import { RedisService } from './services/redis.service';
|
||||
import { ObjectStoreService } from 'n8n-core';
|
||||
import { InstanceSettings, ObjectStoreService } from 'n8n-core';
|
||||
|
||||
type FeatureReturnType = Partial<
|
||||
{
|
||||
@@ -29,20 +29,17 @@ export class License {
|
||||
|
||||
private manager: LicenseManager | undefined;
|
||||
|
||||
instanceId: string | undefined;
|
||||
|
||||
private redisPublisher: RedisServicePubSubPublisher;
|
||||
|
||||
constructor() {
|
||||
constructor(private readonly instanceSettings: InstanceSettings) {
|
||||
this.logger = getLogger();
|
||||
}
|
||||
|
||||
async init(instanceId: string, instanceType: N8nInstanceType = 'main') {
|
||||
async init(instanceType: N8nInstanceType = 'main') {
|
||||
if (this.manager) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.instanceId = instanceId;
|
||||
const isMainInstance = instanceType === 'main';
|
||||
const server = config.getEnv('license.serverUrl');
|
||||
const autoRenewEnabled = isMainInstance && config.getEnv('license.autoRenewEnabled');
|
||||
@@ -67,7 +64,7 @@ export class License {
|
||||
logger: this.logger,
|
||||
loadCertStr: async () => this.loadCertStr(),
|
||||
saveCertStr,
|
||||
deviceFingerprint: () => instanceId,
|
||||
deviceFingerprint: () => this.instanceSettings.instanceId,
|
||||
onFeatureChange,
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import fsPromises from 'fs/promises';
|
||||
import type { DirectoryLoader, Types } from 'n8n-core';
|
||||
import {
|
||||
CUSTOM_EXTENSION_ENV,
|
||||
UserSettings,
|
||||
InstanceSettings,
|
||||
CustomDirectoryLoader,
|
||||
PackageDirectoryLoader,
|
||||
LazyPackageDirectoryLoader,
|
||||
@@ -47,10 +47,10 @@ export class LoadNodesAndCredentials {
|
||||
|
||||
includeNodes = config.getEnv('nodes.include');
|
||||
|
||||
private downloadFolder: string;
|
||||
|
||||
private postProcessors: Array<() => Promise<void>> = [];
|
||||
|
||||
constructor(private readonly instanceSettings: InstanceSettings) {}
|
||||
|
||||
async init() {
|
||||
if (inTest) throw new Error('Not available in tests');
|
||||
|
||||
@@ -67,8 +67,6 @@ export class LoadNodesAndCredentials {
|
||||
this.excludeNodes.push('n8n-nodes-base.e2eTest');
|
||||
}
|
||||
|
||||
this.downloadFolder = UserSettings.getUserN8nFolderDownloadedNodesPath();
|
||||
|
||||
// Load nodes from `n8n-nodes-base`
|
||||
const basePathsToScan = [
|
||||
// In case "n8n" package is in same node_modules folder.
|
||||
@@ -84,7 +82,9 @@ export class LoadNodesAndCredentials {
|
||||
|
||||
// Load nodes from any other `n8n-nodes-*` packages in the download directory
|
||||
// This includes the community nodes
|
||||
await this.loadNodesFromNodeModules(path.join(this.downloadFolder, 'node_modules'));
|
||||
await this.loadNodesFromNodeModules(
|
||||
path.join(this.instanceSettings.nodesDownloadDir, 'node_modules'),
|
||||
);
|
||||
|
||||
await this.loadNodesFromCustomDirectories();
|
||||
await this.postProcessLoaders();
|
||||
@@ -155,7 +155,7 @@ export class LoadNodesAndCredentials {
|
||||
}
|
||||
|
||||
getCustomDirectories(): string[] {
|
||||
const customDirectories = [UserSettings.getUserN8nFolderCustomExtensionPath()];
|
||||
const customDirectories = [this.instanceSettings.customExtensionDir];
|
||||
|
||||
if (process.env[CUSTOM_EXTENSION_ENV] !== undefined) {
|
||||
const customExtensionFolders = process.env[CUSTOM_EXTENSION_ENV].split(';');
|
||||
@@ -172,7 +172,11 @@ export class LoadNodesAndCredentials {
|
||||
}
|
||||
|
||||
async loadPackage(packageName: string) {
|
||||
const finalNodeUnpackedPath = path.join(this.downloadFolder, 'node_modules', packageName);
|
||||
const finalNodeUnpackedPath = path.join(
|
||||
this.instanceSettings.nodesDownloadDir,
|
||||
'node_modules',
|
||||
packageName,
|
||||
);
|
||||
return this.runDirectoryLoader(PackageDirectoryLoader, finalNodeUnpackedPath);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { AES, enc } from 'crypto-js';
|
||||
import { TOTPService } from './totp.service';
|
||||
import { Service } from 'typedi';
|
||||
import { UserRepository } from '@/databases/repositories';
|
||||
import { Cipher } from 'n8n-core';
|
||||
import { UserRepository } from '@db/repositories';
|
||||
import { TOTPService } from './totp.service';
|
||||
|
||||
@Service()
|
||||
export class MfaService {
|
||||
constructor(
|
||||
private userRepository: UserRepository,
|
||||
public totp: TOTPService,
|
||||
private encryptionKey: string,
|
||||
private cipher: Cipher,
|
||||
) {}
|
||||
|
||||
public generateRecoveryCodes(n = 10) {
|
||||
@@ -17,9 +17,7 @@ export class MfaService {
|
||||
}
|
||||
|
||||
public generateEncryptedRecoveryCodes() {
|
||||
return this.generateRecoveryCodes().map((code) =>
|
||||
AES.encrypt(code, this.encryptionKey).toString(),
|
||||
);
|
||||
return this.generateRecoveryCodes().map((code) => this.cipher.encrypt(code));
|
||||
}
|
||||
|
||||
public async saveSecretAndRecoveryCodes(userId: string, secret: string, recoveryCodes: string[]) {
|
||||
@@ -34,10 +32,8 @@ export class MfaService {
|
||||
}
|
||||
|
||||
public encryptSecretAndRecoveryCodes(rawSecret: string, rawRecoveryCodes: string[]) {
|
||||
const encryptedSecret = AES.encrypt(rawSecret, this.encryptionKey).toString(),
|
||||
encryptedRecoveryCodes = rawRecoveryCodes.map((code) =>
|
||||
AES.encrypt(code, this.encryptionKey).toString(),
|
||||
);
|
||||
const encryptedSecret = this.cipher.encrypt(rawSecret),
|
||||
encryptedRecoveryCodes = rawRecoveryCodes.map((code) => this.cipher.encrypt(code));
|
||||
return {
|
||||
encryptedRecoveryCodes,
|
||||
encryptedSecret,
|
||||
@@ -46,10 +42,8 @@ export class MfaService {
|
||||
|
||||
private decryptSecretAndRecoveryCodes(mfaSecret: string, mfaRecoveryCodes: string[]) {
|
||||
return {
|
||||
decryptedSecret: AES.decrypt(mfaSecret, this.encryptionKey).toString(enc.Utf8),
|
||||
decryptedRecoveryCodes: mfaRecoveryCodes.map((code) =>
|
||||
AES.decrypt(code, this.encryptionKey).toString(enc.Utf8),
|
||||
),
|
||||
decryptedSecret: this.cipher.decrypt(mfaSecret),
|
||||
decryptedRecoveryCodes: mfaRecoveryCodes.map((code) => this.cipher.decrypt(code)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -66,7 +60,7 @@ export class MfaService {
|
||||
}
|
||||
|
||||
public encryptRecoveryCodes(mfaRecoveryCodes: string[]) {
|
||||
return mfaRecoveryCodes.map((code) => AES.encrypt(code, this.encryptionKey).toString());
|
||||
return mfaRecoveryCodes.map((code) => this.cipher.encrypt(code));
|
||||
}
|
||||
|
||||
public async disableMfa(userId: string) {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import OTPAuth from 'otpauth';
|
||||
import { Service } from 'typedi';
|
||||
|
||||
@Service()
|
||||
export class TOTPService {
|
||||
generateSecret(): string {
|
||||
return new OTPAuth.Secret()?.base32;
|
||||
|
||||
@@ -93,7 +93,7 @@ export = {
|
||||
return res.status(404).json({ message: 'Not Found' });
|
||||
}
|
||||
|
||||
const schema = new CredentialsHelper('')
|
||||
const schema = Container.get(CredentialsHelper)
|
||||
.getCredentialsProperties(credentialTypeName)
|
||||
.filter((property) => property.type !== 'hidden');
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const validCredentialsProperties = (
|
||||
): express.Response | void => {
|
||||
const { type, data } = req.body;
|
||||
|
||||
const properties = new CredentialsHelper('')
|
||||
const properties = Container.get(CredentialsHelper)
|
||||
.getCredentialsProperties(type)
|
||||
.filter((property) => property.type !== 'hidden');
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UserSettings, Credentials } from 'n8n-core';
|
||||
import { Credentials } from 'n8n-core';
|
||||
import type { IDataObject, INodeProperties, INodePropertyOptions } from 'n8n-workflow';
|
||||
import * as Db from '@/Db';
|
||||
import type { ICredentialsDb } from '@/Interfaces';
|
||||
@@ -87,8 +87,6 @@ export async function removeCredential(credentials: CredentialsEntity): Promise<
|
||||
}
|
||||
|
||||
export async function encryptCredential(credential: CredentialsEntity): Promise<ICredentialsDb> {
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
|
||||
// Encrypt the data
|
||||
const coreCredential = new Credentials(
|
||||
{ id: null, name: credential.name },
|
||||
@@ -97,7 +95,7 @@ export async function encryptCredential(credential: CredentialsEntity): Promise<
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
coreCredential.setData(credential.data, encryptionKey);
|
||||
coreCredential.setData(credential.data);
|
||||
|
||||
return coreCredential.getDataToSave() as ICredentialsDb;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import {
|
||||
LoadMappingOptions,
|
||||
LoadNodeParameterOptions,
|
||||
LoadNodeListSearch,
|
||||
UserSettings,
|
||||
} from 'n8n-core';
|
||||
|
||||
import type {
|
||||
@@ -146,7 +145,6 @@ import { SourceControlService } from '@/environments/sourceControl/sourceControl
|
||||
import { SourceControlController } from '@/environments/sourceControl/sourceControl.controller.ee';
|
||||
import { ExecutionRepository, SettingsRepository } from '@db/repositories';
|
||||
import type { ExecutionEntity } from '@db/entities/ExecutionEntity';
|
||||
import { TOTPService } from './Mfa/totp.service';
|
||||
import { MfaService } from './Mfa/mfa.service';
|
||||
import { handleMfaDisable, isMfaFeatureEnabled } from './Mfa/helpers';
|
||||
import type { FrontendService } from './services/frontend.service';
|
||||
@@ -159,25 +157,25 @@ import { WorkflowHistoryController } from './workflows/workflowHistory/workflowH
|
||||
const exec = promisify(callbackExec);
|
||||
|
||||
export class Server extends AbstractServer {
|
||||
endpointPresetCredentials: string;
|
||||
private endpointPresetCredentials: string;
|
||||
|
||||
waitTracker: WaitTracker;
|
||||
private waitTracker: WaitTracker;
|
||||
|
||||
activeExecutionsInstance: ActiveExecutions;
|
||||
private activeExecutionsInstance: ActiveExecutions;
|
||||
|
||||
presetCredentialsLoaded: boolean;
|
||||
private presetCredentialsLoaded: boolean;
|
||||
|
||||
loadNodesAndCredentials: LoadNodesAndCredentials;
|
||||
private loadNodesAndCredentials: LoadNodesAndCredentials;
|
||||
|
||||
nodeTypes: NodeTypes;
|
||||
private nodeTypes: NodeTypes;
|
||||
|
||||
credentialTypes: ICredentialTypes;
|
||||
private credentialTypes: ICredentialTypes;
|
||||
|
||||
frontendService: FrontendService;
|
||||
private frontendService: FrontendService;
|
||||
|
||||
postHog: PostHogClient;
|
||||
private postHog: PostHogClient;
|
||||
|
||||
push: Push;
|
||||
private push: Push;
|
||||
|
||||
constructor() {
|
||||
super('main');
|
||||
@@ -285,15 +283,13 @@ export class Server extends AbstractServer {
|
||||
const repositories = Db.collections;
|
||||
setupAuthMiddlewares(app, ignoredEndpoints, this.restEndpoint);
|
||||
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
|
||||
const logger = LoggerProxy;
|
||||
const internalHooks = Container.get(InternalHooks);
|
||||
const mailer = Container.get(UserManagementMailer);
|
||||
const userService = Container.get(UserService);
|
||||
const jwtService = Container.get(JwtService);
|
||||
const postHog = this.postHog;
|
||||
const mfaService = new MfaService(repositories.User, new TOTPService(), encryptionKey);
|
||||
const mfaService = Container.get(MfaService);
|
||||
|
||||
const controllers: object[] = [
|
||||
new EventBusController(),
|
||||
@@ -376,19 +372,16 @@ export class Server extends AbstractServer {
|
||||
await Container.get(MetricsService).configureMetrics(this.app);
|
||||
}
|
||||
|
||||
this.instanceId = await UserSettings.getInstanceId();
|
||||
|
||||
this.frontendService.addToSettings({
|
||||
isNpmAvailable: await exec('npm --version')
|
||||
.then(() => true)
|
||||
.catch(() => false),
|
||||
versionCli: N8N_VERSION,
|
||||
instanceId: this.instanceId,
|
||||
});
|
||||
|
||||
await this.externalHooks.run('frontend.settings', [this.frontendService.getSettings()]);
|
||||
|
||||
await this.postHog.init(this.instanceId);
|
||||
await this.postHog.init();
|
||||
|
||||
const publicApiEndpoint = config.getEnv('publicApi.path');
|
||||
const excludeEndpoints = config.getEnv('security.excludeEndpoints');
|
||||
@@ -739,18 +732,11 @@ export class Server extends AbstractServer {
|
||||
throw new ResponseHelper.NotFoundError(RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL);
|
||||
}
|
||||
|
||||
let encryptionKey: string;
|
||||
try {
|
||||
encryptionKey = await UserSettings.getEncryptionKey();
|
||||
} catch (error) {
|
||||
throw new ResponseHelper.InternalServerError(error.message);
|
||||
}
|
||||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(req.user.id);
|
||||
|
||||
const mode: WorkflowExecuteMode = 'internal';
|
||||
const timezone = config.getEnv('generic.timezone');
|
||||
const credentialsHelper = new CredentialsHelper(encryptionKey);
|
||||
const credentialsHelper = Container.get(CredentialsHelper);
|
||||
const decryptedDataOriginal = await credentialsHelper.getDecrypted(
|
||||
additionalData,
|
||||
credential as INodeCredentialsDetails,
|
||||
@@ -835,7 +821,7 @@ export class Server extends AbstractServer {
|
||||
credential.nodesAccess,
|
||||
);
|
||||
|
||||
credentials.setData(decryptedDataOriginal, encryptionKey);
|
||||
credentials.setData(decryptedDataOriginal);
|
||||
const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb;
|
||||
|
||||
// Add special database related data
|
||||
@@ -889,18 +875,11 @@ export class Server extends AbstractServer {
|
||||
return ResponseHelper.sendErrorResponse(res, errorResponse);
|
||||
}
|
||||
|
||||
let encryptionKey: string;
|
||||
try {
|
||||
encryptionKey = await UserSettings.getEncryptionKey();
|
||||
} catch (error) {
|
||||
throw new ResponseHelper.InternalServerError(error.message);
|
||||
}
|
||||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(req.user.id);
|
||||
|
||||
const mode: WorkflowExecuteMode = 'internal';
|
||||
const timezone = config.getEnv('generic.timezone');
|
||||
const credentialsHelper = new CredentialsHelper(encryptionKey);
|
||||
const credentialsHelper = Container.get(CredentialsHelper);
|
||||
const decryptedDataOriginal = await credentialsHelper.getDecrypted(
|
||||
additionalData,
|
||||
credential as INodeCredentialsDetails,
|
||||
@@ -952,7 +931,7 @@ export class Server extends AbstractServer {
|
||||
credential.type,
|
||||
credential.nodesAccess,
|
||||
);
|
||||
credentials.setData(decryptedDataOriginal, encryptionKey);
|
||||
credentials.setData(decryptedDataOriginal);
|
||||
const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb;
|
||||
// Add special database related data
|
||||
newCredentialsData.updatedAt = new Date();
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
|
||||
/* eslint-disable id-denylist */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { UserSettings, WorkflowExecute } from 'n8n-core';
|
||||
import { WorkflowExecute } from 'n8n-core';
|
||||
|
||||
import type {
|
||||
IDataObject,
|
||||
@@ -1033,14 +1029,10 @@ export async function getBase(
|
||||
const webhookWaitingBaseUrl = urlBaseWebhook + config.getEnv('endpoints.webhookWaiting');
|
||||
const webhookTestBaseUrl = urlBaseWebhook + config.getEnv('endpoints.webhookTest');
|
||||
|
||||
const [encryptionKey, variables] = await Promise.all([
|
||||
UserSettings.getEncryptionKey(),
|
||||
WorkflowHelpers.getVariables(),
|
||||
]);
|
||||
const variables = await WorkflowHelpers.getVariables();
|
||||
|
||||
return {
|
||||
credentialsHelper: new CredentialsHelper(encryptionKey),
|
||||
encryptionKey,
|
||||
credentialsHelper: Container.get(CredentialsHelper),
|
||||
executeWorkflow,
|
||||
restApiUrl: urlBaseWebhook + config.getEnv('endpoints.rest'),
|
||||
timezone,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { setDefaultResultOrder } from 'dns';
|
||||
|
||||
import { Container } from 'typedi';
|
||||
import type { IProcessMessage } from 'n8n-core';
|
||||
import { BinaryDataService, UserSettings, WorkflowExecute } from 'n8n-core';
|
||||
import { BinaryDataService, WorkflowExecute } from 'n8n-core';
|
||||
|
||||
import type {
|
||||
ExecutionError,
|
||||
@@ -107,8 +107,6 @@ class WorkflowRunnerProcess {
|
||||
// Init db since we need to read the license.
|
||||
await Db.init();
|
||||
|
||||
const userSettings = await UserSettings.prepareUserSettings();
|
||||
|
||||
const loadNodesAndCredentials = Container.get(LoadNodesAndCredentials);
|
||||
await loadNodesAndCredentials.init();
|
||||
|
||||
@@ -118,15 +116,14 @@ class WorkflowRunnerProcess {
|
||||
const externalHooks = Container.get(ExternalHooks);
|
||||
await externalHooks.init();
|
||||
|
||||
const instanceId = userSettings.instanceId ?? '';
|
||||
await Container.get(PostHogClient).init(instanceId);
|
||||
await Container.get(InternalHooks).init(instanceId);
|
||||
await Container.get(PostHogClient).init();
|
||||
await Container.get(InternalHooks).init();
|
||||
|
||||
const binaryDataConfig = config.getEnv('binaryDataManager');
|
||||
await Container.get(BinaryDataService).init(binaryDataConfig);
|
||||
|
||||
const license = Container.get(License);
|
||||
await license.init(instanceId);
|
||||
await license.init();
|
||||
|
||||
const workflowSettings = this.data.workflowData.settings ?? {};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { Container } from 'typedi';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import config from '@/config';
|
||||
import { toFlaggedNode } from '@/audit/utils';
|
||||
import { separate } from '@/utils';
|
||||
@@ -81,7 +82,7 @@ function getUnprotectedWebhookNodes(workflows: WorkflowEntity[]) {
|
||||
|
||||
async function getNextVersions(currentVersionName: string) {
|
||||
const BASE_URL = config.getEnv('versionNotifications.endpoint');
|
||||
const instanceId = await UserSettings.getInstanceId();
|
||||
const { instanceId } = Container.get(InstanceSettings);
|
||||
|
||||
const response = await axios.get<n8n.Version[]>(BASE_URL + currentVersionName, {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
|
||||
@@ -2,8 +2,7 @@ import { Command } from '@oclif/command';
|
||||
import { ExitError } from '@oclif/errors';
|
||||
import { Container } from 'typedi';
|
||||
import { LoggerProxy, ErrorReporterProxy as ErrorReporter, sleep } from 'n8n-workflow';
|
||||
import type { IUserSettings } from 'n8n-core';
|
||||
import { BinaryDataService, ObjectStoreService, UserSettings } from 'n8n-core';
|
||||
import { BinaryDataService, InstanceSettings, ObjectStoreService } from 'n8n-core';
|
||||
import type { AbstractServer } from '@/AbstractServer';
|
||||
import { getLogger } from '@/Logger';
|
||||
import config from '@/config';
|
||||
@@ -30,11 +29,9 @@ export abstract class BaseCommand extends Command {
|
||||
|
||||
protected nodeTypes: NodeTypes;
|
||||
|
||||
protected userSettings: IUserSettings;
|
||||
protected instanceSettings: InstanceSettings;
|
||||
|
||||
protected instanceId: string;
|
||||
|
||||
instanceType: N8nInstanceType = 'main';
|
||||
private instanceType: N8nInstanceType = 'main';
|
||||
|
||||
queueModeId: string;
|
||||
|
||||
@@ -48,7 +45,7 @@ export abstract class BaseCommand extends Command {
|
||||
process.once('SIGINT', async () => this.stopProcess());
|
||||
|
||||
// Make sure the settings exist
|
||||
this.userSettings = await UserSettings.prepareUserSettings();
|
||||
this.instanceSettings = Container.get(InstanceSettings);
|
||||
|
||||
await Container.get(LoadNodesAndCredentials).init();
|
||||
this.nodeTypes = Container.get(NodeTypes);
|
||||
@@ -76,9 +73,8 @@ export abstract class BaseCommand extends Command {
|
||||
);
|
||||
}
|
||||
|
||||
this.instanceId = this.userSettings.instanceId ?? '';
|
||||
await Container.get(PostHogClient).init(this.instanceId);
|
||||
await Container.get(InternalHooks).init(this.instanceId);
|
||||
await Container.get(PostHogClient).init();
|
||||
await Container.get(InternalHooks).init();
|
||||
}
|
||||
|
||||
protected setInstanceType(instanceType: N8nInstanceType) {
|
||||
@@ -241,7 +237,7 @@ export abstract class BaseCommand extends Command {
|
||||
|
||||
async initLicense(): Promise<void> {
|
||||
const license = Container.get(License);
|
||||
await license.init(this.instanceId, this.instanceType ?? 'main');
|
||||
await license.init(this.instanceType ?? 'main');
|
||||
|
||||
const activationKey = config.getEnv('license.activationKey');
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { flags } from '@oclif/command';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import type { FindOptionsWhere } from 'typeorm';
|
||||
import { Credentials, UserSettings } from 'n8n-core';
|
||||
import { Credentials } from 'n8n-core';
|
||||
import * as Db from '@/Db';
|
||||
import type { ICredentialsDb, ICredentialsDecryptedDb } from '@/Interfaces';
|
||||
import { BaseCommand } from '../BaseCommand';
|
||||
@@ -113,13 +113,11 @@ export class ExportCredentialsCommand extends BaseCommand {
|
||||
const credentials: ICredentialsDb[] = await Db.collections.Credentials.findBy(findQuery);
|
||||
|
||||
if (flags.decrypted) {
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
|
||||
for (let i = 0; i < credentials.length; i++) {
|
||||
const { name, type, nodesAccess, data } = credentials[i];
|
||||
const id = credentials[i].id;
|
||||
const credential = new Credentials({ id, name }, type, nodesAccess, data);
|
||||
const plainData = credential.getData(encryptionKey);
|
||||
const plainData = credential.getData();
|
||||
(credentials[i] as ICredentialsDecryptedDb).data = plainData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,6 @@ export class ImportCredentialsCommand extends BaseCommand {
|
||||
await this.initOwnerCredentialRole();
|
||||
const user = flags.userId ? await this.getAssignee(flags.userId) : await this.getOwner();
|
||||
|
||||
const encryptionKey = this.userSettings.encryptionKey;
|
||||
|
||||
if (flags.separate) {
|
||||
let { input: inputPath } = flags;
|
||||
|
||||
@@ -97,7 +95,7 @@ export class ImportCredentialsCommand extends BaseCommand {
|
||||
|
||||
if (typeof credential.data === 'object') {
|
||||
// plain data / decrypted input. Should be encrypted first.
|
||||
Credentials.prototype.setData.call(credential, credential.data, encryptionKey);
|
||||
Credentials.prototype.setData.call(credential, credential.data);
|
||||
}
|
||||
|
||||
await this.storeCredential(credential, user);
|
||||
@@ -125,7 +123,7 @@ export class ImportCredentialsCommand extends BaseCommand {
|
||||
for (const credential of credentials) {
|
||||
if (typeof credential.data === 'object') {
|
||||
// plain data / decrypted input. Should be encrypted first.
|
||||
Credentials.prototype.setData.call(credential, credential.data, encryptionKey);
|
||||
Credentials.prototype.setData.call(credential, credential.data);
|
||||
}
|
||||
await this.storeCredential(credential, user);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export class LicenseInfoCommand extends BaseCommand {
|
||||
|
||||
async run() {
|
||||
const license = Container.get(License);
|
||||
await license.init(this.instanceId);
|
||||
await license.init();
|
||||
|
||||
this.logger.info('Printing license information:\n' + license.getInfo());
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import path from 'path';
|
||||
import { mkdir } from 'fs/promises';
|
||||
import { createReadStream, createWriteStream, existsSync } from 'fs';
|
||||
import localtunnel from 'localtunnel';
|
||||
import { TUNNEL_SUBDOMAIN_ENV, UserSettings } from 'n8n-core';
|
||||
import { flags } from '@oclif/command';
|
||||
import stream from 'stream';
|
||||
import replaceStream from 'replacestream';
|
||||
@@ -245,7 +244,7 @@ export class Start extends BaseCommand {
|
||||
if (!config.getEnv('userManagement.jwtSecret')) {
|
||||
// If we don't have a JWT secret set, generate
|
||||
// one based and save to config.
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
const { encryptionKey } = this.instanceSettings;
|
||||
|
||||
// For a key off every other letter from encryption key
|
||||
// CAREFUL: do not change this or it breaks all existing tokens.
|
||||
@@ -256,8 +255,6 @@ export class Start extends BaseCommand {
|
||||
config.set('userManagement.jwtSecret', createHash('sha256').update(baseKey).digest('hex'));
|
||||
}
|
||||
|
||||
await UserSettings.getEncryptionKey();
|
||||
|
||||
// Load settings from database and set them to config.
|
||||
const databaseSettings = await Db.collections.Settings.findBy({ loadOnStartup: true });
|
||||
databaseSettings.forEach((setting) => {
|
||||
@@ -285,28 +282,19 @@ export class Start extends BaseCommand {
|
||||
if (flags.tunnel) {
|
||||
this.log('\nWaiting for tunnel ...');
|
||||
|
||||
let tunnelSubdomain;
|
||||
if (
|
||||
process.env[TUNNEL_SUBDOMAIN_ENV] !== undefined &&
|
||||
process.env[TUNNEL_SUBDOMAIN_ENV] !== ''
|
||||
) {
|
||||
tunnelSubdomain = process.env[TUNNEL_SUBDOMAIN_ENV];
|
||||
} else if (this.userSettings.tunnelSubdomain !== undefined) {
|
||||
tunnelSubdomain = this.userSettings.tunnelSubdomain;
|
||||
}
|
||||
let tunnelSubdomain =
|
||||
process.env.N8N_TUNNEL_SUBDOMAIN ?? this.instanceSettings.tunnelSubdomain ?? '';
|
||||
|
||||
if (tunnelSubdomain === undefined) {
|
||||
if (tunnelSubdomain === '') {
|
||||
// When no tunnel subdomain did exist yet create a new random one
|
||||
const availableCharacters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
this.userSettings.tunnelSubdomain = Array.from({ length: 24 })
|
||||
.map(() => {
|
||||
return availableCharacters.charAt(
|
||||
Math.floor(Math.random() * availableCharacters.length),
|
||||
);
|
||||
})
|
||||
tunnelSubdomain = Array.from({ length: 24 })
|
||||
.map(() =>
|
||||
availableCharacters.charAt(Math.floor(Math.random() * availableCharacters.length)),
|
||||
)
|
||||
.join('');
|
||||
|
||||
await UserSettings.writeUserSettings(this.userSettings);
|
||||
this.instanceSettings.update({ tunnelSubdomain });
|
||||
}
|
||||
|
||||
const tunnelSettings: localtunnel.TunnelConfig = {
|
||||
|
||||
@@ -318,7 +318,6 @@ export class Worker extends BaseCommand {
|
||||
await Container.get(OrchestrationWorkerService).init();
|
||||
await Container.get(OrchestrationHandlerWorkerService).initWithOptions({
|
||||
queueModeId: this.queueModeId,
|
||||
instanceId: this.instanceId,
|
||||
redisPublisher: Container.get(OrchestrationWorkerService).redisPublisher,
|
||||
getRunningJobIds: () => Object.keys(Worker.runningJobs),
|
||||
getRunningJobsSummary: () => Object.values(Worker.runningJobsSummary),
|
||||
|
||||
@@ -21,12 +21,9 @@ if (inE2ETests) {
|
||||
N8N_AI_ENABLED: 'true',
|
||||
};
|
||||
} else if (inTest) {
|
||||
const testsDir = join(tmpdir(), 'n8n-tests/');
|
||||
mkdirSync(testsDir, { recursive: true });
|
||||
process.env.N8N_LOG_LEVEL = 'silent';
|
||||
process.env.N8N_ENCRYPTION_KEY = 'test-encryption-key';
|
||||
process.env.N8N_PUBLIC_API_DISABLED = 'true';
|
||||
process.env.N8N_USER_FOLDER = mkdtempSync(testsDir);
|
||||
process.env.SKIP_STATISTICS_EVENTS = 'true';
|
||||
} else {
|
||||
dotenv.config();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import path from 'path';
|
||||
import convict from 'convict';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { Container } from 'typedi';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { ensureStringArray } from './utils';
|
||||
|
||||
@@ -881,7 +882,7 @@ export const schema = {
|
||||
location: {
|
||||
doc: 'Log file location; only used if log output is set to file.',
|
||||
format: String,
|
||||
default: path.join(UserSettings.getUserN8nFolderPath(), 'logs/n8n.log'),
|
||||
default: path.join(Container.get(InstanceSettings).n8nFolder, 'logs/n8n.log'),
|
||||
env: 'N8N_LOG_FILE_LOCATION',
|
||||
},
|
||||
},
|
||||
@@ -947,7 +948,7 @@ export const schema = {
|
||||
},
|
||||
localStoragePath: {
|
||||
format: String,
|
||||
default: path.join(UserSettings.getUserN8nFolderPath(), 'binaryData'),
|
||||
default: path.join(Container.get(InstanceSettings).n8nFolder, 'binaryData'),
|
||||
env: 'N8N_BINARY_DATA_STORAGE_PATH',
|
||||
doc: 'Path for binary data storage in "filesystem" mode',
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve, join, dirname } from 'path';
|
||||
import { Container } from 'typedi';
|
||||
import type { n8n } from 'n8n-core';
|
||||
import { RESPONSE_ERROR_MESSAGES as CORE_RESPONSE_ERROR_MESSAGES, UserSettings } from 'n8n-core';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
|
||||
const { NODE_ENV, E2E_TESTS } = process.env;
|
||||
@@ -16,7 +17,10 @@ export const CUSTOM_API_CALL_KEY = '__CUSTOM_API_CALL__';
|
||||
export const CLI_DIR = resolve(__dirname, '..');
|
||||
export const TEMPLATES_DIR = join(CLI_DIR, 'templates');
|
||||
export const NODES_BASE_DIR = dirname(require.resolve('n8n-nodes-base'));
|
||||
export const GENERATED_STATIC_DIR = join(UserSettings.getUserHome(), '.cache/n8n/public');
|
||||
export const GENERATED_STATIC_DIR = join(
|
||||
Container.get(InstanceSettings).userHome,
|
||||
'.cache/n8n/public',
|
||||
);
|
||||
export const EDITOR_UI_DIST_DIR = join(dirname(require.resolve('n8n-editor-ui')), 'dist');
|
||||
|
||||
export function getN8nPackageJson() {
|
||||
@@ -34,7 +38,6 @@ export const STARTER_TEMPLATE_NAME = `${NODE_PACKAGE_PREFIX}starter`;
|
||||
export const RESPONSE_ERROR_MESSAGES = {
|
||||
NO_CREDENTIAL: 'Credential not found',
|
||||
NO_NODE: 'Node not found',
|
||||
NO_ENCRYPTION_KEY: CORE_RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY,
|
||||
PACKAGE_NAME_NOT_PROVIDED: 'Package name is required',
|
||||
PACKAGE_NAME_NOT_VALID: `Package name is not valid - it must start with "${NODE_PACKAGE_PREFIX}"`,
|
||||
PACKAGE_NOT_INSTALLED: 'This package is not installed - you must install it first',
|
||||
|
||||
@@ -12,9 +12,7 @@ import { LICENSE_FEATURES, inE2ETests } from '@/constants';
|
||||
import { NoAuthRequired, Patch, Post, RestController } from '@/decorators';
|
||||
import type { UserSetupPayload } from '@/requests';
|
||||
import type { BooleanLicenseFeature } from '@/Interfaces';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { MfaService } from '@/Mfa/mfa.service';
|
||||
import { TOTPService } from '@/Mfa/totp.service';
|
||||
|
||||
if (!inE2ETests) {
|
||||
console.error('E2E endpoints only allowed during E2E tests');
|
||||
@@ -77,6 +75,7 @@ export class E2EController {
|
||||
private settingsRepo: SettingsRepository,
|
||||
private userRepo: UserRepository,
|
||||
private workflowRunner: ActiveWorkflowRunner,
|
||||
private mfaService: MfaService,
|
||||
) {
|
||||
license.isFeatureEnabled = (feature: BooleanLicenseFeature) =>
|
||||
this.enabledFeatures[feature] ?? false;
|
||||
@@ -141,10 +140,6 @@ export class E2EController {
|
||||
roles.map(([name, scope], index) => ({ name, scope, id: (index + 1).toString() })),
|
||||
);
|
||||
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
|
||||
const mfaService = new MfaService(this.userRepo, new TOTPService(), encryptionKey);
|
||||
|
||||
const instanceOwner = {
|
||||
id: uuid(),
|
||||
...owner,
|
||||
@@ -153,10 +148,8 @@ export class E2EController {
|
||||
};
|
||||
|
||||
if (owner?.mfaSecret && owner.mfaRecoveryCodes?.length) {
|
||||
const { encryptedRecoveryCodes, encryptedSecret } = mfaService.encryptSecretAndRecoveryCodes(
|
||||
owner.mfaSecret,
|
||||
owner.mfaRecoveryCodes,
|
||||
);
|
||||
const { encryptedRecoveryCodes, encryptedSecret } =
|
||||
this.mfaService.encryptSecretAndRecoveryCodes(owner.mfaSecret, owner.mfaRecoveryCodes);
|
||||
instanceOwner.mfaSecret = encryptedSecret;
|
||||
instanceOwner.mfaRecoveryCodes = encryptedRecoveryCodes;
|
||||
}
|
||||
|
||||
@@ -61,11 +61,7 @@ EECredentialsController.get(
|
||||
|
||||
const { data: _, ...rest } = credential;
|
||||
|
||||
const key = await EECredentials.getEncryptionKey();
|
||||
const decryptedData = EECredentials.redact(
|
||||
await EECredentials.decrypt(key, credential),
|
||||
credential,
|
||||
);
|
||||
const decryptedData = EECredentials.redact(EECredentials.decrypt(credential), credential);
|
||||
|
||||
return { data: decryptedData, ...rest };
|
||||
}),
|
||||
@@ -81,8 +77,6 @@ EECredentialsController.post(
|
||||
ResponseHelper.send(async (req: CredentialRequest.Test): Promise<INodeCredentialTestResult> => {
|
||||
const { credentials } = req.body;
|
||||
|
||||
const encryptionKey = await EECredentials.getEncryptionKey();
|
||||
|
||||
const credentialId = credentials.id;
|
||||
const { ownsCredential } = await EECredentials.isOwned(req.user, credentialId);
|
||||
|
||||
@@ -92,17 +86,17 @@ EECredentialsController.post(
|
||||
throw new ResponseHelper.UnauthorizedError('Forbidden');
|
||||
}
|
||||
|
||||
const decryptedData = await EECredentials.decrypt(encryptionKey, sharing.credentials);
|
||||
const decryptedData = EECredentials.decrypt(sharing.credentials);
|
||||
Object.assign(credentials, { data: decryptedData });
|
||||
}
|
||||
|
||||
const mergedCredentials = deepCopy(credentials);
|
||||
if (mergedCredentials.data && sharing?.credentials) {
|
||||
const decryptedData = await EECredentials.decrypt(encryptionKey, sharing.credentials);
|
||||
const decryptedData = EECredentials.decrypt(sharing.credentials);
|
||||
mergedCredentials.data = EECredentials.unredact(mergedCredentials.data, decryptedData);
|
||||
}
|
||||
|
||||
return EECredentials.test(req.user, encryptionKey, mergedCredentials);
|
||||
return EECredentials.test(req.user, mergedCredentials);
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -86,9 +86,8 @@ credentialsController.get(
|
||||
return { ...rest };
|
||||
}
|
||||
|
||||
const key = await CredentialsService.getEncryptionKey();
|
||||
const decryptedData = CredentialsService.redact(
|
||||
await CredentialsService.decrypt(key, credential),
|
||||
CredentialsService.decrypt(credential),
|
||||
credential,
|
||||
);
|
||||
|
||||
@@ -106,16 +105,15 @@ credentialsController.post(
|
||||
ResponseHelper.send(async (req: CredentialRequest.Test): Promise<INodeCredentialTestResult> => {
|
||||
const { credentials } = req.body;
|
||||
|
||||
const encryptionKey = await CredentialsService.getEncryptionKey();
|
||||
const sharing = await CredentialsService.getSharing(req.user, credentials.id);
|
||||
|
||||
const mergedCredentials = deepCopy(credentials);
|
||||
if (mergedCredentials.data && sharing?.credentials) {
|
||||
const decryptedData = await CredentialsService.decrypt(encryptionKey, sharing.credentials);
|
||||
const decryptedData = CredentialsService.decrypt(sharing.credentials);
|
||||
mergedCredentials.data = CredentialsService.unredact(mergedCredentials.data, decryptedData);
|
||||
}
|
||||
|
||||
return CredentialsService.test(req.user, encryptionKey, mergedCredentials);
|
||||
return CredentialsService.test(req.user, mergedCredentials);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -127,8 +125,7 @@ credentialsController.post(
|
||||
ResponseHelper.send(async (req: CredentialRequest.Create) => {
|
||||
const newCredential = await CredentialsService.prepareCreateData(req.body);
|
||||
|
||||
const key = await CredentialsService.getEncryptionKey();
|
||||
const encryptedData = CredentialsService.createEncryptedData(key, null, newCredential);
|
||||
const encryptedData = CredentialsService.createEncryptedData(null, newCredential);
|
||||
const credential = await CredentialsService.save(newCredential, encryptedData, req.user);
|
||||
|
||||
void Container.get(InternalHooks).onUserCreatedCredentials({
|
||||
@@ -165,14 +162,12 @@ credentialsController.patch(
|
||||
|
||||
const { credentials: credential } = sharing;
|
||||
|
||||
const key = await CredentialsService.getEncryptionKey();
|
||||
const decryptedData = await CredentialsService.decrypt(key, credential);
|
||||
const decryptedData = CredentialsService.decrypt(credential);
|
||||
const preparedCredentialData = await CredentialsService.prepareUpdateData(
|
||||
req.body,
|
||||
decryptedData,
|
||||
);
|
||||
const newCredentialData = CredentialsService.createEncryptedData(
|
||||
key,
|
||||
credentialId,
|
||||
preparedCredentialData,
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Credentials, UserSettings } from 'n8n-core';
|
||||
import { Credentials } from 'n8n-core';
|
||||
import type {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialsDecrypted,
|
||||
@@ -12,10 +12,9 @@ import type { FindManyOptions, FindOptionsWhere } from 'typeorm';
|
||||
import { In, Like } from 'typeorm';
|
||||
|
||||
import * as Db from '@/Db';
|
||||
import * as ResponseHelper from '@/ResponseHelper';
|
||||
import type { ICredentialsDb } from '@/Interfaces';
|
||||
import { CredentialsHelper, createCredentialsFromCredentialsEntity } from '@/CredentialsHelper';
|
||||
import { CREDENTIAL_BLANKING_VALUE, RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||
import { CREDENTIAL_BLANKING_VALUE } from '@/constants';
|
||||
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||
import { SharedCredentials } from '@db/entities/SharedCredentials';
|
||||
import { validateEntity } from '@/GenericHelpers';
|
||||
@@ -205,18 +204,14 @@ export class CredentialsService {
|
||||
return updateData;
|
||||
}
|
||||
|
||||
static createEncryptedData(
|
||||
encryptionKey: string,
|
||||
credentialId: string | null,
|
||||
data: CredentialsEntity,
|
||||
): ICredentialsDb {
|
||||
static createEncryptedData(credentialId: string | null, data: CredentialsEntity): ICredentialsDb {
|
||||
const credentials = new Credentials(
|
||||
{ id: credentialId, name: data.name },
|
||||
data.type,
|
||||
data.nodesAccess,
|
||||
);
|
||||
|
||||
credentials.setData(data.data as unknown as ICredentialDataDecryptedObject, encryptionKey);
|
||||
credentials.setData(data.data as unknown as ICredentialDataDecryptedObject);
|
||||
|
||||
const newCredentialData = credentials.getDataToSave() as ICredentialsDb;
|
||||
|
||||
@@ -226,22 +221,9 @@ export class CredentialsService {
|
||||
return newCredentialData;
|
||||
}
|
||||
|
||||
static async getEncryptionKey(): Promise<string> {
|
||||
try {
|
||||
return await UserSettings.getEncryptionKey();
|
||||
} catch (error) {
|
||||
throw new ResponseHelper.InternalServerError(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
static async decrypt(
|
||||
encryptionKey: string,
|
||||
credential: CredentialsEntity,
|
||||
): Promise<ICredentialDataDecryptedObject> {
|
||||
static decrypt(credential: CredentialsEntity): ICredentialDataDecryptedObject {
|
||||
const coreCredential = createCredentialsFromCredentialsEntity(credential);
|
||||
const data = coreCredential.getData(encryptionKey);
|
||||
|
||||
return data;
|
||||
return coreCredential.getData();
|
||||
}
|
||||
|
||||
static async update(
|
||||
@@ -303,11 +285,9 @@ export class CredentialsService {
|
||||
|
||||
static async test(
|
||||
user: User,
|
||||
encryptionKey: string,
|
||||
credentials: ICredentialsDecrypted,
|
||||
): Promise<INodeCredentialTestResult> {
|
||||
const helper = new CredentialsHelper(encryptionKey);
|
||||
|
||||
const helper = Container.get(CredentialsHelper);
|
||||
return helper.testCredentials(user, credentials.type, credentials);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,8 @@ import omit from 'lodash/omit';
|
||||
import set from 'lodash/set';
|
||||
import split from 'lodash/split';
|
||||
import unset from 'lodash/unset';
|
||||
import { Credentials, UserSettings } from 'n8n-core';
|
||||
import type {
|
||||
WorkflowExecuteMode,
|
||||
INodeCredentialsDetails,
|
||||
ICredentialsEncrypted,
|
||||
} from 'n8n-workflow';
|
||||
import { Credentials } from 'n8n-core';
|
||||
import type { WorkflowExecuteMode, INodeCredentialsDetails } from 'n8n-workflow';
|
||||
import { LoggerProxy, jsonStringify } from 'n8n-workflow';
|
||||
import { resolve as pathResolve } from 'path';
|
||||
|
||||
@@ -76,20 +72,13 @@ oauth2CredentialController.get(
|
||||
throw new ResponseHelper.NotFoundError(RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL);
|
||||
}
|
||||
|
||||
let encryptionKey: string;
|
||||
try {
|
||||
encryptionKey = await UserSettings.getEncryptionKey();
|
||||
} catch (error) {
|
||||
throw new ResponseHelper.InternalServerError((error as Error).message);
|
||||
}
|
||||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(req.user.id);
|
||||
|
||||
const credentialType = (credential as unknown as ICredentialsEncrypted).type;
|
||||
const credentialType = credential.type;
|
||||
|
||||
const mode: WorkflowExecuteMode = 'internal';
|
||||
const timezone = config.getEnv('generic.timezone');
|
||||
const credentialsHelper = new CredentialsHelper(encryptionKey);
|
||||
const credentialsHelper = Container.get(CredentialsHelper);
|
||||
const decryptedDataOriginal = await credentialsHelper.getDecrypted(
|
||||
additionalData,
|
||||
credential as INodeCredentialsDetails,
|
||||
@@ -152,7 +141,7 @@ oauth2CredentialController.get(
|
||||
const credentials = new Credentials(
|
||||
credential as INodeCredentialsDetails,
|
||||
credentialType,
|
||||
(credential as unknown as ICredentialsEncrypted).nodesAccess,
|
||||
credential.nodesAccess,
|
||||
);
|
||||
decryptedDataOriginal.csrfSecret = csrfSecret;
|
||||
|
||||
@@ -166,7 +155,7 @@ oauth2CredentialController.get(
|
||||
decryptedDataOriginal.codeVerifier = code_verifier;
|
||||
}
|
||||
|
||||
credentials.setData(decryptedDataOriginal, encryptionKey);
|
||||
credentials.setData(decryptedDataOriginal);
|
||||
const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb;
|
||||
|
||||
// Add special database related data
|
||||
@@ -228,16 +217,15 @@ oauth2CredentialController.get(
|
||||
return renderCallbackError(res, errorMessage);
|
||||
}
|
||||
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(state.cid);
|
||||
|
||||
const mode: WorkflowExecuteMode = 'internal';
|
||||
const timezone = config.getEnv('generic.timezone');
|
||||
const credentialsHelper = new CredentialsHelper(encryptionKey);
|
||||
const credentialsHelper = Container.get(CredentialsHelper);
|
||||
const decryptedDataOriginal = await credentialsHelper.getDecrypted(
|
||||
additionalData,
|
||||
credential as INodeCredentialsDetails,
|
||||
(credential as unknown as ICredentialsEncrypted).type,
|
||||
credential.type,
|
||||
mode,
|
||||
timezone,
|
||||
true,
|
||||
@@ -245,7 +233,7 @@ oauth2CredentialController.get(
|
||||
const oauthCredentials = credentialsHelper.applyDefaultsAndOverwrites(
|
||||
additionalData,
|
||||
decryptedDataOriginal,
|
||||
(credential as unknown as ICredentialsEncrypted).type,
|
||||
credential.type,
|
||||
mode,
|
||||
timezone,
|
||||
);
|
||||
@@ -330,10 +318,10 @@ oauth2CredentialController.get(
|
||||
|
||||
const credentials = new Credentials(
|
||||
credential as INodeCredentialsDetails,
|
||||
(credential as unknown as ICredentialsEncrypted).type,
|
||||
(credential as unknown as ICredentialsEncrypted).nodesAccess,
|
||||
credential.type,
|
||||
credential.nodesAccess,
|
||||
);
|
||||
credentials.setData(decryptedDataOriginal, encryptionKey);
|
||||
credentials.setData(decryptedDataOriginal);
|
||||
const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb;
|
||||
// Add special database related data
|
||||
newCredentialsData.updatedAt = new Date();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import path from 'path';
|
||||
import { Container } from 'typedi';
|
||||
import type { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions';
|
||||
import type { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
|
||||
import type { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
|
||||
import { entities } from './entities';
|
||||
import { mysqlMigrations } from './migrations/mysqldb';
|
||||
@@ -21,7 +22,7 @@ const getDBConnectionOptions = (dbType: DatabaseType) => {
|
||||
configDBType === 'sqlite'
|
||||
? {
|
||||
database: path.resolve(
|
||||
UserSettings.getUserN8nFolderPath(),
|
||||
Container.get(InstanceSettings).n8nFolder,
|
||||
config.getEnv('database.sqlite.database'),
|
||||
),
|
||||
enableWAL: config.getEnv('database.sqlite.enableWAL'),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { statSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { Container } from 'typedi';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import type { MigrationContext, IrreversibleMigration } from '@db/types';
|
||||
import config from '@/config';
|
||||
|
||||
@@ -191,7 +192,7 @@ const migrationsPruningEnabled = process.env.MIGRATIONS_PRUNING_ENABLED === 'tru
|
||||
|
||||
function getSqliteDbFileSize(): number {
|
||||
const filename = path.resolve(
|
||||
UserSettings.getUserN8nFolderPath(),
|
||||
Container.get(InstanceSettings).n8nFolder,
|
||||
config.getEnv('database.sqlite.database'),
|
||||
);
|
||||
const { size } = statSync(filename);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Container } from 'typedi';
|
||||
import { readFileSync, rmSync } from 'fs';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import type { ObjectLiteral } from 'typeorm';
|
||||
import type { QueryRunner } from 'typeorm/query-runner/QueryRunner';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
@@ -16,9 +16,10 @@ const logger = getLogger();
|
||||
const PERSONALIZATION_SURVEY_FILENAME = 'personalizationSurvey.json';
|
||||
|
||||
function loadSurveyFromDisk(): string | null {
|
||||
const userSettingsPath = UserSettings.getUserN8nFolderPath();
|
||||
try {
|
||||
const filename = `${userSettingsPath}/${PERSONALIZATION_SURVEY_FILENAME}`;
|
||||
const filename = `${
|
||||
Container.get(InstanceSettings).n8nFolder
|
||||
}/${PERSONALIZATION_SURVEY_FILENAME}`;
|
||||
const surveyFile = readFileSync(filename, 'utf-8');
|
||||
rmSync(filename);
|
||||
const personalizationSurvey = JSON.parse(surveyFile) as object;
|
||||
|
||||
@@ -12,14 +12,10 @@ import type { SourceControlPreferences } from './types/sourceControlPreferences'
|
||||
import {
|
||||
SOURCE_CONTROL_DEFAULT_EMAIL,
|
||||
SOURCE_CONTROL_DEFAULT_NAME,
|
||||
SOURCE_CONTROL_GIT_FOLDER,
|
||||
SOURCE_CONTROL_README,
|
||||
SOURCE_CONTROL_SSH_FOLDER,
|
||||
SOURCE_CONTROL_SSH_KEY_NAME,
|
||||
} from './constants';
|
||||
import { LoggerProxy } from 'n8n-workflow';
|
||||
import { SourceControlGitService } from './sourceControlGit.service.ee';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import type { PushResult } from 'simple-git';
|
||||
import { SourceControlExportService } from './sourceControlExport.service.ee';
|
||||
import { BadRequestError } from '@/ResponseHelper';
|
||||
@@ -55,10 +51,10 @@ export class SourceControlService {
|
||||
private sourceControlImportService: SourceControlImportService,
|
||||
private tagRepository: TagRepository,
|
||||
) {
|
||||
const userFolder = UserSettings.getUserN8nFolderPath();
|
||||
this.sshFolder = path.join(userFolder, SOURCE_CONTROL_SSH_FOLDER);
|
||||
this.gitFolder = path.join(userFolder, SOURCE_CONTROL_GIT_FOLDER);
|
||||
this.sshKeyName = path.join(this.sshFolder, SOURCE_CONTROL_SSH_KEY_NAME);
|
||||
const { gitFolder, sshFolder, sshKeyName } = sourceControlPreferencesService;
|
||||
this.gitFolder = gitFolder;
|
||||
this.sshFolder = sshFolder;
|
||||
this.sshKeyName = sshKeyName;
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { ICredentialDataDecryptedObject } from 'n8n-workflow';
|
||||
import { LoggerProxy } from 'n8n-workflow';
|
||||
import { writeFile as fsWriteFile, rm as fsRm } from 'fs/promises';
|
||||
import { rmSync } from 'fs';
|
||||
import { Credentials, UserSettings } from 'n8n-core';
|
||||
import { Credentials, InstanceSettings } from 'n8n-core';
|
||||
import type { ExportableWorkflow } from './types/exportableWorkflow';
|
||||
import type { ExportableCredential } from './types/exportableCredential';
|
||||
import type { ExportResult } from './types/exportResult';
|
||||
@@ -39,9 +39,9 @@ export class SourceControlExportService {
|
||||
constructor(
|
||||
private readonly variablesService: VariablesService,
|
||||
private readonly tagRepository: TagRepository,
|
||||
instanceSettings: InstanceSettings,
|
||||
) {
|
||||
const userFolder = UserSettings.getUserN8nFolderPath();
|
||||
this.gitFolder = path.join(userFolder, SOURCE_CONTROL_GIT_FOLDER);
|
||||
this.gitFolder = path.join(instanceSettings.n8nFolder, SOURCE_CONTROL_GIT_FOLDER);
|
||||
this.workflowExportFolder = path.join(this.gitFolder, SOURCE_CONTROL_WORKFLOW_EXPORT_FOLDER);
|
||||
this.credentialExportFolder = path.join(
|
||||
this.gitFolder,
|
||||
@@ -248,12 +248,11 @@ export class SourceControlExportService {
|
||||
(remote) => foundCredentialIds.findIndex((local) => local === remote) === -1,
|
||||
);
|
||||
}
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
await Promise.all(
|
||||
credentialsToBeExported.map(async (sharedCredential) => {
|
||||
const { name, type, nodesAccess, data, id } = sharedCredential.credentials;
|
||||
const credentialObject = new Credentials({ id, name }, type, nodesAccess, data);
|
||||
const plainData = credentialObject.getData(encryptionKey);
|
||||
const plainData = credentialObject.getData();
|
||||
const sanitizedData = this.replaceCredentialData(plainData);
|
||||
const fileName = this.getCredentialsPath(sharedCredential.credentials.id);
|
||||
const sanitizedCredential: ExportableCredential = {
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as Db from '@/Db';
|
||||
import glob from 'fast-glob';
|
||||
import { LoggerProxy, jsonParse } from 'n8n-workflow';
|
||||
import { readFile as fsReadFile } from 'fs/promises';
|
||||
import { Credentials, UserSettings } from 'n8n-core';
|
||||
import { Credentials, InstanceSettings } from 'n8n-core';
|
||||
import type { IWorkflowToImport } from '@/Interfaces';
|
||||
import type { ExportableCredential } from './types/exportableCredential';
|
||||
import type { Variables } from '@db/entities/Variables';
|
||||
@@ -41,9 +41,9 @@ export class SourceControlImportService {
|
||||
private readonly variablesService: VariablesService,
|
||||
private readonly activeWorkflowRunner: ActiveWorkflowRunner,
|
||||
private readonly tagRepository: TagRepository,
|
||||
instanceSettings: InstanceSettings,
|
||||
) {
|
||||
const userFolder = UserSettings.getUserN8nFolderPath();
|
||||
this.gitFolder = path.join(userFolder, SOURCE_CONTROL_GIT_FOLDER);
|
||||
this.gitFolder = path.join(instanceSettings.n8nFolder, SOURCE_CONTROL_GIT_FOLDER);
|
||||
this.workflowExportFolder = path.join(this.gitFolder, SOURCE_CONTROL_WORKFLOW_EXPORT_FOLDER);
|
||||
this.credentialExportFolder = path.join(
|
||||
this.gitFolder,
|
||||
@@ -81,69 +81,6 @@ export class SourceControlImportService {
|
||||
return workflowOwnerRole;
|
||||
}
|
||||
|
||||
private async importCredentialsFromFiles(
|
||||
userId: string,
|
||||
): Promise<Array<{ id: string; name: string; type: string }>> {
|
||||
const credentialFiles = await glob('*.json', {
|
||||
cwd: this.credentialExportFolder,
|
||||
absolute: true,
|
||||
});
|
||||
const existingCredentials = await Db.collections.Credentials.find();
|
||||
const ownerCredentialRole = await this.getCredentialOwnerRole();
|
||||
const ownerGlobalRole = await this.getOwnerGlobalRole();
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
let importCredentialsResult: Array<{ id: string; name: string; type: string }> = [];
|
||||
importCredentialsResult = await Promise.all(
|
||||
credentialFiles.map(async (file) => {
|
||||
LoggerProxy.debug(`Importing credentials file ${file}`);
|
||||
const credential = jsonParse<ExportableCredential>(
|
||||
await fsReadFile(file, { encoding: 'utf8' }),
|
||||
);
|
||||
const existingCredential = existingCredentials.find(
|
||||
(e) => e.id === credential.id && e.type === credential.type,
|
||||
);
|
||||
const sharedOwner = await Db.collections.SharedCredentials.findOne({
|
||||
select: ['userId'],
|
||||
where: {
|
||||
credentialsId: credential.id,
|
||||
roleId: In([ownerCredentialRole.id, ownerGlobalRole.id]),
|
||||
},
|
||||
});
|
||||
|
||||
const { name, type, data, id, nodesAccess } = credential;
|
||||
const newCredentialObject = new Credentials({ id, name }, type, []);
|
||||
if (existingCredential?.data) {
|
||||
newCredentialObject.data = existingCredential.data;
|
||||
} else {
|
||||
newCredentialObject.setData(data, encryptionKey);
|
||||
}
|
||||
newCredentialObject.nodesAccess = nodesAccess || existingCredential?.nodesAccess || [];
|
||||
|
||||
LoggerProxy.debug(`Updating credential id ${newCredentialObject.id as string}`);
|
||||
await Db.collections.Credentials.upsert(newCredentialObject, ['id']);
|
||||
|
||||
if (!sharedOwner) {
|
||||
const newSharedCredential = new SharedCredentials();
|
||||
newSharedCredential.credentialsId = newCredentialObject.id as string;
|
||||
newSharedCredential.userId = userId;
|
||||
newSharedCredential.roleId = ownerGlobalRole.id;
|
||||
|
||||
await Db.collections.SharedCredentials.upsert({ ...newSharedCredential }, [
|
||||
'credentialsId',
|
||||
'userId',
|
||||
]);
|
||||
}
|
||||
|
||||
return {
|
||||
id: newCredentialObject.id as string,
|
||||
name: newCredentialObject.name,
|
||||
type: newCredentialObject.type,
|
||||
};
|
||||
}),
|
||||
);
|
||||
return importCredentialsResult.filter((e) => e !== undefined);
|
||||
}
|
||||
|
||||
public async getRemoteVersionIdsFromFiles(): Promise<SourceControlWorkflowVersionId[]> {
|
||||
const remoteWorkflowFiles = await glob('*.json', {
|
||||
cwd: this.workflowExportFolder,
|
||||
@@ -407,7 +344,6 @@ export class SourceControlImportService {
|
||||
roleId: In([ownerCredentialRole.id, ownerGlobalRole.id]),
|
||||
},
|
||||
});
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
let importCredentialsResult: Array<{ id: string; name: string; type: string }> = [];
|
||||
importCredentialsResult = await Promise.all(
|
||||
candidates.map(async (candidate) => {
|
||||
@@ -427,7 +363,7 @@ export class SourceControlImportService {
|
||||
if (existingCredential?.data) {
|
||||
newCredentialObject.data = existingCredential.data;
|
||||
} else {
|
||||
newCredentialObject.setData(data, encryptionKey);
|
||||
newCredentialObject.setData(data);
|
||||
}
|
||||
newCredentialObject.nodesAccess = nodesAccess || existingCredential?.nodesAccess || [];
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
isSourceControlLicensed,
|
||||
sourceControlFoldersExistCheck,
|
||||
} from './sourceControlHelper.ee';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import { LoggerProxy, jsonParse } from 'n8n-workflow';
|
||||
import * as Db from '@/Db';
|
||||
import {
|
||||
@@ -26,16 +26,15 @@ import config from '@/config';
|
||||
export class SourceControlPreferencesService {
|
||||
private _sourceControlPreferences: SourceControlPreferences = new SourceControlPreferences();
|
||||
|
||||
private sshKeyName: string;
|
||||
readonly sshKeyName: string;
|
||||
|
||||
private sshFolder: string;
|
||||
readonly sshFolder: string;
|
||||
|
||||
private gitFolder: string;
|
||||
readonly gitFolder: string;
|
||||
|
||||
constructor() {
|
||||
const userFolder = UserSettings.getUserN8nFolderPath();
|
||||
this.sshFolder = path.join(userFolder, SOURCE_CONTROL_SSH_FOLDER);
|
||||
this.gitFolder = path.join(userFolder, SOURCE_CONTROL_GIT_FOLDER);
|
||||
constructor(instanceSettings: InstanceSettings) {
|
||||
this.sshFolder = path.join(instanceSettings.n8nFolder, SOURCE_CONTROL_SSH_FOLDER);
|
||||
this.gitFolder = path.join(instanceSettings.n8nFolder, SOURCE_CONTROL_GIT_FOLDER);
|
||||
this.sshKeyName = path.join(this.sshFolder, SOURCE_CONTROL_SSH_KEY_NAME);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import type {
|
||||
IWorkflowExecuteAdditionalData,
|
||||
} from 'n8n-workflow';
|
||||
import { CredentialsHelper } from '@/CredentialsHelper';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { Agent as HTTPSAgent } from 'https';
|
||||
import config from '@/config';
|
||||
import { isLogStreamingEnabled } from '../MessageEventBus/MessageEventBusHelper';
|
||||
@@ -26,6 +25,7 @@ import { eventMessageGenericDestinationTestEvent } from '../EventMessageClasses/
|
||||
import { MessageEventBus } from '../MessageEventBus/MessageEventBus';
|
||||
import type { MessageWithCallback } from '../MessageEventBus/MessageEventBus';
|
||||
import * as SecretsHelpers from '@/ExternalSecrets/externalSecretsHelper.ee';
|
||||
import Container from 'typedi';
|
||||
|
||||
export const isMessageEventBusDestinationWebhookOptions = (
|
||||
candidate: unknown,
|
||||
@@ -135,13 +135,7 @@ export class MessageEventBusDestinationWebhook
|
||||
} as AxiosRequestConfig;
|
||||
|
||||
if (this.credentialsHelper === undefined) {
|
||||
let encryptionKey: string | undefined;
|
||||
try {
|
||||
encryptionKey = await UserSettings.getEncryptionKey();
|
||||
} catch {}
|
||||
if (encryptionKey) {
|
||||
this.credentialsHelper = new CredentialsHelper(encryptionKey);
|
||||
}
|
||||
this.credentialsHelper = Container.get(CredentialsHelper);
|
||||
}
|
||||
|
||||
const sendQuery = this.sendQuery;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
|
||||
import { isEventMessageOptions } from '../EventMessageClasses/AbstractEventMessage';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import path, { parse } from 'path';
|
||||
import { Worker } from 'worker_threads';
|
||||
import { createReadStream, existsSync, rmSync } from 'fs';
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from '../EventMessageClasses/EventMessageConfirm';
|
||||
import { once as eventOnce } from 'events';
|
||||
import { inTest } from '@/constants';
|
||||
import Container from 'typedi';
|
||||
|
||||
interface MessageEventBusLogWriterConstructorOptions {
|
||||
logBaseName?: string;
|
||||
@@ -66,7 +67,7 @@ export class MessageEventBusLogWriter {
|
||||
MessageEventBusLogWriter.instance = new MessageEventBusLogWriter();
|
||||
MessageEventBusLogWriter.options = {
|
||||
logFullBasePath: path.join(
|
||||
options?.logBasePath ?? UserSettings.getUserN8nFolderPath(),
|
||||
options?.logBasePath ?? Container.get(InstanceSettings).n8nFolder,
|
||||
options?.logBaseName ?? config.getEnv('eventBus.logWriter.logBaseName'),
|
||||
),
|
||||
keepNumberOfFiles:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Service } from 'typedi';
|
||||
import type { PostHog } from 'posthog-node';
|
||||
import type { FeatureFlags, ITelemetryTrackProperties } from 'n8n-workflow';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import config from '@/config';
|
||||
import type { PublicUser } from '@/Interfaces';
|
||||
|
||||
@@ -8,10 +9,9 @@ import type { PublicUser } from '@/Interfaces';
|
||||
export class PostHogClient {
|
||||
private postHog?: PostHog;
|
||||
|
||||
private instanceId?: string;
|
||||
constructor(private readonly instanceSettings: InstanceSettings) {}
|
||||
|
||||
async init(instanceId: string) {
|
||||
this.instanceId = instanceId;
|
||||
async init() {
|
||||
const enabled = config.getEnv('diagnostics.enabled');
|
||||
if (!enabled) {
|
||||
return;
|
||||
@@ -46,7 +46,7 @@ export class PostHogClient {
|
||||
async getFeatureFlags(user: Pick<PublicUser, 'id' | 'createdAt'>): Promise<FeatureFlags> {
|
||||
if (!this.postHog) return {};
|
||||
|
||||
const fullId = [this.instanceId, user.id].join('#');
|
||||
const fullId = [this.instanceSettings.instanceId, user.id].join('#');
|
||||
|
||||
// cannot use local evaluation because that requires PostHog personal api key with org-wide
|
||||
// https://github.com/PostHog/posthog/issues/4849
|
||||
|
||||
@@ -6,7 +6,7 @@ import axios from 'axios';
|
||||
|
||||
import { LoggerProxy as Logger } from 'n8n-workflow';
|
||||
import type { PublicInstalledPackage } from 'n8n-workflow';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import type { PackageDirectoryLoader } from 'n8n-core';
|
||||
|
||||
import { toError } from '@/utils';
|
||||
@@ -47,6 +47,7 @@ export class CommunityPackagesService {
|
||||
missingPackages: string[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly instanceSettings: InstanceSettings,
|
||||
private readonly installedPackageRepository: InstalledPackagesRepository,
|
||||
private readonly loadNodesAndCredentials: LoadNodesAndCredentials,
|
||||
) {}
|
||||
@@ -114,7 +115,7 @@ export class CommunityPackagesService {
|
||||
}
|
||||
|
||||
async executeNpmCommand(command: string, options?: { doNotHandleError?: boolean }) {
|
||||
const downloadFolder = UserSettings.getUserN8nFolderDownloadedNodesPath();
|
||||
const downloadFolder = this.instanceSettings.nodesDownloadDir;
|
||||
|
||||
const execOptions = {
|
||||
cwd: downloadFolder,
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
INodeTypeBaseDescription,
|
||||
ITelemetrySettings,
|
||||
} from 'n8n-workflow';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
|
||||
import { GENERATED_STATIC_DIR, LICENSE_FEATURES } from '@/constants';
|
||||
import { CredentialsOverwrites } from '@/CredentialsOverwrites';
|
||||
@@ -40,6 +41,7 @@ export class FrontendService {
|
||||
private readonly credentialsOverwrites: CredentialsOverwrites,
|
||||
private readonly license: License,
|
||||
private readonly mailer: UserManagementMailer,
|
||||
private readonly instanceSettings: InstanceSettings,
|
||||
) {
|
||||
this.initSettings();
|
||||
}
|
||||
@@ -87,7 +89,7 @@ export class FrontendService {
|
||||
endpoint: config.getEnv('versionNotifications.endpoint'),
|
||||
infoUrl: config.getEnv('versionNotifications.infoUrl'),
|
||||
},
|
||||
instanceId: '',
|
||||
instanceId: this.instanceSettings.instanceId,
|
||||
telemetry: telemetrySettings,
|
||||
posthog: {
|
||||
enabled: config.getEnv('diagnostics.enabled'),
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { RedisServicePubSubPublisher } from '../../redis/RedisServicePubSub
|
||||
|
||||
export interface WorkerCommandReceivedHandlerOptions {
|
||||
queueModeId: string;
|
||||
instanceId: string;
|
||||
redisPublisher: RedisServicePubSubPublisher;
|
||||
getRunningJobIds: () => string[];
|
||||
getRunningJobsSummary: () => WorkerJobStatusSummary[];
|
||||
|
||||
@@ -10,6 +10,7 @@ import { LicenseService } from '@/license/License.service';
|
||||
import { N8N_VERSION } from '@/constants';
|
||||
import Container, { Service } from 'typedi';
|
||||
import { SourceControlPreferencesService } from '../environments/sourceControl/sourceControlPreferences.service.ee';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
|
||||
type ExecutionTrackDataKey = 'manual_error' | 'manual_success' | 'prod_error' | 'prod_success';
|
||||
|
||||
@@ -30,8 +31,6 @@ interface IExecutionsBuffer {
|
||||
|
||||
@Service()
|
||||
export class Telemetry {
|
||||
private instanceId: string;
|
||||
|
||||
private rudderStack?: RudderStack;
|
||||
|
||||
private pulseIntervalReference: NodeJS.Timeout;
|
||||
@@ -41,12 +40,9 @@ export class Telemetry {
|
||||
constructor(
|
||||
private postHog: PostHogClient,
|
||||
private license: License,
|
||||
private readonly instanceSettings: InstanceSettings,
|
||||
) {}
|
||||
|
||||
setInstanceId(instanceId: string) {
|
||||
this.instanceId = instanceId;
|
||||
}
|
||||
|
||||
async init() {
|
||||
const enabled = config.getEnv('diagnostics.enabled');
|
||||
if (enabled) {
|
||||
@@ -172,15 +168,13 @@ export class Telemetry {
|
||||
async identify(traits?: {
|
||||
[key: string]: string | number | boolean | object | undefined | null;
|
||||
}): Promise<void> {
|
||||
const { instanceId } = this.instanceSettings;
|
||||
return new Promise<void>((resolve) => {
|
||||
if (this.rudderStack) {
|
||||
this.rudderStack.identify(
|
||||
{
|
||||
userId: this.instanceId,
|
||||
traits: {
|
||||
...traits,
|
||||
instanceId: this.instanceId,
|
||||
},
|
||||
userId: instanceId,
|
||||
traits: { ...traits, instanceId },
|
||||
},
|
||||
resolve,
|
||||
);
|
||||
@@ -195,17 +189,18 @@ export class Telemetry {
|
||||
properties: ITelemetryTrackProperties = {},
|
||||
{ withPostHog } = { withPostHog: false }, // whether to additionally track with PostHog
|
||||
): Promise<void> {
|
||||
const { instanceId } = this.instanceSettings;
|
||||
return new Promise<void>((resolve) => {
|
||||
if (this.rudderStack) {
|
||||
const { user_id } = properties;
|
||||
const updatedProperties: ITelemetryTrackProperties = {
|
||||
...properties,
|
||||
instance_id: this.instanceId,
|
||||
instance_id: instanceId,
|
||||
version_cli: N8N_VERSION,
|
||||
};
|
||||
|
||||
const payload = {
|
||||
userId: `${this.instanceId}${user_id ? `#${user_id}` : ''}`,
|
||||
userId: `${instanceId}${user_id ? `#${user_id}` : ''}`,
|
||||
event: eventName,
|
||||
properties: updatedProperties,
|
||||
};
|
||||
|
||||
@@ -3,10 +3,9 @@ import { License } from '@/License';
|
||||
import * as testDb from '../shared/testDb';
|
||||
import * as utils from '../shared/utils/';
|
||||
import type { ExternalSecretsSettings, SecretsProviderState } from '@/Interfaces';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { Cipher } from 'n8n-core';
|
||||
import { SettingsRepository } from '@/databases/repositories/settings.repository';
|
||||
import Container from 'typedi';
|
||||
import { AES, enc } from 'crypto-js';
|
||||
import { ExternalSecretsProviders } from '@/ExternalSecrets/ExternalSecretsProviders.ee';
|
||||
import {
|
||||
DummyProvider,
|
||||
@@ -17,7 +16,7 @@ import {
|
||||
import config from '@/config';
|
||||
import { ExternalSecretsManager } from '@/ExternalSecrets/ExternalSecretsManager.ee';
|
||||
import { CREDENTIAL_BLANKING_VALUE } from '@/constants';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
import { jsonParse, type IDataObject } from 'n8n-workflow';
|
||||
|
||||
let authOwnerAgent: SuperAgentTest;
|
||||
let authMemberAgent: SuperAgentTest;
|
||||
@@ -28,29 +27,24 @@ const licenseLike = utils.mockInstance(License, {
|
||||
});
|
||||
|
||||
const mockProvidersInstance = new MockProviders();
|
||||
let providersMock: ExternalSecretsProviders = utils.mockInstance(
|
||||
ExternalSecretsProviders,
|
||||
mockProvidersInstance,
|
||||
);
|
||||
utils.mockInstance(ExternalSecretsProviders, mockProvidersInstance);
|
||||
|
||||
const testServer = utils.setupTestServer({ endpointGroups: ['externalSecrets'] });
|
||||
|
||||
const connectedDate = '2023-08-01T12:32:29.000Z';
|
||||
|
||||
async function setExternalSecretsSettings(settings: ExternalSecretsSettings) {
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
return Container.get(SettingsRepository).saveEncryptedSecretsProviderSettings(
|
||||
AES.encrypt(JSON.stringify(settings), encryptionKey).toString(),
|
||||
Container.get(Cipher).encrypt(settings),
|
||||
);
|
||||
}
|
||||
|
||||
async function getExternalSecretsSettings(): Promise<ExternalSecretsSettings | null> {
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
const encSettings = await Container.get(SettingsRepository).getEncryptedSecretsProviderSettings();
|
||||
if (encSettings === null) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(AES.decrypt(encSettings, encryptionKey).toString(enc.Utf8));
|
||||
return jsonParse(Container.get(Cipher).decrypt(encSettings));
|
||||
}
|
||||
|
||||
const resetManager = async () => {
|
||||
@@ -61,6 +55,7 @@ const resetManager = async () => {
|
||||
Container.get(SettingsRepository),
|
||||
licenseLike,
|
||||
mockProvidersInstance,
|
||||
Container.get(Cipher),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -100,8 +95,6 @@ const getDummyProviderData = ({
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await utils.initEncryptionKey();
|
||||
|
||||
const owner = await testDb.createOwner();
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
const member = await testDb.createUser();
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import { In } from 'typeorm';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
import * as Db from '@/Db';
|
||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||
import type { Credentials } from '@/requests';
|
||||
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
@@ -304,21 +302,6 @@ describe('GET /credentials/:id', () => {
|
||||
expect(response.body.data).toBeUndefined(); // owner's cred not returned
|
||||
});
|
||||
|
||||
test('should fail with missing encryption key', async () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
||||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
|
||||
expect(response.statusCode).toBe(500);
|
||||
|
||||
mock.mockRestore();
|
||||
});
|
||||
|
||||
test('should return 404 if cred not found', async () => {
|
||||
const response = await authOwnerAgent.get('/credentials/789');
|
||||
expect(response.statusCode).toBe(404);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
|
||||
import * as Db from '@/Db';
|
||||
import config from '@/config';
|
||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
|
||||
import type { Credentials } from '@/requests';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
@@ -130,17 +128,6 @@ describe('POST /credentials', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('should fail with missing encryption key', async () => {
|
||||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
||||
|
||||
const response = await authOwnerAgent.post('/credentials').send(randomCredentialPayload());
|
||||
|
||||
expect(response.statusCode).toBe(500);
|
||||
|
||||
mock.mockRestore();
|
||||
});
|
||||
|
||||
test('should ignore ID in payload', async () => {
|
||||
const firstResponse = await authOwnerAgent
|
||||
.post('/credentials')
|
||||
@@ -385,17 +372,6 @@ describe('PATCH /credentials/:id', () => {
|
||||
|
||||
expect(response.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('should fail with missing encryption key', async () => {
|
||||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
||||
|
||||
const response = await authOwnerAgent.post('/credentials').send(randomCredentialPayload());
|
||||
|
||||
expect(response.statusCode).toBe(500);
|
||||
|
||||
mock.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /credentials/new', () => {
|
||||
@@ -504,21 +480,6 @@ describe('GET /credentials/:id', () => {
|
||||
expect(response.body.data).toBeUndefined(); // owner's cred not returned
|
||||
});
|
||||
|
||||
test('should fail with missing encryption key', async () => {
|
||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||
|
||||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get(`/credentials/${savedCredential.id}`)
|
||||
.query({ includeData: true });
|
||||
|
||||
expect(response.statusCode).toBe(500);
|
||||
|
||||
mock.mockRestore();
|
||||
});
|
||||
|
||||
test('should return 404 if cred not found', async () => {
|
||||
const response = await authOwnerAgent.get('/credentials/789');
|
||||
expect(response.statusCode).toBe(404);
|
||||
|
||||
@@ -89,7 +89,6 @@ beforeAll(async () => {
|
||||
|
||||
mockedSyslog.createClient.mockImplementation(() => new syslog.Client());
|
||||
|
||||
await utils.initEncryptionKey();
|
||||
config.set('eventBus.logWriter.logBaseName', 'n8n-test-logwriter');
|
||||
config.set('eventBus.logWriter.keepLogCount', 1);
|
||||
|
||||
|
||||
@@ -11,13 +11,15 @@ import type { User } from '@db/entities/User';
|
||||
import { LDAP_DEFAULT_CONFIGURATION, LDAP_FEATURE_NAME } from '@/Ldap/constants';
|
||||
import { LdapManager } from '@/Ldap/LdapManager.ee';
|
||||
import { LdapService } from '@/Ldap/LdapService.ee';
|
||||
import { encryptPassword, saveLdapSynchronization } from '@/Ldap/helpers';
|
||||
import { saveLdapSynchronization } from '@/Ldap/helpers';
|
||||
import type { LdapConfig } from '@/Ldap/types';
|
||||
import { getCurrentAuthenticationMethod, setCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
|
||||
|
||||
import { randomEmail, randomName, uniqueId } from './../shared/random';
|
||||
import * as testDb from './../shared/testDb';
|
||||
import * as utils from '../shared/utils/';
|
||||
import Container from 'typedi';
|
||||
import { Cipher } from 'n8n-core';
|
||||
|
||||
jest.mock('@/telemetry');
|
||||
|
||||
@@ -54,12 +56,10 @@ beforeAll(async () => {
|
||||
owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
authOwnerAgent = testServer.authAgentFor(owner);
|
||||
|
||||
defaultLdapConfig.bindingAdminPassword = await encryptPassword(
|
||||
defaultLdapConfig.bindingAdminPassword = Container.get(Cipher).encrypt(
|
||||
defaultLdapConfig.bindingAdminPassword,
|
||||
);
|
||||
|
||||
await utils.initEncryptionKey();
|
||||
|
||||
await setCurrentAuthenticationMethod('email');
|
||||
});
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ const testServer = utils.setupTestServer({ endpointGroups: ['passwordReset'] });
|
||||
const jwtService = Container.get(JwtService);
|
||||
|
||||
beforeAll(async () => {
|
||||
await utils.initEncryptionKey();
|
||||
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
||||
globalMemberRole = await testDb.getGlobalMemberRole();
|
||||
});
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { SuperAgentTest } from 'supertest';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import * as Db from '@/Db';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||
|
||||
import { randomApiKey, randomName, randomString } from '../shared/random';
|
||||
import * as utils from '../shared/utils/';
|
||||
@@ -22,9 +20,6 @@ let saveCredential: SaveCredentialFunction;
|
||||
const testServer = utils.setupTestServer({ endpointGroups: ['publicApi'] });
|
||||
|
||||
beforeAll(async () => {
|
||||
// TODO: mock encryption key
|
||||
await utils.initEncryptionKey();
|
||||
|
||||
const [globalOwnerRole, fetchedGlobalMemberRole, _, fetchedCredentialOwnerRole] =
|
||||
await testDb.getAllRoles();
|
||||
|
||||
@@ -87,17 +82,6 @@ describe('POST /credentials', () => {
|
||||
expect(response.statusCode === 400 || response.statusCode === 415).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('should fail with missing encryption key', async () => {
|
||||
const mock = jest.spyOn(UserSettings, 'getEncryptionKey');
|
||||
mock.mockRejectedValue(new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY));
|
||||
|
||||
const response = await authOwnerAgent.post('/credentials').send(credentialPayload());
|
||||
|
||||
expect(response.statusCode).toBe(500);
|
||||
|
||||
mock.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /credentials/:id', () => {
|
||||
|
||||
@@ -35,7 +35,6 @@ beforeAll(async () => {
|
||||
apiKey: randomApiKey(),
|
||||
});
|
||||
|
||||
await utils.initEncryptionKey();
|
||||
await utils.initNodeTypes();
|
||||
workflowRunner = await utils.initActiveWorkflowRunner();
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import type { DataSourceOptions as ConnectionOptions, Repository } from 'typeorm';
|
||||
import { DataSource as Connection } from 'typeorm';
|
||||
import { Container } from 'typedi';
|
||||
@@ -213,8 +212,6 @@ export async function createLdapUser(attributes: Partial<User>, ldapId: string):
|
||||
export async function createUserWithMfaEnabled(
|
||||
data: { numberOfRecoveryCodes: number } = { numberOfRecoveryCodes: 10 },
|
||||
) {
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
|
||||
const email = randomEmail();
|
||||
const password = randomPassword();
|
||||
|
||||
@@ -222,7 +219,7 @@ export async function createUserWithMfaEnabled(
|
||||
|
||||
const secret = toptService.generateSecret();
|
||||
|
||||
const mfaService = new MfaService(Db.collections.User, toptService, encryptionKey);
|
||||
const mfaService = Container.get(MfaService);
|
||||
|
||||
const recoveryCodes = mfaService.generateRecoveryCodes(data.numberOfRecoveryCodes);
|
||||
|
||||
@@ -687,12 +684,10 @@ const getDBOptions = (type: TestDBType, name: string) => ({
|
||||
// ----------------------------------
|
||||
|
||||
async function encryptCredentialData(credential: CredentialsEntity) {
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
|
||||
const coreCredential = createCredentialsFromCredentialsEntity(credential, true);
|
||||
|
||||
// @ts-ignore
|
||||
coreCredential.setData(credential.data, encryptionKey);
|
||||
coreCredential.setData(credential.data);
|
||||
|
||||
return coreCredential.getDataToSave() as ICredentialsDb;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Container } from 'typedi';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { existsSync } from 'fs';
|
||||
import { BinaryDataService, UserSettings } from 'n8n-core';
|
||||
import { BinaryDataService } from 'n8n-core';
|
||||
import type { INode } from 'n8n-workflow';
|
||||
import { GithubApi } from 'n8n-nodes-base/credentials/GithubApi.credentials';
|
||||
import { Ftp } from 'n8n-nodes-base/credentials/Ftp.credentials';
|
||||
@@ -84,19 +82,6 @@ export async function initBinaryDataService(mode: 'default' | 'filesystem' = 'de
|
||||
Container.set(BinaryDataService, binaryDataService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a user settings config file if non-existent.
|
||||
*/
|
||||
// TODO: this should be mocked
|
||||
export async function initEncryptionKey() {
|
||||
const settingsPath = UserSettings.getUserSettingsPath();
|
||||
|
||||
if (!existsSync(settingsPath)) {
|
||||
const userSettings = { encryptionKey: randomBytes(24).toString('base64') };
|
||||
await UserSettings.writeUserSettings(userSettings, settingsPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the value (token) of the auth cookie in a response.
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,6 @@ import request from 'supertest';
|
||||
import { URL } from 'url';
|
||||
|
||||
import config from '@/config';
|
||||
import * as Db from '@/Db';
|
||||
import { ExternalHooks } from '@/ExternalHooks';
|
||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||
import { workflowsController } from '@/workflows/workflows.controller';
|
||||
@@ -50,8 +49,6 @@ import type { EndpointGroup, SetupProps, TestServer } from '../types';
|
||||
import { mockInstance } from './mocking';
|
||||
import { ExternalSecretsController } from '@/ExternalSecrets/ExternalSecrets.controller.ee';
|
||||
import { MfaService } from '@/Mfa/mfa.service';
|
||||
import { TOTPService } from '@/Mfa/totp.service';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { MetricsService } from '@/services/metrics.service';
|
||||
import {
|
||||
SettingsRepository,
|
||||
@@ -200,12 +197,10 @@ export const setupTestServer = ({
|
||||
}
|
||||
|
||||
if (functionEndpoints.length) {
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
const repositories = Db.collections;
|
||||
const externalHooks = Container.get(ExternalHooks);
|
||||
const internalHooks = Container.get(InternalHooks);
|
||||
const mailer = Container.get(UserManagementMailer);
|
||||
const mfaService = new MfaService(repositories.User, new TOTPService(), encryptionKey);
|
||||
const mfaService = Container.get(MfaService);
|
||||
const userService = Container.get(UserService);
|
||||
|
||||
for (const group of functionEndpoints) {
|
||||
|
||||
@@ -16,7 +16,6 @@ const licenseLike = {
|
||||
const testServer = utils.setupTestServer({ endpointGroups: ['variables'] });
|
||||
|
||||
beforeAll(async () => {
|
||||
await utils.initEncryptionKey();
|
||||
utils.mockInstance(License, licenseLike);
|
||||
|
||||
const owner = await testDb.createOwner();
|
||||
|
||||
16
packages/cli/test/setup-test-folder.ts
Normal file
16
packages/cli/test/setup-test-folder.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { tmpdir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { mkdirSync, mkdtempSync, writeFileSync } from 'fs';
|
||||
|
||||
const baseDir = join(tmpdir(), 'n8n-tests/');
|
||||
mkdirSync(baseDir, { recursive: true });
|
||||
|
||||
const testDir = mkdtempSync(baseDir);
|
||||
mkdirSync(join(testDir, '.n8n'));
|
||||
process.env.N8N_USER_FOLDER = testDir;
|
||||
|
||||
writeFileSync(
|
||||
join(testDir, '.n8n/config'),
|
||||
JSON.stringify({ encryptionKey: 'testkey', instanceId: '123' }),
|
||||
'utf-8',
|
||||
);
|
||||
@@ -12,6 +12,7 @@ import { CredentialsHelper } from '@/CredentialsHelper';
|
||||
import { NodeTypes } from '@/NodeTypes';
|
||||
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
||||
import { mockInstance } from '../integration/shared/utils';
|
||||
import Container from 'typedi';
|
||||
|
||||
describe('CredentialsHelper', () => {
|
||||
const TEST_ENCRYPTION_KEY = 'test';
|
||||
@@ -277,7 +278,7 @@ describe('CredentialsHelper', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const credentialsHelper = new CredentialsHelper(TEST_ENCRYPTION_KEY);
|
||||
const credentialsHelper = Container.get(CredentialsHelper);
|
||||
|
||||
const result = await credentialsHelper.authenticate(
|
||||
testData.input.credentials,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { SettingsRepository } from '@/databases/repositories';
|
||||
import { Container } from 'typedi';
|
||||
import { Cipher } from 'n8n-core';
|
||||
import { SettingsRepository } from '@/databases/repositories';
|
||||
import type { ExternalSecretsSettings } from '@/Interfaces';
|
||||
import { License } from '@/License';
|
||||
import { ExternalSecretsManager } from '@/ExternalSecrets/ExternalSecretsManager.ee';
|
||||
import { ExternalSecretsProviders } from '@/ExternalSecrets/ExternalSecretsProviders.ee';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import Container from 'typedi';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { mockInstance } from '../../integration/shared/utils';
|
||||
import {
|
||||
DummyProvider,
|
||||
@@ -13,56 +13,42 @@ import {
|
||||
FailedProvider,
|
||||
MockProviders,
|
||||
} from '../../shared/ExternalSecrets/utils';
|
||||
import { AES, enc } from 'crypto-js';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
|
||||
const connectedDate = '2023-08-01T12:32:29.000Z';
|
||||
const encryptionKey = 'testkey';
|
||||
let settings: string | null = null;
|
||||
const mockProvidersInstance = new MockProviders();
|
||||
const settingsRepo = mock<SettingsRepository>({
|
||||
async getEncryptedSecretsProviderSettings() {
|
||||
return settings;
|
||||
},
|
||||
async saveEncryptedSecretsProviderSettings(data) {
|
||||
settings = data;
|
||||
},
|
||||
});
|
||||
let licenseMock: License;
|
||||
let providersMock: ExternalSecretsProviders;
|
||||
let manager: ExternalSecretsManager | undefined;
|
||||
|
||||
const createMockSettings = (settings: ExternalSecretsSettings): string => {
|
||||
return AES.encrypt(JSON.stringify(settings), encryptionKey).toString();
|
||||
};
|
||||
|
||||
const decryptSettings = (settings: string) => {
|
||||
return JSON.parse(AES.decrypt(settings ?? '', encryptionKey).toString(enc.Utf8));
|
||||
};
|
||||
|
||||
describe('External Secrets Manager', () => {
|
||||
const connectedDate = '2023-08-01T12:32:29.000Z';
|
||||
let settings: string | null = null;
|
||||
|
||||
const mockProvidersInstance = new MockProviders();
|
||||
const license = mockInstance(License);
|
||||
const settingsRepo = mockInstance(SettingsRepository);
|
||||
mockInstance(InternalHooks);
|
||||
const cipher = Container.get(Cipher);
|
||||
|
||||
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(() => {
|
||||
jest
|
||||
.spyOn(UserSettings, 'getEncryptionKey')
|
||||
.mockReturnValue(new Promise((resolve) => resolve(encryptionKey)));
|
||||
providersMock = mockInstance(ExternalSecretsProviders, mockProvidersInstance);
|
||||
licenseMock = mockInstance(License, {
|
||||
isExternalSecretsEnabled() {
|
||||
return true;
|
||||
},
|
||||
settings = createMockSettings({
|
||||
dummy: { connected: true, connectedAt: new Date(connectedDate), settings: {} },
|
||||
});
|
||||
mockInstance(InternalHooks);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockProvidersInstance.setProviders({
|
||||
dummy: DummyProvider,
|
||||
});
|
||||
settings = createMockSettings({
|
||||
dummy: { connected: true, connectedAt: new Date(connectedDate), settings: {} },
|
||||
});
|
||||
|
||||
Container.remove(ExternalSecretsManager);
|
||||
license.isExternalSecretsEnabled.mockReturnValue(true);
|
||||
settingsRepo.getEncryptedSecretsProviderSettings.mockResolvedValue(settings);
|
||||
manager = new ExternalSecretsManager(settingsRepo, license, providersMock, cipher);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -71,8 +57,6 @@ describe('External Secrets Manager', () => {
|
||||
});
|
||||
|
||||
test('should get secret', async () => {
|
||||
manager = new ExternalSecretsManager(settingsRepo, licenseMock, providersMock);
|
||||
|
||||
await manager.init();
|
||||
|
||||
expect(manager.getSecret('dummy', 'test1')).toBe('value1');
|
||||
@@ -82,8 +66,6 @@ describe('External Secrets Manager', () => {
|
||||
mockProvidersInstance.setProviders({
|
||||
dummy: ErrorProvider,
|
||||
});
|
||||
manager = new ExternalSecretsManager(settingsRepo, licenseMock, providersMock);
|
||||
|
||||
expect(async () => manager!.init()).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -91,16 +73,12 @@ describe('External Secrets Manager', () => {
|
||||
mockProvidersInstance.setProviders({
|
||||
dummy: ErrorProvider,
|
||||
});
|
||||
manager = new ExternalSecretsManager(settingsRepo, licenseMock, providersMock);
|
||||
|
||||
await manager.init();
|
||||
expect(() => manager!.shutdown()).not.toThrow();
|
||||
manager = undefined;
|
||||
});
|
||||
|
||||
test('should save provider settings', async () => {
|
||||
manager = new ExternalSecretsManager(settingsRepo, licenseMock, providersMock);
|
||||
|
||||
const settingsSpy = jest.spyOn(settingsRepo, 'saveEncryptedSecretsProviderSettings');
|
||||
|
||||
await manager.init();
|
||||
@@ -122,8 +100,6 @@ describe('External Secrets Manager', () => {
|
||||
|
||||
test('should call provider update functions on a timer', async () => {
|
||||
jest.useFakeTimers();
|
||||
manager = new ExternalSecretsManager(settingsRepo, licenseMock, providersMock);
|
||||
|
||||
await manager.init();
|
||||
|
||||
const updateSpy = jest.spyOn(manager.getProvider('dummy')!, 'update');
|
||||
@@ -138,15 +114,7 @@ describe('External Secrets Manager', () => {
|
||||
test('should not call provider update functions if the not licensed', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
manager = new ExternalSecretsManager(
|
||||
settingsRepo,
|
||||
mock<License>({
|
||||
isExternalSecretsEnabled() {
|
||||
return false;
|
||||
},
|
||||
}),
|
||||
providersMock,
|
||||
);
|
||||
license.isExternalSecretsEnabled.mockReturnValue(false);
|
||||
|
||||
await manager.init();
|
||||
|
||||
@@ -165,7 +133,6 @@ describe('External Secrets Manager', () => {
|
||||
mockProvidersInstance.setProviders({
|
||||
dummy: FailedProvider,
|
||||
});
|
||||
manager = new ExternalSecretsManager(settingsRepo, licenseMock, providersMock);
|
||||
|
||||
await manager.init();
|
||||
|
||||
@@ -179,8 +146,6 @@ describe('External Secrets Manager', () => {
|
||||
});
|
||||
|
||||
test('should reinitialize a provider when save provider settings', async () => {
|
||||
manager = new ExternalSecretsManager(settingsRepo, licenseMock, providersMock);
|
||||
|
||||
await manager.init();
|
||||
|
||||
const dummyInitSpy = jest.spyOn(DummyProvider.prototype, 'init');
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { LicenseManager } from '@n8n_io/license-sdk';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import config from '@/config';
|
||||
import { License } from '@/License';
|
||||
import { N8N_VERSION } from '@/constants';
|
||||
import { mockInstance } from '../integration/shared/utils';
|
||||
|
||||
jest.mock('@n8n_io/license-sdk');
|
||||
|
||||
@@ -21,10 +23,11 @@ describe('License', () => {
|
||||
});
|
||||
|
||||
let license: License;
|
||||
const instanceSettings = mockInstance(InstanceSettings, { instanceId: MOCK_INSTANCE_ID });
|
||||
|
||||
beforeEach(async () => {
|
||||
license = new License();
|
||||
await license.init(MOCK_INSTANCE_ID);
|
||||
license = new License(instanceSettings);
|
||||
await license.init();
|
||||
});
|
||||
|
||||
test('initializes license manager', async () => {
|
||||
@@ -45,8 +48,8 @@ describe('License', () => {
|
||||
});
|
||||
|
||||
test('initializes license manager for worker', async () => {
|
||||
license = new License();
|
||||
await license.init(MOCK_INSTANCE_ID, 'worker');
|
||||
license = new License(instanceSettings);
|
||||
await license.init('worker');
|
||||
expect(LicenseManager).toHaveBeenCalledWith({
|
||||
autoRenewEnabled: false,
|
||||
autoRenewOffset: MOCK_RENEW_OFFSET,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { PostHog } from 'posthog-node';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import { PostHogClient } from '@/posthog';
|
||||
import config from '@/config';
|
||||
import { mockInstance } from '../integration/shared/utils';
|
||||
|
||||
jest.mock('posthog-node');
|
||||
|
||||
@@ -10,6 +12,8 @@ describe('PostHog', () => {
|
||||
const apiKey = 'api-key';
|
||||
const apiHost = 'api-host';
|
||||
|
||||
const instanceSettings = mockInstance(InstanceSettings, { instanceId });
|
||||
|
||||
beforeAll(() => {
|
||||
config.set('diagnostics.config.posthog.apiKey', apiKey);
|
||||
config.set('diagnostics.config.posthog.apiHost', apiHost);
|
||||
@@ -21,8 +25,8 @@ describe('PostHog', () => {
|
||||
});
|
||||
|
||||
it('inits PostHog correctly', async () => {
|
||||
const ph = new PostHogClient();
|
||||
await ph.init(instanceId);
|
||||
const ph = new PostHogClient(instanceSettings);
|
||||
await ph.init();
|
||||
|
||||
expect(PostHog.prototype.constructor).toHaveBeenCalledWith(apiKey, { host: apiHost });
|
||||
});
|
||||
@@ -30,8 +34,8 @@ describe('PostHog', () => {
|
||||
it('does not initialize or track if diagnostics are not enabled', async () => {
|
||||
config.set('diagnostics.enabled', false);
|
||||
|
||||
const ph = new PostHogClient();
|
||||
await ph.init(instanceId);
|
||||
const ph = new PostHogClient(instanceSettings);
|
||||
await ph.init();
|
||||
|
||||
ph.track({
|
||||
userId: 'test',
|
||||
@@ -50,8 +54,8 @@ describe('PostHog', () => {
|
||||
test: true,
|
||||
};
|
||||
|
||||
const ph = new PostHogClient();
|
||||
await ph.init(instanceId);
|
||||
const ph = new PostHogClient(instanceSettings);
|
||||
await ph.init();
|
||||
|
||||
ph.track({
|
||||
userId,
|
||||
@@ -70,8 +74,8 @@ describe('PostHog', () => {
|
||||
|
||||
it('gets feature flags', async () => {
|
||||
const createdAt = new Date();
|
||||
const ph = new PostHogClient();
|
||||
await ph.init(instanceId);
|
||||
const ph = new PostHogClient(instanceSettings);
|
||||
await ph.init();
|
||||
|
||||
await ph.getFeatureFlags({
|
||||
id: userId,
|
||||
|
||||
@@ -9,12 +9,11 @@ import {
|
||||
} from '@/environments/sourceControl/sourceControlHelper.ee';
|
||||
import { License } from '@/License';
|
||||
import { SourceControlPreferencesService } from '@/environments/sourceControl/sourceControlPreferences.service.ee';
|
||||
import { UserSettings } from 'n8n-core';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import path from 'path';
|
||||
import {
|
||||
SOURCE_CONTROL_SSH_FOLDER,
|
||||
SOURCE_CONTROL_GIT_FOLDER,
|
||||
SOURCE_CONTROL_SSH_KEY_NAME,
|
||||
} from '@/environments/sourceControl/constants';
|
||||
import { LoggerProxy } from 'n8n-workflow';
|
||||
import { getLogger } from '@/Logger';
|
||||
@@ -184,10 +183,9 @@ describe('Source Control', () => {
|
||||
});
|
||||
|
||||
it('should check for git and ssh folders and create them if required', async () => {
|
||||
const userFolder = UserSettings.getUserN8nFolderPath();
|
||||
const sshFolder = path.join(userFolder, SOURCE_CONTROL_SSH_FOLDER);
|
||||
const gitFolder = path.join(userFolder, SOURCE_CONTROL_GIT_FOLDER);
|
||||
const sshKeyName = path.join(sshFolder, SOURCE_CONTROL_SSH_KEY_NAME);
|
||||
const { n8nFolder } = Container.get(InstanceSettings);
|
||||
const sshFolder = path.join(n8nFolder, SOURCE_CONTROL_SSH_FOLDER);
|
||||
const gitFolder = path.join(n8nFolder, SOURCE_CONTROL_GIT_FOLDER);
|
||||
let hasThrown = false;
|
||||
try {
|
||||
accessSync(sshFolder, fsConstants.F_OK);
|
||||
|
||||
@@ -4,6 +4,8 @@ import config from '@/config';
|
||||
import { flushPromises } from './Helpers';
|
||||
import { PostHogClient } from '@/posthog';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { mockInstance } from '../integration/shared/utils';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
|
||||
jest.unmock('@/telemetry');
|
||||
jest.mock('@/license/License.service', () => {
|
||||
@@ -28,6 +30,7 @@ describe('Telemetry', () => {
|
||||
let telemetry: Telemetry;
|
||||
const instanceId = 'Telemetry unit test';
|
||||
const testDateTime = new Date('2022-01-01 00:00:00');
|
||||
const instanceSettings = mockInstance(InstanceSettings, { instanceId });
|
||||
|
||||
beforeAll(() => {
|
||||
startPulseSpy = jest
|
||||
@@ -49,11 +52,10 @@ describe('Telemetry', () => {
|
||||
beforeEach(async () => {
|
||||
spyTrack.mockClear();
|
||||
|
||||
const postHog = new PostHogClient();
|
||||
await postHog.init(instanceId);
|
||||
const postHog = new PostHogClient(instanceSettings);
|
||||
await postHog.init();
|
||||
|
||||
telemetry = new Telemetry(postHog, mock());
|
||||
telemetry.setInstanceId(instanceId);
|
||||
telemetry = new Telemetry(postHog, mock(), instanceSettings);
|
||||
(telemetry as any).rudderStack = mockRudderStack;
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user