Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(core): Load and validate all config at startup (no-changelog) #5283

Merged
merged 1 commit into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions packages/cli/src/CredentialsOverwrites.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import config from '@/config';
import type { ICredentialDataDecryptedObject, ICredentialTypes } from 'n8n-workflow';
import { deepCopy, LoggerProxy as Logger, jsonParse } from 'n8n-workflow';
import type { ICredentialsOverwrite } from '@/Interfaces';
import * as GenericHelpers from '@/GenericHelpers';

class CredentialsOverwritesClass {
private overwriteData: ICredentialsOverwrite = {};

private resolvedTypes: string[] = [];

constructor(private credentialTypes: ICredentialTypes) {}

async init() {
const data = (await GenericHelpers.getConfigValue('credentials.overwrite.data')) as string;

constructor(private credentialTypes: ICredentialTypes) {
const data = config.getEnv('credentials.overwrite.data');
const overwriteData = jsonParse<ICredentialsOverwrite>(data, {
errorMessage: 'The credentials-overwrite is not valid JSON.',
});
Expand Down
35 changes: 12 additions & 23 deletions packages/cli/src/Db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import type {
import { DataSource as Connection } from 'typeorm';
import type { TlsOptions } from 'tls';
import type { DatabaseType, IDatabaseCollections } from '@/Interfaces';
import * as GenericHelpers from '@/GenericHelpers';

import config from '@/config';

Expand Down Expand Up @@ -44,17 +43,13 @@ export function linkRepository<Entity extends ObjectLiteral>(
return connection.getRepository(entityClass);
}

export async function getConnectionOptions(dbType: DatabaseType): Promise<ConnectionOptions> {
export function getConnectionOptions(dbType: DatabaseType): ConnectionOptions {
switch (dbType) {
case 'postgresdb':
const sslCa = (await GenericHelpers.getConfigValue('database.postgresdb.ssl.ca')) as string;
const sslCert = (await GenericHelpers.getConfigValue(
'database.postgresdb.ssl.cert',
)) as string;
const sslKey = (await GenericHelpers.getConfigValue('database.postgresdb.ssl.key')) as string;
const sslRejectUnauthorized = (await GenericHelpers.getConfigValue(
'database.postgresdb.ssl.rejectUnauthorized',
)) as boolean;
const sslCa = config.getEnv('database.postgresdb.ssl.ca');
const sslCert = config.getEnv('database.postgresdb.ssl.cert');
const sslKey = config.getEnv('database.postgresdb.ssl.key');
const sslRejectUnauthorized = config.getEnv('database.postgresdb.ssl.rejectUnauthorized');

let ssl: TlsOptions | undefined;
if (sslCa !== '' || sslCert !== '' || sslKey !== '' || !sslRejectUnauthorized) {
Expand All @@ -68,15 +63,15 @@ export async function getConnectionOptions(dbType: DatabaseType): Promise<Connec

return {
...getPostgresConnectionOptions(),
...(await getOptionOverrides('postgresdb')),
...getOptionOverrides('postgresdb'),
ssl,
};

case 'mariadb':
case 'mysqldb':
return {
...(dbType === 'mysqldb' ? getMysqlConnectionOptions() : getMariaDBConnectionOptions()),
...(await getOptionOverrides('mysqldb')),
...getOptionOverrides('mysqldb'),
timezone: 'Z', // set UTC as default
};

Expand All @@ -93,17 +88,13 @@ export async function init(
): Promise<IDatabaseCollections> {
if (isInitialized) return collections;

const dbType = (await GenericHelpers.getConfigValue('database.type')) as DatabaseType;
const connectionOptions = testConnectionOptions ?? (await getConnectionOptions(dbType));
const dbType = config.getEnv('database.type');
const connectionOptions = testConnectionOptions ?? getConnectionOptions(dbType);

let loggingOption: LoggerOptions = (await GenericHelpers.getConfigValue(
'database.logging.enabled',
)) as boolean;
let loggingOption: LoggerOptions = config.getEnv('database.logging.enabled');

if (loggingOption) {
const optionsString = (
(await GenericHelpers.getConfigValue('database.logging.options')) as string
).replace(/\s+/g, '');
const optionsString = config.getEnv('database.logging.options').replace(/\s+/g, '');

if (optionsString === 'all') {
loggingOption = optionsString;
Expand All @@ -112,9 +103,7 @@ export async function init(
}
}

const maxQueryExecutionTime = (await GenericHelpers.getConfigValue(
'database.logging.maxQueryExecutionTime',
)) as string;
const maxQueryExecutionTime = config.getEnv('database.logging.maxQueryExecutionTime');

Object.assign(connectionOptions, {
entities: Object.values(entities),
Expand Down
68 changes: 1 addition & 67 deletions packages/cli/src/GenericHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,26 @@
/* eslint-disable no-underscore-dangle */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import type express from 'express';
import { readFile as fsReadFile } from 'fs/promises';
import type {
ExecutionError,
IDataObject,
INode,
IRunExecutionData,
Workflow,
WorkflowExecuteMode,
} from 'n8n-workflow';
import { validate } from 'class-validator';
import { Like } from 'typeorm';
import config from '@/config';
import * as Db from '@/Db';
import type { ICredentialsDb, IExecutionDb, IExecutionFlattedDb, IWorkflowDb } from '@/Interfaces';
import * as ResponseHelper from '@/ResponseHelper';
// eslint-disable-next-line import/order
import { Like } from 'typeorm';
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
import type { TagEntity } from '@db/entities/TagEntity';
import type { User } from '@db/entities/User';

/**
* Returns the base URL n8n is reachable from
*
*/
export function getBaseUrl(): string {
const protocol = config.getEnv('protocol');
Expand All @@ -44,73 +40,11 @@ export function getBaseUrl(): string {

/**
* Returns the session id if one is set
*
*/
export function getSessionId(req: express.Request): string | undefined {
return req.headers.sessionid as string | undefined;
}

/**
* Extracts configuration schema for key
*/
function extractSchemaForKey(configKey: string, configSchema: IDataObject): IDataObject {
const configKeyParts = configKey.split('.');

// eslint-disable-next-line no-restricted-syntax
for (const key of configKeyParts) {
if (configSchema[key] === undefined) {
throw new Error(`Key "${key}" of ConfigKey "${configKey}" does not exist`);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
} else if ((configSchema[key]! as IDataObject)._cvtProperties === undefined) {
configSchema = configSchema[key] as IDataObject;
} else {
configSchema = (configSchema[key] as IDataObject)._cvtProperties as IDataObject;
}
}
return configSchema;
}

/**
* Gets value from config with support for "_FILE" environment variables
*
* @param {string} configKey The key of the config data to get
*/
export async function getConfigValue(
configKey: string,
): Promise<string | boolean | number | undefined> {
// Get the environment variable
const configSchema = config.getSchema();
// @ts-ignore
const currentSchema = extractSchemaForKey(configKey, configSchema._cvtProperties as IDataObject);
// Check if environment variable is defined for config key
if (currentSchema.env === undefined) {
// No environment variable defined, so return value from config
// @ts-ignore
return config.getEnv(configKey);
}

// Check if special file environment variable exists
const fileEnvironmentVariable = process.env[`${currentSchema.env}_FILE`];
if (fileEnvironmentVariable === undefined) {
// Does not exist, so return value from config
// @ts-ignore
return config.getEnv(configKey);
}

let data;
try {
data = await fsReadFile(fileEnvironmentVariable, 'utf8');
} catch (error) {
if (error.code === 'ENOENT') {
throw new Error(`The file "${fileEnvironmentVariable}" could not be found.`);
}

throw error;
}

return data;
}

/**
* Generate a unique name for a workflow or credentials entity.
*
Expand Down
40 changes: 11 additions & 29 deletions packages/cli/src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,23 +409,17 @@ class Server extends AbstractServer {
// Check for basic auth credentials if activated
const basicAuthActive = config.getEnv('security.basicAuth.active');
if (basicAuthActive) {
const basicAuthUser = (await GenericHelpers.getConfigValue(
'security.basicAuth.user',
)) as string;
const basicAuthUser = config.getEnv('security.basicAuth.user');
if (basicAuthUser === '') {
throw new Error('Basic auth is activated but no user got defined. Please set one!');
}

const basicAuthPassword = (await GenericHelpers.getConfigValue(
'security.basicAuth.password',
)) as string;
const basicAuthPassword = config.getEnv('security.basicAuth.password');
if (basicAuthPassword === '') {
throw new Error('Basic auth is activated but no password got defined. Please set one!');
}

const basicAuthHashEnabled = (await GenericHelpers.getConfigValue(
'security.basicAuth.hash',
)) as boolean;
const basicAuthHashEnabled = config.getEnv('security.basicAuth.hash') as boolean;

let validPassword: null | string = null;

Expand Down Expand Up @@ -483,31 +477,19 @@ class Server extends AbstractServer {
// Check for and validate JWT if configured
const jwtAuthActive = config.getEnv('security.jwtAuth.active');
if (jwtAuthActive) {
const jwtAuthHeader = (await GenericHelpers.getConfigValue(
'security.jwtAuth.jwtHeader',
)) as string;
const jwtAuthHeader = config.getEnv('security.jwtAuth.jwtHeader');
if (jwtAuthHeader === '') {
throw new Error('JWT auth is activated but no request header was defined. Please set one!');
}
const jwksUri = (await GenericHelpers.getConfigValue('security.jwtAuth.jwksUri')) as string;
const jwksUri = config.getEnv('security.jwtAuth.jwksUri');
if (jwksUri === '') {
throw new Error('JWT auth is activated but no JWK Set URI was defined. Please set one!');
}
const jwtHeaderValuePrefix = (await GenericHelpers.getConfigValue(
'security.jwtAuth.jwtHeaderValuePrefix',
)) as string;
const jwtIssuer = (await GenericHelpers.getConfigValue(
'security.jwtAuth.jwtIssuer',
)) as string;
const jwtNamespace = (await GenericHelpers.getConfigValue(
'security.jwtAuth.jwtNamespace',
)) as string;
const jwtAllowedTenantKey = (await GenericHelpers.getConfigValue(
'security.jwtAuth.jwtAllowedTenantKey',
)) as string;
const jwtAllowedTenant = (await GenericHelpers.getConfigValue(
'security.jwtAuth.jwtAllowedTenant',
)) as string;
const jwtHeaderValuePrefix = config.getEnv('security.jwtAuth.jwtHeaderValuePrefix');
const jwtIssuer = config.getEnv('security.jwtAuth.jwtIssuer');
const jwtNamespace = config.getEnv('security.jwtAuth.jwtNamespace');
const jwtAllowedTenantKey = config.getEnv('security.jwtAuth.jwtAllowedTenantKey');
const jwtAllowedTenant = config.getEnv('security.jwtAuth.jwtAllowedTenant');

// eslint-disable-next-line no-inner-declarations
function isTenantAllowed(decodedToken: object): boolean {
Expand Down Expand Up @@ -1456,7 +1438,7 @@ export async function start(): Promise<void> {
const binaryDataConfig = config.getEnv('binaryDataManager');
const diagnosticInfo: IDiagnosticInfo = {
basicAuthActive: config.getEnv('security.basicAuth.active'),
databaseType: (await GenericHelpers.getConfigValue('database.type')) as DatabaseType,
databaseType: config.getEnv('database.type'),
disableProductionWebhooksOnMainProcess: config.getEnv(
'endpoints.disableProductionWebhooksOnMainProcess',
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { existsSync } from 'fs';
import { readFile } from 'fs/promises';
import Handlebars from 'handlebars';
import { join as pathJoin } from 'path';
import * as GenericHelpers from '@/GenericHelpers';
import config from '@/config';
import type {
InviteEmailData,
Expand All @@ -23,9 +22,7 @@ async function getTemplate(
): Promise<Template> {
let template = templates[templateName];
if (!template) {
const templateOverride = (await GenericHelpers.getConfigValue(
`userManagement.emails.templates.${templateName}`,
)) as string;
const templateOverride = config.getEnv(`userManagement.emails.templates.${templateName}`);

let markup;
if (templateOverride && existsSync(templateOverride)) {
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/WaitTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ import type { FindManyOptions, ObjectLiteral } from 'typeorm';
import { LessThanOrEqual } from 'typeorm';
import { DateUtils } from 'typeorm/util/DateUtils';

import config from '@/config';
import * as Db from '@/Db';
import * as ResponseHelper from '@/ResponseHelper';
import * as GenericHelpers from '@/GenericHelpers';
import * as ActiveExecutions from '@/ActiveExecutions';
import type {
DatabaseType,
IExecutionFlattedDb,
IExecutionsStopData,
IWorkflowExecutionDataProcess,
Expand Down Expand Up @@ -63,7 +62,8 @@ export class WaitTrackerClass {
waitTill: 'ASC',
},
};
const dbType = (await GenericHelpers.getConfigValue('database.type')) as DatabaseType;

const dbType = config.getEnv('database.type');
if (dbType === 'sqlite') {
// This is needed because of issue in TypeORM <> SQLite:
// https://github.com/typeorm/typeorm/issues/2286
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/WorkflowRunnerProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ class WorkflowRunnerProcess {
const credentialTypes = CredentialTypes(loadNodesAndCredentials);

// Load the credentials overwrites if any exist
const credentialsOverwrites = CredentialsOverwrites(credentialTypes);
await credentialsOverwrites.init();
CredentialsOverwrites(credentialTypes);

// Load all external hooks
const externalHooks = ExternalHooks();
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/db/revert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class DbRevertMigrationCommand extends Command {
try {
const dbType = config.getEnv('database.type');
const connectionOptions: ConnectionOptions = {
...(await getConnectionOptions(dbType)),
...getConnectionOptions(dbType),
subscribers: [],
synchronize: false,
migrationsRun: false,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class Execute extends Command {
const credentialTypes = CredentialTypes(loadNodesAndCredentials);

// Load the credentials overwrites if any exist
await CredentialsOverwrites(credentialTypes).init();
CredentialsOverwrites(credentialTypes);

// Load all external hooks
const externalHooks = ExternalHooks();
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/executeBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ export class ExecuteBatch extends Command {
const credentialTypes = CredentialTypes(loadNodesAndCredentials);

// Load the credentials overwrites if any exist
await CredentialsOverwrites(credentialTypes).init();
CredentialsOverwrites(credentialTypes);

// Load all external hooks
const externalHooks = ExternalHooks();
Expand Down
6 changes: 2 additions & 4 deletions packages/cli/src/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import { NodeTypes } from '@/NodeTypes';
import { InternalHooksManager } from '@/InternalHooksManager';
import * as Server from '@/Server';
import type { DatabaseType } from '@/Interfaces';
import * as TestWebhooks from '@/TestWebhooks';
import { WaitTracker } from '@/WaitTracker';

Expand Down Expand Up @@ -279,7 +278,7 @@ export class Start extends Command {
const credentialTypes = CredentialTypes(loadNodesAndCredentials);

// Load the credentials overwrites if any exist
await CredentialsOverwrites(credentialTypes).init();
CredentialsOverwrites(credentialTypes);

await loadNodesAndCredentials.generateTypesForFrontend();

Expand Down Expand Up @@ -341,8 +340,7 @@ export class Start extends Command {
);
}

const dbType = (await GenericHelpers.getConfigValue('database.type')) as DatabaseType;

const dbType = config.getEnv('database.type');
if (dbType === 'sqlite') {
const shouldRunVacuum = config.getEnv('database.sqlite.executeVacuumOnStartup');
if (shouldRunVacuum) {
Expand Down
Loading