mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-23 11:44:06 -08:00
refactor(core): Port endpoints config (no-changelog) (#10268)
This commit is contained in:
parent
d91eb2cdd5
commit
1608d2527b
102
packages/@n8n/config/src/configs/endpoints.ts
Normal file
102
packages/@n8n/config/src/configs/endpoints.ts
Normal file
|
@ -0,0 +1,102 @@
|
|||
import { Config, Env, Nested } from '../decorators';
|
||||
|
||||
@Config
|
||||
class PrometheusMetricsConfig {
|
||||
/** Whether to enable the `/metrics` endpoint to expose Prometheus metrics. */
|
||||
@Env('N8N_METRICS')
|
||||
readonly enable: boolean = false;
|
||||
|
||||
/** Prefix for Prometheus metric names. */
|
||||
@Env('N8N_METRICS_PREFIX')
|
||||
readonly prefix: string = 'n8n_';
|
||||
|
||||
/** Whether to expose system and Node.js metrics. See: https://www.npmjs.com/package/prom-client */
|
||||
@Env('N8N_METRICS_INCLUDE_DEFAULT_METRICS')
|
||||
readonly includeDefaultMetrics = true;
|
||||
|
||||
/** Whether to include a label for workflow ID on workflow metrics. */
|
||||
@Env('N8N_METRICS_INCLUDE_WORKFLOW_ID_LABEL')
|
||||
readonly includeWorkflowIdLabel: boolean = false;
|
||||
|
||||
/** Whether to include a label for node type on node metrics. */
|
||||
@Env('N8N_METRICS_INCLUDE_NODE_TYPE_LABEL')
|
||||
readonly includeNodeTypeLabel: boolean = false;
|
||||
|
||||
/** Whether to include a label for credential type on credential metrics. */
|
||||
@Env('N8N_METRICS_INCLUDE_CREDENTIAL_TYPE_LABEL')
|
||||
readonly includeCredentialTypeLabel: boolean = false;
|
||||
|
||||
/** Whether to expose metrics for API endpoints. See: https://www.npmjs.com/package/express-prom-bundle */
|
||||
@Env('N8N_METRICS_INCLUDE_API_ENDPOINTS')
|
||||
readonly includeApiEndpoints: boolean = false;
|
||||
|
||||
/** Whether to include a label for the path of API endpoint calls. */
|
||||
@Env('N8N_METRICS_INCLUDE_API_PATH_LABEL')
|
||||
readonly includeApiPathLabel: boolean = false;
|
||||
|
||||
/** Whether to include a label for the HTTP method of API endpoint calls. */
|
||||
@Env('N8N_METRICS_INCLUDE_API_METHOD_LABEL')
|
||||
readonly includeApiMethodLabel: boolean = false;
|
||||
|
||||
/** Whether to include a label for the status code of API endpoint calls. */
|
||||
@Env('N8N_METRICS_INCLUDE_API_STATUS_CODE_LABEL')
|
||||
readonly includeApiStatusCodeLabel: boolean = false;
|
||||
|
||||
/** Whether to include metrics for cache hits and misses. */
|
||||
@Env('N8N_METRICS_INCLUDE_CACHE_METRICS')
|
||||
readonly includeCacheMetrics: boolean = false;
|
||||
|
||||
/** Whether to include metrics derived from n8n's internal events */
|
||||
@Env('N8N_METRICS_INCLUDE_MESSAGE_EVENT_BUS_METRICS')
|
||||
readonly includeMessageEventBusMetrics: boolean = false;
|
||||
}
|
||||
|
||||
@Config
|
||||
export class EndpointsConfig {
|
||||
/** Max payload size in MiB */
|
||||
@Env('N8N_PAYLOAD_SIZE_MAX')
|
||||
readonly payloadSizeMax: number = 16;
|
||||
|
||||
@Nested
|
||||
readonly metrics: PrometheusMetricsConfig;
|
||||
|
||||
/** Path segment for REST API endpoints. */
|
||||
@Env('N8N_ENDPOINT_REST')
|
||||
readonly rest: string = 'rest';
|
||||
|
||||
/** Path segment for form endpoints. */
|
||||
@Env('N8N_ENDPOINT_FORM')
|
||||
readonly form: string = 'form';
|
||||
|
||||
/** Path segment for test form endpoints. */
|
||||
@Env('N8N_ENDPOINT_FORM_TEST')
|
||||
readonly formTest: string = 'form-test';
|
||||
|
||||
/** Path segment for waiting form endpoints. */
|
||||
@Env('N8N_ENDPOINT_FORM_WAIT')
|
||||
readonly formWaiting: string = 'form-waiting';
|
||||
|
||||
/** Path segment for webhook endpoints. */
|
||||
@Env('N8N_ENDPOINT_WEBHOOK')
|
||||
readonly webhook: string = 'webhook';
|
||||
|
||||
/** Path segment for test webhook endpoints. */
|
||||
@Env('N8N_ENDPOINT_WEBHOOK_TEST')
|
||||
readonly webhookTest: string = 'webhook-test';
|
||||
|
||||
/** Path segment for waiting webhook endpoints. */
|
||||
@Env('N8N_ENDPOINT_WEBHOOK_WAIT')
|
||||
readonly webhookWaiting: string = 'webhook-waiting';
|
||||
|
||||
/** Whether to disable n8n's UI (frontend). */
|
||||
@Env('N8N_DISABLE_UI')
|
||||
readonly disableUi: boolean = false;
|
||||
|
||||
/** Whether to disable production webhooks on the main process, when using webhook-specific processes. */
|
||||
@Env('N8N_DISABLE_PRODUCTION_MAIN_PROCESS')
|
||||
readonly disableProductionWebhooksOnMainProcess: boolean = false;
|
||||
|
||||
/** Colon-delimited list of additional endpoints to not open the UI on. */
|
||||
@Env('N8N_ADDITIONAL_NON_UI_ROUTES')
|
||||
readonly additionalNonUIRoutes: string = '';
|
||||
}
|
|
@ -10,6 +10,7 @@ import { EventBusConfig } from './configs/event-bus';
|
|||
import { NodesConfig } from './configs/nodes';
|
||||
import { ExternalStorageConfig } from './configs/external-storage';
|
||||
import { WorkflowsConfig } from './configs/workflows';
|
||||
import { EndpointsConfig } from './configs/endpoints';
|
||||
|
||||
@Config
|
||||
class UserManagementConfig {
|
||||
|
@ -71,4 +72,7 @@ export class GlobalConfig {
|
|||
/** HTTP Protocol via which n8n can be reached */
|
||||
@Env('N8N_PROTOCOL')
|
||||
readonly protocol: 'http' | 'https' = 'http';
|
||||
|
||||
@Nested
|
||||
readonly endpoints: EndpointsConfig;
|
||||
}
|
||||
|
|
|
@ -145,12 +145,44 @@ describe('GlobalConfig', () => {
|
|||
onboardingFlowDisabled: false,
|
||||
callerPolicyDefaultOption: 'workflowsFromSameOwner',
|
||||
},
|
||||
endpoints: {
|
||||
metrics: {
|
||||
enable: false,
|
||||
prefix: 'n8n_',
|
||||
includeWorkflowIdLabel: false,
|
||||
includeDefaultMetrics: true,
|
||||
includeMessageEventBusMetrics: false,
|
||||
includeNodeTypeLabel: false,
|
||||
includeCacheMetrics: false,
|
||||
includeApiEndpoints: false,
|
||||
includeApiPathLabel: false,
|
||||
includeApiMethodLabel: false,
|
||||
includeCredentialTypeLabel: false,
|
||||
includeApiStatusCodeLabel: false,
|
||||
},
|
||||
additionalNonUIRoutes: '',
|
||||
disableProductionWebhooksOnMainProcess: false,
|
||||
disableUi: false,
|
||||
form: 'form',
|
||||
formTest: 'form-test',
|
||||
formWaiting: 'form-waiting',
|
||||
payloadSizeMax: 16,
|
||||
rest: 'rest',
|
||||
webhook: 'webhook',
|
||||
webhookTest: 'webhook-test',
|
||||
webhookWaiting: 'webhook-waiting',
|
||||
},
|
||||
};
|
||||
|
||||
it('should use all default values when no env variables are defined', () => {
|
||||
process.env = {};
|
||||
const config = Container.get(GlobalConfig);
|
||||
expect(config).toEqual(defaultConfig);
|
||||
|
||||
// deepCopy for diff to show plain objects
|
||||
// eslint-disable-next-line n8n-local-rules/no-json-parse-json-stringify
|
||||
const deepCopy = <T>(obj: T): T => JSON.parse(JSON.stringify(obj));
|
||||
|
||||
expect(deepCopy(config)).toEqual(defaultConfig);
|
||||
expect(mockFs.readFileSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ export abstract class AbstractServer {
|
|||
|
||||
protected externalHooks: ExternalHooks;
|
||||
|
||||
protected protocol = Container.get(GlobalConfig).protocol;
|
||||
protected globalConfig = Container.get(GlobalConfig);
|
||||
|
||||
protected sslKey: string;
|
||||
|
||||
|
@ -74,15 +74,15 @@ export abstract class AbstractServer {
|
|||
this.sslKey = config.getEnv('ssl_key');
|
||||
this.sslCert = config.getEnv('ssl_cert');
|
||||
|
||||
this.restEndpoint = config.getEnv('endpoints.rest');
|
||||
this.restEndpoint = this.globalConfig.endpoints.rest;
|
||||
|
||||
this.endpointForm = config.getEnv('endpoints.form');
|
||||
this.endpointFormTest = config.getEnv('endpoints.formTest');
|
||||
this.endpointFormWaiting = config.getEnv('endpoints.formWaiting');
|
||||
this.endpointForm = this.globalConfig.endpoints.form;
|
||||
this.endpointFormTest = this.globalConfig.endpoints.formTest;
|
||||
this.endpointFormWaiting = this.globalConfig.endpoints.formWaiting;
|
||||
|
||||
this.endpointWebhook = config.getEnv('endpoints.webhook');
|
||||
this.endpointWebhookTest = config.getEnv('endpoints.webhookTest');
|
||||
this.endpointWebhookWaiting = config.getEnv('endpoints.webhookWaiting');
|
||||
this.endpointWebhook = this.globalConfig.endpoints.webhook;
|
||||
this.endpointWebhookTest = this.globalConfig.endpoints.webhookTest;
|
||||
this.endpointWebhookWaiting = this.globalConfig.endpoints.webhookWaiting;
|
||||
|
||||
this.uniqueInstanceId = generateHostInstanceId(instanceType);
|
||||
|
||||
|
@ -134,7 +134,8 @@ export abstract class AbstractServer {
|
|||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
const { app, protocol, sslKey, sslCert } = this;
|
||||
const { app, sslKey, sslCert } = this;
|
||||
const { protocol } = this.globalConfig;
|
||||
|
||||
if (protocol === 'https' && sslKey && sslCert) {
|
||||
const https = await import('https');
|
||||
|
@ -261,14 +262,16 @@ export abstract class AbstractServer {
|
|||
return;
|
||||
}
|
||||
|
||||
this.logger.debug(`Shutting down ${this.protocol} server`);
|
||||
const { protocol } = this.globalConfig;
|
||||
|
||||
this.logger.debug(`Shutting down ${protocol} server`);
|
||||
|
||||
this.server.close((error) => {
|
||||
if (error) {
|
||||
this.logger.error(`Error while shutting down ${this.protocol} server`, { error });
|
||||
this.logger.error(`Error while shutting down ${protocol} server`, { error });
|
||||
}
|
||||
|
||||
this.logger.debug(`${this.protocol} server shut down`);
|
||||
this.logger.debug(`${protocol} server shut down`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import { promisify } from 'util';
|
|||
import cookieParser from 'cookie-parser';
|
||||
import express from 'express';
|
||||
import helmet from 'helmet';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import type { IN8nUISettings } from 'n8n-workflow';
|
||||
|
||||
|
@ -81,17 +80,16 @@ export class Server extends AbstractServer {
|
|||
private readonly loadNodesAndCredentials: LoadNodesAndCredentials,
|
||||
private readonly orchestrationService: OrchestrationService,
|
||||
private readonly postHogClient: PostHogClient,
|
||||
private readonly globalConfig: GlobalConfig,
|
||||
private readonly eventService: EventService,
|
||||
) {
|
||||
super('main');
|
||||
|
||||
this.testWebhooksEnabled = true;
|
||||
this.webhooksEnabled = !config.getEnv('endpoints.disableProductionWebhooksOnMainProcess');
|
||||
this.webhooksEnabled = !this.globalConfig.endpoints.disableProductionWebhooksOnMainProcess;
|
||||
}
|
||||
|
||||
async start() {
|
||||
if (!config.getEnv('endpoints.disableUi')) {
|
||||
if (!this.globalConfig.endpoints.disableUi) {
|
||||
const { FrontendService } = await import('@/services/frontend.service');
|
||||
this.frontendService = Container.get(FrontendService);
|
||||
}
|
||||
|
@ -133,7 +131,7 @@ export class Server extends AbstractServer {
|
|||
await import('@/controllers/mfa.controller');
|
||||
}
|
||||
|
||||
if (!config.getEnv('endpoints.disableUi')) {
|
||||
if (!this.globalConfig.endpoints.disableUi) {
|
||||
await import('@/controllers/cta.controller');
|
||||
}
|
||||
|
||||
|
@ -167,7 +165,7 @@ export class Server extends AbstractServer {
|
|||
}
|
||||
|
||||
async configure(): Promise<void> {
|
||||
if (config.getEnv('endpoints.metrics.enable')) {
|
||||
if (this.globalConfig.endpoints.metrics.enable) {
|
||||
const { PrometheusMetricsService } = await import('@/metrics/prometheus-metrics.service');
|
||||
await Container.get(PrometheusMetricsService).init(this.app);
|
||||
}
|
||||
|
@ -307,7 +305,8 @@ export class Server extends AbstractServer {
|
|||
this.app.use('/icons/@:scope/:packageName/*/*.(svg|png)', serveIcons);
|
||||
this.app.use('/icons/:packageName/*/*.(svg|png)', serveIcons);
|
||||
|
||||
const isTLSEnabled = this.protocol === 'https' && !!(this.sslKey && this.sslCert);
|
||||
const isTLSEnabled =
|
||||
this.globalConfig.protocol === 'https' && !!(this.sslKey && this.sslCert);
|
||||
const isPreviewMode = process.env.N8N_PREVIEW_MODE === 'true';
|
||||
const securityHeadersMiddleware = helmet({
|
||||
contentSecurityPolicy: false,
|
||||
|
@ -341,7 +340,7 @@ export class Server extends AbstractServer {
|
|||
this.restEndpoint,
|
||||
this.endpointPresetCredentials,
|
||||
isApiEnabled() ? '' : publicApiEndpoint,
|
||||
...config.getEnv('endpoints.additionalNonUIRoutes').split(':'),
|
||||
...this.globalConfig.endpoints.additionalNonUIRoutes.split(':'),
|
||||
].filter((u) => !!u);
|
||||
const nonUIRoutesRegex = new RegExp(`^/(${nonUIRoutes.join('|')})/?.*$`);
|
||||
const historyApiHandler: express.RequestHandler = (req, res, next) => {
|
||||
|
|
|
@ -1002,23 +1002,19 @@ export async function getBase(
|
|||
): Promise<IWorkflowExecuteAdditionalData> {
|
||||
const urlBaseWebhook = Container.get(UrlService).getWebhookBaseUrl();
|
||||
|
||||
const formWaitingBaseUrl = urlBaseWebhook + config.getEnv('endpoints.formWaiting');
|
||||
|
||||
const webhookBaseUrl = urlBaseWebhook + config.getEnv('endpoints.webhook');
|
||||
const webhookTestBaseUrl = urlBaseWebhook + config.getEnv('endpoints.webhookTest');
|
||||
const webhookWaitingBaseUrl = urlBaseWebhook + config.getEnv('endpoints.webhookWaiting');
|
||||
const globalConfig = Container.get(GlobalConfig);
|
||||
|
||||
const variables = await WorkflowHelpers.getVariables();
|
||||
|
||||
return {
|
||||
credentialsHelper: Container.get(CredentialsHelper),
|
||||
executeWorkflow,
|
||||
restApiUrl: urlBaseWebhook + config.getEnv('endpoints.rest'),
|
||||
restApiUrl: urlBaseWebhook + globalConfig.endpoints.rest,
|
||||
instanceBaseUrl: urlBaseWebhook,
|
||||
formWaitingBaseUrl,
|
||||
webhookBaseUrl,
|
||||
webhookWaitingBaseUrl,
|
||||
webhookTestBaseUrl,
|
||||
formWaitingBaseUrl: globalConfig.endpoints.formWaiting,
|
||||
webhookBaseUrl: globalConfig.endpoints.webhook,
|
||||
webhookWaitingBaseUrl: globalConfig.endpoints.webhookWaiting,
|
||||
webhookTestBaseUrl: globalConfig.endpoints.webhookTest,
|
||||
currentNodeParameters,
|
||||
executionTimeoutTimestamp,
|
||||
userId,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Service } from 'typedi';
|
||||
import Container, { Service } from 'typedi';
|
||||
import type { NextFunction, Response } from 'express';
|
||||
import { createHash } from 'crypto';
|
||||
import { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken';
|
||||
|
@ -14,6 +14,7 @@ import { Logger } from '@/Logger';
|
|||
import type { AuthenticatedRequest } from '@/requests';
|
||||
import { JwtService } from '@/services/jwt.service';
|
||||
import { UrlService } from '@/services/url.service';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
|
||||
interface AuthJwtPayload {
|
||||
/** User Id */
|
||||
|
@ -33,7 +34,7 @@ interface PasswordResetToken {
|
|||
hash: string;
|
||||
}
|
||||
|
||||
const restEndpoint = config.get('endpoints.rest');
|
||||
const restEndpoint = Container.get(GlobalConfig).endpoints.rest;
|
||||
// The browser-id check needs to be skipped on these endpoints
|
||||
const skipBrowserIdCheckEndpoints = [
|
||||
// we need to exclude push endpoint because we can't send custom header on websocket requests
|
||||
|
|
|
@ -44,7 +44,7 @@ export abstract class BaseCommand extends Command {
|
|||
|
||||
protected license: License;
|
||||
|
||||
private globalConfig = Container.get(GlobalConfig);
|
||||
protected globalConfig = Container.get(GlobalConfig);
|
||||
|
||||
/**
|
||||
* How long to wait for graceful shutdown before force killing the process.
|
||||
|
|
|
@ -124,8 +124,8 @@ export class Start extends BaseCommand {
|
|||
|
||||
private async generateStaticAssets() {
|
||||
// Read the index file and replace the path placeholder
|
||||
const n8nPath = Container.get(GlobalConfig).path;
|
||||
const restEndpoint = config.getEnv('endpoints.rest');
|
||||
const n8nPath = this.globalConfig.path;
|
||||
|
||||
const hooksUrls = config.getEnv('externalFrontendHooksUrls');
|
||||
|
||||
let scriptsString = '';
|
||||
|
@ -151,7 +151,9 @@ export class Start extends BaseCommand {
|
|||
];
|
||||
if (filePath.endsWith('index.html')) {
|
||||
streams.push(
|
||||
replaceStream('{{REST_ENDPOINT}}', restEndpoint, { ignoreCase: false }),
|
||||
replaceStream('{{REST_ENDPOINT}}', this.globalConfig.endpoints.rest, {
|
||||
ignoreCase: false,
|
||||
}),
|
||||
replaceStream(closingTitleTag, closingTitleTag + scriptsString, {
|
||||
ignoreCase: false,
|
||||
}),
|
||||
|
@ -201,7 +203,7 @@ export class Start extends BaseCommand {
|
|||
this.initWorkflowHistory();
|
||||
this.logger.debug('Workflow history init complete');
|
||||
|
||||
if (!config.getEnv('endpoints.disableUi')) {
|
||||
if (!this.globalConfig.endpoints.disableUi) {
|
||||
await this.generateStaticAssets();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -355,149 +355,6 @@ export const schema = {
|
|||
},
|
||||
},
|
||||
|
||||
endpoints: {
|
||||
payloadSizeMax: {
|
||||
format: Number,
|
||||
default: 16,
|
||||
env: 'N8N_PAYLOAD_SIZE_MAX',
|
||||
doc: 'Maximum payload size in MB.',
|
||||
},
|
||||
metrics: {
|
||||
enable: {
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: 'N8N_METRICS',
|
||||
doc: 'Enable /metrics endpoint. Default: false',
|
||||
},
|
||||
prefix: {
|
||||
format: String,
|
||||
default: 'n8n_',
|
||||
env: 'N8N_METRICS_PREFIX',
|
||||
doc: 'An optional prefix for metric names. Default: n8n_',
|
||||
},
|
||||
includeDefaultMetrics: {
|
||||
format: Boolean,
|
||||
default: true,
|
||||
env: 'N8N_METRICS_INCLUDE_DEFAULT_METRICS',
|
||||
doc: 'Whether to expose default system and node.js metrics. Default: true',
|
||||
},
|
||||
includeWorkflowIdLabel: {
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: 'N8N_METRICS_INCLUDE_WORKFLOW_ID_LABEL',
|
||||
doc: 'Whether to include a label for the workflow ID on workflow metrics. Default: false',
|
||||
},
|
||||
includeNodeTypeLabel: {
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: 'N8N_METRICS_INCLUDE_NODE_TYPE_LABEL',
|
||||
doc: 'Whether to include a label for the node type on node metrics. Default: false',
|
||||
},
|
||||
includeCredentialTypeLabel: {
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: 'N8N_METRICS_INCLUDE_CREDENTIAL_TYPE_LABEL',
|
||||
doc: 'Whether to include a label for the credential type on credential metrics. Default: false',
|
||||
},
|
||||
includeApiEndpoints: {
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: 'N8N_METRICS_INCLUDE_API_ENDPOINTS',
|
||||
doc: 'Whether to expose metrics for API endpoints. Default: false',
|
||||
},
|
||||
includeApiPathLabel: {
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: 'N8N_METRICS_INCLUDE_API_PATH_LABEL',
|
||||
doc: 'Whether to include a label for the path of API invocations. Default: false',
|
||||
},
|
||||
includeApiMethodLabel: {
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: 'N8N_METRICS_INCLUDE_API_METHOD_LABEL',
|
||||
doc: 'Whether to include a label for the HTTP method (GET, POST, ...) of API invocations. Default: false',
|
||||
},
|
||||
includeApiStatusCodeLabel: {
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: 'N8N_METRICS_INCLUDE_API_STATUS_CODE_LABEL',
|
||||
doc: 'Whether to include a label for the HTTP status code (200, 404, ...) of API invocations. Default: false',
|
||||
},
|
||||
includeCacheMetrics: {
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: 'N8N_METRICS_INCLUDE_CACHE_METRICS',
|
||||
doc: 'Whether to include metrics for cache hits and misses. Default: false',
|
||||
},
|
||||
includeMessageEventBusMetrics: {
|
||||
format: Boolean,
|
||||
default: true,
|
||||
env: 'N8N_METRICS_INCLUDE_MESSAGE_EVENT_BUS_METRICS',
|
||||
doc: 'Whether to include metrics for events. Default: false',
|
||||
},
|
||||
},
|
||||
rest: {
|
||||
format: String,
|
||||
default: 'rest',
|
||||
env: 'N8N_ENDPOINT_REST',
|
||||
doc: 'Path for rest endpoint',
|
||||
},
|
||||
form: {
|
||||
format: String,
|
||||
default: 'form',
|
||||
env: 'N8N_ENDPOINT_FORM',
|
||||
doc: 'Path for form endpoint',
|
||||
},
|
||||
formTest: {
|
||||
format: String,
|
||||
default: 'form-test',
|
||||
env: 'N8N_ENDPOINT_FORM_TEST',
|
||||
doc: 'Path for test form endpoint',
|
||||
},
|
||||
formWaiting: {
|
||||
format: String,
|
||||
default: 'form-waiting',
|
||||
env: 'N8N_ENDPOINT_FORM_WAIT',
|
||||
doc: 'Path for waiting form endpoint',
|
||||
},
|
||||
webhook: {
|
||||
format: String,
|
||||
default: 'webhook',
|
||||
env: 'N8N_ENDPOINT_WEBHOOK',
|
||||
doc: 'Path for webhook endpoint',
|
||||
},
|
||||
webhookWaiting: {
|
||||
format: String,
|
||||
default: 'webhook-waiting',
|
||||
env: 'N8N_ENDPOINT_WEBHOOK_WAIT',
|
||||
doc: 'Path for waiting-webhook endpoint',
|
||||
},
|
||||
webhookTest: {
|
||||
format: String,
|
||||
default: 'webhook-test',
|
||||
env: 'N8N_ENDPOINT_WEBHOOK_TEST',
|
||||
doc: 'Path for test-webhook endpoint',
|
||||
},
|
||||
disableUi: {
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: 'N8N_DISABLE_UI',
|
||||
doc: 'Disable N8N UI (Frontend).',
|
||||
},
|
||||
disableProductionWebhooksOnMainProcess: {
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: 'N8N_DISABLE_PRODUCTION_MAIN_PROCESS',
|
||||
doc: 'Disable production webhooks from main process. This helps ensures no http traffic load to main process when using webhook-specific processes.',
|
||||
},
|
||||
additionalNonUIRoutes: {
|
||||
doc: 'Additional endpoints to not open the UI on. Multiple endpoints can be separated by colon (":")',
|
||||
format: String,
|
||||
default: '',
|
||||
env: 'N8N_ADDITIONAL_NON_UI_ROUTES',
|
||||
},
|
||||
},
|
||||
|
||||
workflowTagsDisabled: {
|
||||
format: Boolean,
|
||||
default: false,
|
||||
|
|
|
@ -5,7 +5,6 @@ import { Credentials } from 'n8n-core';
|
|||
import type { ICredentialDataDecryptedObject, IWorkflowExecuteAdditionalData } from 'n8n-workflow';
|
||||
import { jsonParse, ApplicationError } from 'n8n-workflow';
|
||||
|
||||
import config from '@/config';
|
||||
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
|
@ -20,6 +19,7 @@ import { ExternalHooks } from '@/ExternalHooks';
|
|||
import { UrlService } from '@/services/url.service';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
|
||||
export interface CsrfStateParam {
|
||||
cid: string;
|
||||
|
@ -37,10 +37,11 @@ export abstract class AbstractOAuthController {
|
|||
private readonly credentialsRepository: CredentialsRepository,
|
||||
private readonly sharedCredentialsRepository: SharedCredentialsRepository,
|
||||
private readonly urlService: UrlService,
|
||||
private readonly globalConfig: GlobalConfig,
|
||||
) {}
|
||||
|
||||
get baseUrl() {
|
||||
const restUrl = `${this.urlService.getInstanceBaseUrl()}/${config.getEnv('endpoints.rest')}`;
|
||||
const restUrl = `${this.urlService.getInstanceBaseUrl()}/${this.globalConfig.endpoints.rest}`;
|
||||
return `${restUrl}/oauth${this.oauthVersion}-credential`;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import type { Application, Request, Response, RequestHandler } from 'express';
|
|||
import { rateLimit as expressRateLimit } from 'express-rate-limit';
|
||||
|
||||
import { AuthService } from '@/auth/auth.service';
|
||||
import config from '@/config';
|
||||
import { UnauthenticatedError } from '@/errors/response-errors/unauthenticated.error';
|
||||
import { inProduction, RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||
import type { BooleanLicenseFeature } from '@/Interfaces';
|
||||
|
@ -12,7 +11,7 @@ import { License } from '@/License';
|
|||
import type { AuthenticatedRequest } from '@/requests';
|
||||
import { send } from '@/ResponseHelper'; // TODO: move `ResponseHelper.send` to this file
|
||||
import { userHasScope } from '@/permissions/checkAccess';
|
||||
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import type {
|
||||
AccessScope,
|
||||
Controller,
|
||||
|
@ -52,6 +51,7 @@ export class ControllerRegistry {
|
|||
constructor(
|
||||
private readonly license: License,
|
||||
private readonly authService: AuthService,
|
||||
private readonly globalConfig: GlobalConfig,
|
||||
) {}
|
||||
|
||||
activate(app: Application) {
|
||||
|
@ -64,7 +64,7 @@ export class ControllerRegistry {
|
|||
const metadata = registry.get(controllerClass)!;
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
const prefix = `/${config.getEnv('endpoints.rest')}/${metadata.basePath}`
|
||||
const prefix = `/${this.globalConfig.endpoints.rest}/${metadata.basePath}`
|
||||
.replace(/\/+/g, '/')
|
||||
.replace(/\/$/, '');
|
||||
app.use(prefix, router);
|
||||
|
|
|
@ -5,6 +5,8 @@ import { mock } from 'jest-mock-extended';
|
|||
import { PrometheusMetricsService } from '../prometheus-metrics.service';
|
||||
import type express from 'express';
|
||||
import type { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus';
|
||||
import { mockInstance } from '@test/mocking';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
|
||||
const mockMiddleware = (
|
||||
_req: express.Request,
|
||||
|
@ -16,13 +18,27 @@ jest.mock('prom-client');
|
|||
jest.mock('express-prom-bundle', () => jest.fn(() => mockMiddleware));
|
||||
|
||||
describe('PrometheusMetricsService', () => {
|
||||
beforeEach(() => {
|
||||
config.load(config.default);
|
||||
const globalConfig = mockInstance(GlobalConfig, {
|
||||
endpoints: {
|
||||
metrics: {
|
||||
prefix: 'n8n_',
|
||||
includeDefaultMetrics: true,
|
||||
includeApiEndpoints: true,
|
||||
includeCacheMetrics: true,
|
||||
includeMessageEventBusMetrics: true,
|
||||
includeCredentialTypeLabel: false,
|
||||
includeNodeTypeLabel: false,
|
||||
includeWorkflowIdLabel: false,
|
||||
includeApiPathLabel: true,
|
||||
includeApiMethodLabel: true,
|
||||
includeApiStatusCodeLabel: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('init', () => {
|
||||
it('should set up `n8n_version_info`', async () => {
|
||||
const service = new PrometheusMetricsService(mock(), mock());
|
||||
const service = new PrometheusMetricsService(mock(), mock(), globalConfig);
|
||||
|
||||
await service.init(mock<express.Application>());
|
||||
|
||||
|
@ -34,7 +50,7 @@ describe('PrometheusMetricsService', () => {
|
|||
});
|
||||
|
||||
it('should set up default metrics collection with `prom-client`', async () => {
|
||||
const service = new PrometheusMetricsService(mock(), mock());
|
||||
const service = new PrometheusMetricsService(mock(), mock(), globalConfig);
|
||||
|
||||
await service.init(mock<express.Application>());
|
||||
|
||||
|
@ -43,7 +59,7 @@ describe('PrometheusMetricsService', () => {
|
|||
|
||||
it('should set up `n8n_cache_hits_total`', async () => {
|
||||
config.set('endpoints.metrics.includeCacheMetrics', true);
|
||||
const service = new PrometheusMetricsService(mock(), mock());
|
||||
const service = new PrometheusMetricsService(mock(), mock(), globalConfig);
|
||||
|
||||
await service.init(mock<express.Application>());
|
||||
|
||||
|
@ -58,7 +74,7 @@ describe('PrometheusMetricsService', () => {
|
|||
|
||||
it('should set up `n8n_cache_misses_total`', async () => {
|
||||
config.set('endpoints.metrics.includeCacheMetrics', true);
|
||||
const service = new PrometheusMetricsService(mock(), mock());
|
||||
const service = new PrometheusMetricsService(mock(), mock(), globalConfig);
|
||||
|
||||
await service.init(mock<express.Application>());
|
||||
|
||||
|
@ -73,7 +89,7 @@ describe('PrometheusMetricsService', () => {
|
|||
|
||||
it('should set up `n8n_cache_updates_total`', async () => {
|
||||
config.set('endpoints.metrics.includeCacheMetrics', true);
|
||||
const service = new PrometheusMetricsService(mock(), mock());
|
||||
const service = new PrometheusMetricsService(mock(), mock(), globalConfig);
|
||||
|
||||
await service.init(mock<express.Application>());
|
||||
|
||||
|
@ -91,7 +107,7 @@ describe('PrometheusMetricsService', () => {
|
|||
config.set('endpoints.metrics.includeApiPathLabel', true);
|
||||
config.set('endpoints.metrics.includeApiMethodLabel', true);
|
||||
config.set('endpoints.metrics.includeApiStatusCodeLabel', true);
|
||||
const service = new PrometheusMetricsService(mock(), mock());
|
||||
const service = new PrometheusMetricsService(mock(), mock(), globalConfig);
|
||||
|
||||
const app = mock<express.Application>();
|
||||
|
||||
|
@ -122,7 +138,7 @@ describe('PrometheusMetricsService', () => {
|
|||
|
||||
it('should set up event bus metrics', async () => {
|
||||
const eventBus = mock<MessageEventBus>();
|
||||
const service = new PrometheusMetricsService(mock(), eventBus);
|
||||
const service = new PrometheusMetricsService(mock(), eventBus, globalConfig);
|
||||
|
||||
await service.init(mock<express.Application>());
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import config from '@/config';
|
||||
import { N8N_VERSION } from '@/constants';
|
||||
import type express from 'express';
|
||||
import promBundle from 'express-prom-bundle';
|
||||
|
@ -11,32 +10,34 @@ import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus';
|
|||
import { EventMessageTypeNames } from 'n8n-workflow';
|
||||
import type { EventMessageTypes } from '@/eventbus';
|
||||
import type { Includes, MetricCategory, MetricLabel } from './types';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
|
||||
@Service()
|
||||
export class PrometheusMetricsService {
|
||||
constructor(
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly eventBus: MessageEventBus,
|
||||
private readonly globalConfig: GlobalConfig,
|
||||
) {}
|
||||
|
||||
private readonly counters: { [key: string]: Counter<string> | null } = {};
|
||||
|
||||
private readonly prefix = config.getEnv('endpoints.metrics.prefix');
|
||||
private readonly prefix = this.globalConfig.endpoints.metrics.prefix;
|
||||
|
||||
private readonly includes: Includes = {
|
||||
metrics: {
|
||||
default: config.getEnv('endpoints.metrics.includeDefaultMetrics'),
|
||||
routes: config.getEnv('endpoints.metrics.includeApiEndpoints'),
|
||||
cache: config.getEnv('endpoints.metrics.includeCacheMetrics'),
|
||||
logs: config.getEnv('endpoints.metrics.includeMessageEventBusMetrics'),
|
||||
default: this.globalConfig.endpoints.metrics.includeDefaultMetrics,
|
||||
routes: this.globalConfig.endpoints.metrics.includeApiEndpoints,
|
||||
cache: this.globalConfig.endpoints.metrics.includeCacheMetrics,
|
||||
logs: this.globalConfig.endpoints.metrics.includeMessageEventBusMetrics,
|
||||
},
|
||||
labels: {
|
||||
credentialsType: config.getEnv('endpoints.metrics.includeCredentialTypeLabel'),
|
||||
nodeType: config.getEnv('endpoints.metrics.includeNodeTypeLabel'),
|
||||
workflowId: config.getEnv('endpoints.metrics.includeWorkflowIdLabel'),
|
||||
apiPath: config.getEnv('endpoints.metrics.includeApiPathLabel'),
|
||||
apiMethod: config.getEnv('endpoints.metrics.includeApiMethodLabel'),
|
||||
apiStatusCode: config.getEnv('endpoints.metrics.includeApiStatusCodeLabel'),
|
||||
credentialsType: this.globalConfig.endpoints.metrics.includeCredentialTypeLabel,
|
||||
nodeType: this.globalConfig.endpoints.metrics.includeNodeTypeLabel,
|
||||
workflowId: this.globalConfig.endpoints.metrics.includeWorkflowIdLabel,
|
||||
apiPath: this.globalConfig.endpoints.metrics.includeApiPathLabel,
|
||||
apiMethod: this.globalConfig.endpoints.metrics.includeApiMethodLabel,
|
||||
apiStatusCode: this.globalConfig.endpoints.metrics.includeApiStatusCodeLabel,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -6,8 +6,9 @@ import { parse as parseQueryString } from 'querystring';
|
|||
import { Parser as XmlParser } from 'xml2js';
|
||||
import { parseIncomingMessage } from 'n8n-core';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import config from '@/config';
|
||||
import { UnprocessableRequestError } from '@/errors/response-errors/unprocessable.error';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import Container from 'typedi';
|
||||
|
||||
const xmlParser = new XmlParser({
|
||||
async: true,
|
||||
|
@ -16,7 +17,7 @@ const xmlParser = new XmlParser({
|
|||
explicitArray: false, // Only put properties in array if length > 1
|
||||
});
|
||||
|
||||
const payloadSizeMax = config.getEnv('endpoints.payloadSizeMax');
|
||||
const payloadSizeMax = Container.get(GlobalConfig).endpoints.payloadSizeMax;
|
||||
export const rawBodyReader: RequestHandler = async (req, _res, next) => {
|
||||
parseIncomingMessage(req);
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ export class FrontendService {
|
|||
|
||||
private initSettings() {
|
||||
const instanceBaseUrl = this.urlService.getInstanceBaseUrl();
|
||||
const restEndpoint = config.getEnv('endpoints.rest');
|
||||
const restEndpoint = this.globalConfig.endpoints.rest;
|
||||
|
||||
const telemetrySettings: ITelemetrySettings = {
|
||||
enabled: config.getEnv('diagnostics.enabled'),
|
||||
|
@ -88,11 +88,11 @@ export class FrontendService {
|
|||
isDocker: this.isDocker(),
|
||||
databaseType: this.globalConfig.database.type,
|
||||
previewMode: process.env.N8N_PREVIEW_MODE === 'true',
|
||||
endpointForm: config.getEnv('endpoints.form'),
|
||||
endpointFormTest: config.getEnv('endpoints.formTest'),
|
||||
endpointFormWaiting: config.getEnv('endpoints.formWaiting'),
|
||||
endpointWebhook: config.getEnv('endpoints.webhook'),
|
||||
endpointWebhookTest: config.getEnv('endpoints.webhookTest'),
|
||||
endpointForm: this.globalConfig.endpoints.form,
|
||||
endpointFormTest: this.globalConfig.endpoints.formTest,
|
||||
endpointFormWaiting: this.globalConfig.endpoints.formWaiting,
|
||||
endpointWebhook: this.globalConfig.endpoints.webhook,
|
||||
endpointWebhookTest: this.globalConfig.endpoints.webhookTest,
|
||||
saveDataErrorExecution: config.getEnv('executions.saveDataOnError'),
|
||||
saveDataSuccessExecution: config.getEnv('executions.saveDataOnSuccess'),
|
||||
saveManualExecutions: config.getEnv('executions.saveDataManualExecutions'),
|
||||
|
@ -246,7 +246,7 @@ export class FrontendService {
|
|||
getSettings(pushRef?: string): IN8nUISettings {
|
||||
this.internalHooks.onFrontendSettingsAPI(pushRef);
|
||||
|
||||
const restEndpoint = config.getEnv('endpoints.rest');
|
||||
const restEndpoint = this.globalConfig.endpoints.rest;
|
||||
|
||||
// Update all urls, in case `WEBHOOK_URL` was updated by `--tunnel`
|
||||
const instanceBaseUrl = this.urlService.getInstanceBaseUrl();
|
||||
|
|
|
@ -444,9 +444,8 @@ export class TelemetryEventRelay {
|
|||
version_cli: N8N_VERSION,
|
||||
db_type: this.globalConfig.database.type,
|
||||
n8n_version_notifications_enabled: this.globalConfig.versionNotifications.enabled,
|
||||
n8n_disable_production_main_process: config.getEnv(
|
||||
'endpoints.disableProductionWebhooksOnMainProcess',
|
||||
),
|
||||
n8n_disable_production_main_process:
|
||||
this.globalConfig.endpoints.disableProductionWebhooksOnMainProcess,
|
||||
system_info: {
|
||||
os: {
|
||||
type: os.type(),
|
||||
|
|
|
@ -2,17 +2,48 @@ import { Container } from 'typedi';
|
|||
import { parse as semverParse } from 'semver';
|
||||
import request, { type Response } from 'supertest';
|
||||
|
||||
import config from '@/config';
|
||||
import { N8N_VERSION } from '@/constants';
|
||||
import { PrometheusMetricsService } from '@/metrics/prometheus-metrics.service';
|
||||
import { setupTestServer } from './shared/utils';
|
||||
import { mockInstance } from '@test/mocking';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
|
||||
jest.unmock('@/eventbus/MessageEventBus/MessageEventBus');
|
||||
|
||||
const toLines = (response: Response) => response.text.trim().split('\n');
|
||||
|
||||
config.set('endpoints.metrics.enable', true);
|
||||
config.set('endpoints.metrics.prefix', 'n8n_test_');
|
||||
mockInstance(GlobalConfig, {
|
||||
database: {
|
||||
type: 'sqlite',
|
||||
sqlite: {
|
||||
database: 'database.sqlite',
|
||||
enableWAL: false,
|
||||
executeVacuumOnStartup: false,
|
||||
poolSize: 0,
|
||||
},
|
||||
logging: {
|
||||
enabled: false,
|
||||
maxQueryExecutionTime: 0,
|
||||
options: 'error',
|
||||
},
|
||||
tablePrefix: '',
|
||||
},
|
||||
endpoints: {
|
||||
metrics: {
|
||||
prefix: 'n8n_test_',
|
||||
includeDefaultMetrics: true,
|
||||
includeApiEndpoints: true,
|
||||
includeCacheMetrics: true,
|
||||
includeMessageEventBusMetrics: true,
|
||||
includeCredentialTypeLabel: false,
|
||||
includeNodeTypeLabel: false,
|
||||
includeWorkflowIdLabel: false,
|
||||
includeApiPathLabel: true,
|
||||
includeApiMethodLabel: true,
|
||||
includeApiStatusCodeLabel: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const server = setupTestServer({ endpointGroups: ['metrics'] });
|
||||
const agent = request.agent(server.app);
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import config from '@/config';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import Container from 'typedi';
|
||||
|
||||
export const REST_PATH_SEGMENT = config.getEnv('endpoints.rest');
|
||||
export const REST_PATH_SEGMENT = Container.get(GlobalConfig).endpoints.rest;
|
||||
|
||||
export const PUBLIC_API_REST_PATH_SEGMENT = Container.get(GlobalConfig).publicApi.path;
|
||||
|
||||
|
|
|
@ -10,16 +10,18 @@ import { ControllerRegistry, Get, Licensed, RestController } from '@/decorators'
|
|||
import type { AuthService } from '@/auth/auth.service';
|
||||
import type { License } from '@/License';
|
||||
import type { SuperAgentTest } from '@test-integration/types';
|
||||
import type { GlobalConfig } from '@n8n/config';
|
||||
|
||||
describe('ControllerRegistry', () => {
|
||||
const license = mock<License>();
|
||||
const authService = mock<AuthService>();
|
||||
const globalConfig = mock<GlobalConfig>({ endpoints: { rest: 'rest' } });
|
||||
let agent: SuperAgentTest;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
const app = express();
|
||||
new ControllerRegistry(license, authService).activate(app);
|
||||
new ControllerRegistry(license, authService, globalConfig).activate(app);
|
||||
agent = testAgent(app);
|
||||
});
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import type SuperAgentTest from 'supertest/lib/agent';
|
|||
import { agent as testAgent } from 'supertest';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
|
||||
import config from '@/config';
|
||||
import { AbstractServer } from '@/AbstractServer';
|
||||
import { ActiveWebhooks } from '@/ActiveWebhooks';
|
||||
import { ExternalHooks } from '@/ExternalHooks';
|
||||
|
@ -13,6 +12,8 @@ import { WaitingForms } from '@/WaitingForms';
|
|||
import type { IResponseCallbackData } from '@/Interfaces';
|
||||
|
||||
import { mockInstance } from '../shared/mocking';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import Container from 'typedi';
|
||||
|
||||
let agent: SuperAgentTest;
|
||||
|
||||
|
@ -46,7 +47,7 @@ describe('WebhookServer', () => {
|
|||
for (const [key, manager] of tests) {
|
||||
describe(`for ${key}`, () => {
|
||||
it('should handle preflight requests', async () => {
|
||||
const pathPrefix = config.getEnv(`endpoints.${key}`);
|
||||
const pathPrefix = Container.get(GlobalConfig).endpoints[key];
|
||||
manager.getWebhookMethods.mockResolvedValueOnce(['GET']);
|
||||
|
||||
const response = await agent
|
||||
|
@ -60,7 +61,7 @@ describe('WebhookServer', () => {
|
|||
});
|
||||
|
||||
it('should handle regular requests', async () => {
|
||||
const pathPrefix = config.getEnv(`endpoints.${key}`);
|
||||
const pathPrefix = Container.get(GlobalConfig).endpoints[key];
|
||||
manager.getWebhookMethods.mockResolvedValueOnce(['GET']);
|
||||
manager.executeWebhook.mockResolvedValueOnce(
|
||||
mockResponse({ test: true }, { key: 'value ' }),
|
||||
|
|
Loading…
Reference in a new issue