Skip to content

Commit

Permalink
refactor(core): Load and validate all config at startup (no-changelog) (
Browse files Browse the repository at this point in the history
  • Loading branch information
netroy authored Jan 30, 2023
1 parent b2f59c3 commit 72249e0
Show file tree
Hide file tree
Showing 16 changed files with 80 additions and 163 deletions.
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

0 comments on commit 72249e0

Please sign in to comment.