mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 20:54:07 -08:00
refactor(core): Move all base URLs to UrlService (no-changelog) (#8141)
This change kept coming up in #6713, #7773, and #8135. So this PR moves the existing code without actually changing anything, to help get rid of some of the circular dependencies. ## Review / Merge checklist - [x] PR title and summary are descriptive.
This commit is contained in:
parent
517b050d0a
commit
baee47a276
|
@ -1,6 +1,5 @@
|
||||||
import type express from 'express';
|
import type express from 'express';
|
||||||
import { validate } from 'class-validator';
|
import { validate } from 'class-validator';
|
||||||
import config from '@/config';
|
|
||||||
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||||
import type { TagEntity } from '@db/entities/TagEntity';
|
import type { TagEntity } from '@db/entities/TagEntity';
|
||||||
|
@ -8,21 +7,6 @@ import type { User } from '@db/entities/User';
|
||||||
import type { UserUpdatePayload } from '@/requests';
|
import type { UserUpdatePayload } from '@/requests';
|
||||||
import { BadRequestError } from './errors/response-errors/bad-request.error';
|
import { BadRequestError } from './errors/response-errors/bad-request.error';
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the base URL n8n is reachable from
|
|
||||||
*/
|
|
||||||
export function getBaseUrl(): string {
|
|
||||||
const protocol = config.getEnv('protocol');
|
|
||||||
const host = config.getEnv('host');
|
|
||||||
const port = config.getEnv('port');
|
|
||||||
const path = config.getEnv('path');
|
|
||||||
|
|
||||||
if ((protocol === 'http' && port === 80) || (protocol === 'https' && port === 443)) {
|
|
||||||
return `${protocol}://${host}${path}`;
|
|
||||||
}
|
|
||||||
return `${protocol}://${host}:${port}${path}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the session id if one is set
|
* Returns the session id if one is set
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
import { Container } from 'typedi';
|
||||||
import type { Router } from 'express';
|
import type { Router } from 'express';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
|
@ -11,11 +12,11 @@ import type { OpenAPIV3 } from 'openapi-types';
|
||||||
import type { JsonObject } from 'swagger-ui-express';
|
import type { JsonObject } from 'swagger-ui-express';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
|
|
||||||
import { Container } from 'typedi';
|
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
import { License } from '@/License';
|
import { License } from '@/License';
|
||||||
import { UserRepository } from '@db/repositories/user.repository';
|
import { UserRepository } from '@db/repositories/user.repository';
|
||||||
|
import { UrlService } from '@/services/url.service';
|
||||||
|
|
||||||
async function createApiRouter(
|
async function createApiRouter(
|
||||||
version: string,
|
version: string,
|
||||||
|
@ -29,7 +30,7 @@ async function createApiRouter(
|
||||||
// from the Swagger UI
|
// from the Swagger UI
|
||||||
swaggerDocument.server = [
|
swaggerDocument.server = [
|
||||||
{
|
{
|
||||||
url: `${getInstanceBaseUrl()}/${publicApiEndpoint}/${version}}`,
|
url: `${Container.get(UrlService).getInstanceBaseUrl()}/${publicApiEndpoint}/${version}}`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const apiController = express.Router();
|
const apiController = express.Router();
|
||||||
|
|
|
@ -1,30 +1,15 @@
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
|
import type { Scope } from '@n8n/permissions';
|
||||||
|
|
||||||
import type { WhereClause } from '@/Interfaces';
|
import type { WhereClause } from '@/Interfaces';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import config from '@/config';
|
|
||||||
import { License } from '@/License';
|
import { License } from '@/License';
|
||||||
import { getWebhookBaseUrl } from '@/WebhookHelpers';
|
|
||||||
import type { Scope } from '@n8n/permissions';
|
|
||||||
|
|
||||||
export function isSharingEnabled(): boolean {
|
export function isSharingEnabled(): boolean {
|
||||||
return Container.get(License).isSharingEnabled();
|
return Container.get(License).isSharingEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the n8n instance base URL without trailing slash.
|
|
||||||
*/
|
|
||||||
export function getInstanceBaseUrl(): string {
|
|
||||||
const n8nBaseUrl = config.getEnv('editorBaseUrl') || getWebhookBaseUrl();
|
|
||||||
|
|
||||||
return n8nBaseUrl.endsWith('/') ? n8nBaseUrl.slice(0, n8nBaseUrl.length - 1) : n8nBaseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateUserInviteUrl(inviterId: string, inviteeId: string): string {
|
|
||||||
return `${getInstanceBaseUrl()}/signup?inviterId=${inviterId}&inviteeId=${inviteeId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the difference between two arrays
|
// return the difference between two arrays
|
||||||
export function rightDiff<T1, T2>(
|
export function rightDiff<T1, T2>(
|
||||||
[arr1, keyExtractor1]: [T1[], (item: T1) => string],
|
[arr1, keyExtractor1]: [T1[], (item: T1) => string],
|
||||||
|
|
|
@ -49,7 +49,6 @@ import type {
|
||||||
WebhookCORSRequest,
|
WebhookCORSRequest,
|
||||||
WebhookRequest,
|
WebhookRequest,
|
||||||
} from '@/Interfaces';
|
} from '@/Interfaces';
|
||||||
import * as GenericHelpers from '@/GenericHelpers';
|
|
||||||
import * as ResponseHelper from '@/ResponseHelper';
|
import * as ResponseHelper from '@/ResponseHelper';
|
||||||
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
||||||
import { WorkflowRunner } from '@/WorkflowRunner';
|
import { WorkflowRunner } from '@/WorkflowRunner';
|
||||||
|
@ -820,14 +819,3 @@ export async function executeWebhook(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the base URL of the webhooks
|
|
||||||
*/
|
|
||||||
export function getWebhookBaseUrl() {
|
|
||||||
let urlBaseWebhook = process.env.WEBHOOK_URL ?? GenericHelpers.getBaseUrl();
|
|
||||||
if (!urlBaseWebhook.endsWith('/')) {
|
|
||||||
urlBaseWebhook += '/';
|
|
||||||
}
|
|
||||||
return urlBaseWebhook;
|
|
||||||
}
|
|
||||||
|
|
|
@ -48,7 +48,6 @@ import type {
|
||||||
} from '@/Interfaces';
|
} from '@/Interfaces';
|
||||||
import { NodeTypes } from '@/NodeTypes';
|
import { NodeTypes } from '@/NodeTypes';
|
||||||
import { Push } from '@/push';
|
import { Push } from '@/push';
|
||||||
import * as WebhookHelpers from '@/WebhookHelpers';
|
|
||||||
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
||||||
import { findSubworkflowStart, isWorkflowIdValid } from '@/utils';
|
import { findSubworkflowStart, isWorkflowIdValid } from '@/utils';
|
||||||
import { PermissionChecker } from './UserManagement/PermissionChecker';
|
import { PermissionChecker } from './UserManagement/PermissionChecker';
|
||||||
|
@ -68,6 +67,7 @@ import { Logger } from './Logger';
|
||||||
import { saveExecutionProgress } from './executionLifecycleHooks/saveExecutionProgress';
|
import { saveExecutionProgress } from './executionLifecycleHooks/saveExecutionProgress';
|
||||||
import { WorkflowStaticDataService } from './workflows/workflowStaticData.service';
|
import { WorkflowStaticDataService } from './workflows/workflowStaticData.service';
|
||||||
import { WorkflowRepository } from './databases/repositories/workflow.repository';
|
import { WorkflowRepository } from './databases/repositories/workflow.repository';
|
||||||
|
import { UrlService } from './services/url.service';
|
||||||
|
|
||||||
const ERROR_TRIGGER_TYPE = config.getEnv('nodes.errorTriggerType');
|
const ERROR_TRIGGER_TYPE = config.getEnv('nodes.errorTriggerType');
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ export function executeErrorWorkflow(
|
||||||
// Check if there was an error and if so if an errorWorkflow or a trigger is set
|
// Check if there was an error and if so if an errorWorkflow or a trigger is set
|
||||||
let pastExecutionUrl: string | undefined;
|
let pastExecutionUrl: string | undefined;
|
||||||
if (executionId !== undefined) {
|
if (executionId !== undefined) {
|
||||||
pastExecutionUrl = `${WebhookHelpers.getWebhookBaseUrl()}workflow/${
|
pastExecutionUrl = `${Container.get(UrlService).getWebhookBaseUrl()}workflow/${
|
||||||
workflowData.id
|
workflowData.id
|
||||||
}/executions/${executionId}`;
|
}/executions/${executionId}`;
|
||||||
}
|
}
|
||||||
|
@ -965,7 +965,7 @@ export async function getBase(
|
||||||
currentNodeParameters?: INodeParameters,
|
currentNodeParameters?: INodeParameters,
|
||||||
executionTimeoutTimestamp?: number,
|
executionTimeoutTimestamp?: number,
|
||||||
): Promise<IWorkflowExecuteAdditionalData> {
|
): Promise<IWorkflowExecuteAdditionalData> {
|
||||||
const urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
|
const urlBaseWebhook = Container.get(UrlService).getWebhookBaseUrl();
|
||||||
|
|
||||||
const formWaitingBaseUrl = urlBaseWebhook + config.getEnv('endpoints.formWaiting');
|
const formWaitingBaseUrl = urlBaseWebhook + config.getEnv('endpoints.formWaiting');
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ import config from '@/config';
|
||||||
|
|
||||||
import { ActiveExecutions } from '@/ActiveExecutions';
|
import { ActiveExecutions } from '@/ActiveExecutions';
|
||||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||||
import * as GenericHelpers from '@/GenericHelpers';
|
|
||||||
import { Server } from '@/Server';
|
import { Server } from '@/Server';
|
||||||
import { EDITOR_UI_DIST_DIR, LICENSE_FEATURES } from '@/constants';
|
import { EDITOR_UI_DIST_DIR, LICENSE_FEATURES } from '@/constants';
|
||||||
import { eventBus } from '@/eventbus';
|
import { eventBus } from '@/eventbus';
|
||||||
|
@ -27,6 +26,7 @@ import { SingleMainSetup } from '@/services/orchestration/main/SingleMainSetup';
|
||||||
import { OrchestrationHandlerMainService } from '@/services/orchestration/main/orchestration.handler.main.service';
|
import { OrchestrationHandlerMainService } from '@/services/orchestration/main/orchestration.handler.main.service';
|
||||||
import { PruningService } from '@/services/pruning.service';
|
import { PruningService } from '@/services/pruning.service';
|
||||||
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
||||||
|
import { UrlService } from '@/services/url.service';
|
||||||
import { SettingsRepository } from '@db/repositories/settings.repository';
|
import { SettingsRepository } from '@db/repositories/settings.repository';
|
||||||
import { ExecutionRepository } from '@db/repositories/execution.repository';
|
import { ExecutionRepository } from '@db/repositories/execution.repository';
|
||||||
import { FeatureNotLicensedError } from '@/errors/feature-not-licensed.error';
|
import { FeatureNotLicensedError } from '@/errors/feature-not-licensed.error';
|
||||||
|
@ -77,7 +77,7 @@ export class Start extends BaseCommand {
|
||||||
* Opens the UI in browser
|
* Opens the UI in browser
|
||||||
*/
|
*/
|
||||||
private openBrowser() {
|
private openBrowser() {
|
||||||
const editorUrl = GenericHelpers.getBaseUrl();
|
const editorUrl = Container.get(UrlService).baseUrl;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
open(editorUrl, { wait: true }).catch((error: Error) => {
|
open(editorUrl, { wait: true }).catch((error: Error) => {
|
||||||
|
@ -321,7 +321,7 @@ export class Start extends BaseCommand {
|
||||||
// Start to get active workflows and run their triggers
|
// Start to get active workflows and run their triggers
|
||||||
await this.activeWorkflowRunner.init();
|
await this.activeWorkflowRunner.init();
|
||||||
|
|
||||||
const editorUrl = GenericHelpers.getBaseUrl();
|
const editorUrl = Container.get(UrlService).baseUrl;
|
||||||
this.log(`\nEditor is now accessible via:\n${editorUrl}`);
|
this.log(`\nEditor is now accessible via:\n${editorUrl}`);
|
||||||
|
|
||||||
// Allow to open n8n editor by pressing "o"
|
// Allow to open n8n editor by pressing "o"
|
||||||
|
|
|
@ -7,13 +7,13 @@ import type { User } from '@db/entities/User';
|
||||||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||||
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
||||||
import type { ICredentialsDb } from '@/Interfaces';
|
import type { ICredentialsDb } from '@/Interfaces';
|
||||||
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
|
|
||||||
import type { OAuthRequest } from '@/requests';
|
import type { OAuthRequest } from '@/requests';
|
||||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||||
import { CredentialsHelper } from '@/CredentialsHelper';
|
import { CredentialsHelper } from '@/CredentialsHelper';
|
||||||
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
|
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
|
||||||
import { Logger } from '@/Logger';
|
import { Logger } from '@/Logger';
|
||||||
import { ExternalHooks } from '@/ExternalHooks';
|
import { ExternalHooks } from '@/ExternalHooks';
|
||||||
|
import { UrlService } from '@/services/url.service';
|
||||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||||
|
|
||||||
|
@ -27,10 +27,11 @@ export abstract class AbstractOAuthController {
|
||||||
private readonly credentialsHelper: CredentialsHelper,
|
private readonly credentialsHelper: CredentialsHelper,
|
||||||
private readonly credentialsRepository: CredentialsRepository,
|
private readonly credentialsRepository: CredentialsRepository,
|
||||||
private readonly sharedCredentialsRepository: SharedCredentialsRepository,
|
private readonly sharedCredentialsRepository: SharedCredentialsRepository,
|
||||||
|
private readonly urlService: UrlService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get baseUrl() {
|
get baseUrl() {
|
||||||
const restUrl = `${getInstanceBaseUrl()}/${config.getEnv('endpoints.rest')}`;
|
const restUrl = `${this.urlService.getInstanceBaseUrl()}/${config.getEnv('endpoints.rest')}`;
|
||||||
return `${restUrl}/oauth${this.oauthVersion}-credential`;
|
return `${restUrl}/oauth${this.oauthVersion}-credential`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { IsNull, Not } from 'typeorm';
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
|
|
||||||
import { Get, Post, RestController } from '@/decorators';
|
import { Get, Post, RestController } from '@/decorators';
|
||||||
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
|
|
||||||
import { PasswordUtility } from '@/services/password.utility';
|
import { PasswordUtility } from '@/services/password.utility';
|
||||||
import { UserManagementMailer } from '@/UserManagement/email';
|
import { UserManagementMailer } from '@/UserManagement/email';
|
||||||
import { PasswordResetRequest } from '@/requests';
|
import { PasswordResetRequest } from '@/requests';
|
||||||
|
@ -19,6 +18,7 @@ import { MfaService } from '@/Mfa/mfa.service';
|
||||||
import { Logger } from '@/Logger';
|
import { Logger } from '@/Logger';
|
||||||
import { ExternalHooks } from '@/ExternalHooks';
|
import { ExternalHooks } from '@/ExternalHooks';
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
|
import { UrlService } from '@/services/url.service';
|
||||||
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||||
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
|
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
|
||||||
|
@ -41,6 +41,7 @@ export class PasswordResetController {
|
||||||
private readonly mailer: UserManagementMailer,
|
private readonly mailer: UserManagementMailer,
|
||||||
private readonly userService: UserService,
|
private readonly userService: UserService,
|
||||||
private readonly mfaService: MfaService,
|
private readonly mfaService: MfaService,
|
||||||
|
private readonly urlService: UrlService,
|
||||||
private readonly license: License,
|
private readonly license: License,
|
||||||
private readonly passwordUtility: PasswordUtility,
|
private readonly passwordUtility: PasswordUtility,
|
||||||
) {}
|
) {}
|
||||||
|
@ -130,7 +131,7 @@ export class PasswordResetController {
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
passwordResetUrl: url,
|
passwordResetUrl: url,
|
||||||
domain: getInstanceBaseUrl(),
|
domain: this.urlService.getInstanceBaseUrl(),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
void this.internalHooks.onEmailFailed({
|
void this.internalHooks.onEmailFailed({
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Container, Service } from 'typedi';
|
||||||
import type { Variables } from '@db/entities/Variables';
|
import type { Variables } from '@db/entities/Variables';
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
import { generateNanoId } from '@db/utils/generators';
|
import { generateNanoId } from '@db/utils/generators';
|
||||||
import { canCreateNewVariable } from './enviromentHelpers';
|
import { canCreateNewVariable } from './environmentHelpers';
|
||||||
import { CacheService } from '@/services/cache.service';
|
import { CacheService } from '@/services/cache.service';
|
||||||
import { VariablesRepository } from '@db/repositories/variables.repository';
|
import { VariablesRepository } from '@db/repositories/variables.repository';
|
||||||
import type { DeepPartial } from 'typeorm';
|
import type { DeepPartial } from 'typeorm';
|
||||||
|
|
|
@ -12,18 +12,16 @@ import type {
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { InstanceSettings } from 'n8n-core';
|
import { InstanceSettings } from 'n8n-core';
|
||||||
|
|
||||||
|
import config from '@/config';
|
||||||
import { LICENSE_FEATURES } from '@/constants';
|
import { LICENSE_FEATURES } from '@/constants';
|
||||||
import { CredentialsOverwrites } from '@/CredentialsOverwrites';
|
import { CredentialsOverwrites } from '@/CredentialsOverwrites';
|
||||||
import { CredentialTypes } from '@/CredentialTypes';
|
import { CredentialTypes } from '@/CredentialTypes';
|
||||||
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
||||||
import { License } from '@/License';
|
import { License } from '@/License';
|
||||||
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
|
|
||||||
import * as WebhookHelpers from '@/WebhookHelpers';
|
|
||||||
import config from '@/config';
|
|
||||||
import { getCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
|
import { getCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
|
||||||
import { getLdapLoginLabel } from '@/Ldap/helpers';
|
import { getLdapLoginLabel } from '@/Ldap/helpers';
|
||||||
import { getSamlLoginLabel } from '@/sso/saml/samlHelpers';
|
import { getSamlLoginLabel } from '@/sso/saml/samlHelpers';
|
||||||
import { getVariablesLimit } from '@/environments/variables/enviromentHelpers';
|
import { getVariablesLimit } from '@/environments/variables/environmentHelpers';
|
||||||
import {
|
import {
|
||||||
getWorkflowHistoryLicensePruneTime,
|
getWorkflowHistoryLicensePruneTime,
|
||||||
getWorkflowHistoryPruneTime,
|
getWorkflowHistoryPruneTime,
|
||||||
|
@ -31,6 +29,7 @@ import {
|
||||||
import { UserManagementMailer } from '@/UserManagement/email';
|
import { UserManagementMailer } from '@/UserManagement/email';
|
||||||
import type { CommunityPackagesService } from '@/services/communityPackages.service';
|
import type { CommunityPackagesService } from '@/services/communityPackages.service';
|
||||||
import { Logger } from '@/Logger';
|
import { Logger } from '@/Logger';
|
||||||
|
import { UrlService } from './url.service';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class FrontendService {
|
export class FrontendService {
|
||||||
|
@ -46,6 +45,7 @@ export class FrontendService {
|
||||||
private readonly license: License,
|
private readonly license: License,
|
||||||
private readonly mailer: UserManagementMailer,
|
private readonly mailer: UserManagementMailer,
|
||||||
private readonly instanceSettings: InstanceSettings,
|
private readonly instanceSettings: InstanceSettings,
|
||||||
|
private readonly urlService: UrlService,
|
||||||
) {
|
) {
|
||||||
loadNodesAndCredentials.addPostProcessor(async () => this.generateTypes());
|
loadNodesAndCredentials.addPostProcessor(async () => this.generateTypes());
|
||||||
void this.generateTypes();
|
void this.generateTypes();
|
||||||
|
@ -61,7 +61,7 @@ export class FrontendService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private initSettings() {
|
private initSettings() {
|
||||||
const instanceBaseUrl = getInstanceBaseUrl();
|
const instanceBaseUrl = this.urlService.getInstanceBaseUrl();
|
||||||
const restEndpoint = config.getEnv('endpoints.rest');
|
const restEndpoint = config.getEnv('endpoints.rest');
|
||||||
|
|
||||||
const telemetrySettings: ITelemetrySettings = {
|
const telemetrySettings: ITelemetrySettings = {
|
||||||
|
@ -93,7 +93,7 @@ export class FrontendService {
|
||||||
maxExecutionTimeout: config.getEnv('executions.maxTimeout'),
|
maxExecutionTimeout: config.getEnv('executions.maxTimeout'),
|
||||||
workflowCallerPolicyDefaultOption: config.getEnv('workflows.callerPolicyDefaultOption'),
|
workflowCallerPolicyDefaultOption: config.getEnv('workflows.callerPolicyDefaultOption'),
|
||||||
timezone: config.getEnv('generic.timezone'),
|
timezone: config.getEnv('generic.timezone'),
|
||||||
urlBaseWebhook: WebhookHelpers.getWebhookBaseUrl(),
|
urlBaseWebhook: this.urlService.getWebhookBaseUrl(),
|
||||||
urlBaseEditor: instanceBaseUrl,
|
urlBaseEditor: instanceBaseUrl,
|
||||||
versionCli: '',
|
versionCli: '',
|
||||||
releaseChannel: config.getEnv('generic.releaseChannel'),
|
releaseChannel: config.getEnv('generic.releaseChannel'),
|
||||||
|
@ -222,8 +222,8 @@ export class FrontendService {
|
||||||
const restEndpoint = config.getEnv('endpoints.rest');
|
const restEndpoint = config.getEnv('endpoints.rest');
|
||||||
|
|
||||||
// Update all urls, in case `WEBHOOK_URL` was updated by `--tunnel`
|
// Update all urls, in case `WEBHOOK_URL` was updated by `--tunnel`
|
||||||
const instanceBaseUrl = getInstanceBaseUrl();
|
const instanceBaseUrl = this.urlService.getInstanceBaseUrl();
|
||||||
this.settings.urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
|
this.settings.urlBaseWebhook = this.urlService.getWebhookBaseUrl();
|
||||||
this.settings.urlBaseEditor = instanceBaseUrl;
|
this.settings.urlBaseEditor = instanceBaseUrl;
|
||||||
this.settings.oauthCallbackUrls = {
|
this.settings.oauthCallbackUrls = {
|
||||||
oauth1: `${instanceBaseUrl}/${restEndpoint}/oauth1-credential/callback`,
|
oauth1: `${instanceBaseUrl}/${restEndpoint}/oauth1-credential/callback`,
|
||||||
|
|
40
packages/cli/src/services/url.service.ts
Normal file
40
packages/cli/src/services/url.service.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { Service } from 'typedi';
|
||||||
|
import config from '@/config';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class UrlService {
|
||||||
|
/** Returns the base URL n8n is reachable from */
|
||||||
|
readonly baseUrl: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.baseUrl = this.generateBaseUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the base URL of the webhooks */
|
||||||
|
getWebhookBaseUrl() {
|
||||||
|
let urlBaseWebhook = process.env.WEBHOOK_URL ?? this.baseUrl;
|
||||||
|
if (!urlBaseWebhook.endsWith('/')) {
|
||||||
|
urlBaseWebhook += '/';
|
||||||
|
}
|
||||||
|
return urlBaseWebhook;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the n8n instance base URL without trailing slash */
|
||||||
|
getInstanceBaseUrl(): string {
|
||||||
|
const n8nBaseUrl = config.getEnv('editorBaseUrl') || this.getWebhookBaseUrl();
|
||||||
|
|
||||||
|
return n8nBaseUrl.endsWith('/') ? n8nBaseUrl.slice(0, n8nBaseUrl.length - 1) : n8nBaseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateBaseUrl(): string {
|
||||||
|
const protocol = config.getEnv('protocol');
|
||||||
|
const host = config.getEnv('host');
|
||||||
|
const port = config.getEnv('port');
|
||||||
|
const path = config.getEnv('path');
|
||||||
|
|
||||||
|
if ((protocol === 'http' && port === 80) || (protocol === 'https' && port === 443)) {
|
||||||
|
return `${protocol}://${host}${path}`;
|
||||||
|
}
|
||||||
|
return `${protocol}://${host}:${port}${path}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
import Container, { Service } from 'typedi';
|
import { Container, Service } from 'typedi';
|
||||||
import type { EntityManager, FindManyOptions, FindOneOptions, FindOptionsWhere } from 'typeorm';
|
import type { EntityManager, FindManyOptions, FindOneOptions, FindOptionsWhere } from 'typeorm';
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
import { User } from '@db/entities/User';
|
import { User } from '@db/entities/User';
|
||||||
import type { IUserSettings } from 'n8n-workflow';
|
import type { IUserSettings } from 'n8n-workflow';
|
||||||
import { UserRepository } from '@db/repositories/user.repository';
|
import { UserRepository } from '@db/repositories/user.repository';
|
||||||
import { generateUserInviteUrl, getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
|
|
||||||
import type { PublicUser } from '@/Interfaces';
|
import type { PublicUser } from '@/Interfaces';
|
||||||
import type { PostHogClient } from '@/posthog';
|
import type { PostHogClient } from '@/posthog';
|
||||||
import { type JwtPayload, JwtService } from './jwt.service';
|
import { type JwtPayload, JwtService } from './jwt.service';
|
||||||
|
@ -14,6 +13,7 @@ import { createPasswordSha } from '@/auth/jwt';
|
||||||
import { UserManagementMailer } from '@/UserManagement/email';
|
import { UserManagementMailer } from '@/UserManagement/email';
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
import { RoleService } from '@/services/role.service';
|
import { RoleService } from '@/services/role.service';
|
||||||
|
import { UrlService } from '@/services/url.service';
|
||||||
import { ApplicationError, ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
|
import { ApplicationError, ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
|
||||||
import type { UserRequest } from '@/requests';
|
import type { UserRequest } from '@/requests';
|
||||||
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||||
|
@ -26,6 +26,7 @@ export class UserService {
|
||||||
private readonly jwtService: JwtService,
|
private readonly jwtService: JwtService,
|
||||||
private readonly mailer: UserManagementMailer,
|
private readonly mailer: UserManagementMailer,
|
||||||
private readonly roleService: RoleService,
|
private readonly roleService: RoleService,
|
||||||
|
private readonly urlService: UrlService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async findOne(options: FindOneOptions<User>) {
|
async findOne(options: FindOneOptions<User>) {
|
||||||
|
@ -78,7 +79,7 @@ export class UserService {
|
||||||
}
|
}
|
||||||
|
|
||||||
generatePasswordResetUrl(user: User) {
|
generatePasswordResetUrl(user: User) {
|
||||||
const instanceBaseUrl = getInstanceBaseUrl();
|
const instanceBaseUrl = this.urlService.getInstanceBaseUrl();
|
||||||
const url = new URL(`${instanceBaseUrl}/change-password`);
|
const url = new URL(`${instanceBaseUrl}/change-password`);
|
||||||
|
|
||||||
url.searchParams.append('token', this.generatePasswordResetToken(user));
|
url.searchParams.append('token', this.generatePasswordResetToken(user));
|
||||||
|
@ -161,7 +162,7 @@ export class UserService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private addInviteUrl(inviterId: string, invitee: PublicUser) {
|
private addInviteUrl(inviterId: string, invitee: PublicUser) {
|
||||||
const url = new URL(getInstanceBaseUrl());
|
const url = new URL(this.urlService.getInstanceBaseUrl());
|
||||||
url.pathname = '/signup';
|
url.pathname = '/signup';
|
||||||
url.searchParams.set('inviterId', inviterId);
|
url.searchParams.set('inviterId', inviterId);
|
||||||
url.searchParams.set('inviteeId', invitee.id);
|
url.searchParams.set('inviteeId', invitee.id);
|
||||||
|
@ -193,11 +194,11 @@ export class UserService {
|
||||||
toInviteUsers: { [key: string]: string },
|
toInviteUsers: { [key: string]: string },
|
||||||
role: 'member' | 'admin',
|
role: 'member' | 'admin',
|
||||||
) {
|
) {
|
||||||
const domain = getInstanceBaseUrl();
|
const domain = this.urlService.getInstanceBaseUrl();
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
Object.entries(toInviteUsers).map(async ([email, id]) => {
|
Object.entries(toInviteUsers).map(async ([email, id]) => {
|
||||||
const inviteAcceptUrl = generateUserInviteUrl(owner.id, id);
|
const inviteAcceptUrl = `${domain}/signup?inviterId=${owner.id}&inviteeId=${id}`;
|
||||||
const invitedUser: UserRequest.InviteResponse = {
|
const invitedUser: UserRequest.InviteResponse = {
|
||||||
user: {
|
user: {
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { Container, Service } from 'typedi';
|
import { Container, Service } from 'typedi';
|
||||||
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
|
|
||||||
import {
|
import {
|
||||||
Authorized,
|
Authorized,
|
||||||
Get,
|
Get,
|
||||||
|
@ -35,12 +34,16 @@ import url from 'url';
|
||||||
import querystring from 'querystring';
|
import querystring from 'querystring';
|
||||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||||
import { AuthError } from '@/errors/response-errors/auth.error';
|
import { AuthError } from '@/errors/response-errors/auth.error';
|
||||||
|
import { UrlService } from '@/services/url.service';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@Authorized()
|
@Authorized()
|
||||||
@RestController('/sso/saml')
|
@RestController('/sso/saml')
|
||||||
export class SamlController {
|
export class SamlController {
|
||||||
constructor(private samlService: SamlService) {}
|
constructor(
|
||||||
|
private readonly samlService: SamlService,
|
||||||
|
private readonly urlService: UrlService,
|
||||||
|
) {}
|
||||||
|
|
||||||
@NoAuthRequired()
|
@NoAuthRequired()
|
||||||
@Get(SamlUrls.metadata)
|
@Get(SamlUrls.metadata)
|
||||||
|
@ -147,10 +150,10 @@ export class SamlController {
|
||||||
if (isSamlLicensedAndEnabled()) {
|
if (isSamlLicensedAndEnabled()) {
|
||||||
await issueCookie(res, loginResult.authenticatedUser);
|
await issueCookie(res, loginResult.authenticatedUser);
|
||||||
if (loginResult.onboardingRequired) {
|
if (loginResult.onboardingRequired) {
|
||||||
return res.redirect(getInstanceBaseUrl() + SamlUrls.samlOnboarding);
|
return res.redirect(this.urlService.getInstanceBaseUrl() + SamlUrls.samlOnboarding);
|
||||||
} else {
|
} else {
|
||||||
const redirectUrl = req.body?.RelayState ?? SamlUrls.defaultRedirect;
|
const redirectUrl = req.body?.RelayState ?? SamlUrls.defaultRedirect;
|
||||||
return res.redirect(getInstanceBaseUrl() + redirectUrl);
|
return res.redirect(this.urlService.getInstanceBaseUrl() + redirectUrl);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return res.status(202).send(loginResult.attributes);
|
return res.status(202).send(loginResult.attributes);
|
||||||
|
|
|
@ -24,12 +24,12 @@ import axios from 'axios';
|
||||||
import https from 'https';
|
import https from 'https';
|
||||||
import type { SamlLoginBinding } from './types';
|
import type { SamlLoginBinding } from './types';
|
||||||
import { validateMetadata, validateResponse } from './samlValidator';
|
import { validateMetadata, validateResponse } from './samlValidator';
|
||||||
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
|
|
||||||
import { Logger } from '@/Logger';
|
import { Logger } from '@/Logger';
|
||||||
import { UserRepository } from '@db/repositories/user.repository';
|
import { UserRepository } from '@db/repositories/user.repository';
|
||||||
import { SettingsRepository } from '@db/repositories/settings.repository';
|
import { SettingsRepository } from '@db/repositories/settings.repository';
|
||||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||||
import { AuthError } from '@/errors/response-errors/auth.error';
|
import { AuthError } from '@/errors/response-errors/auth.error';
|
||||||
|
import { UrlService } from '@/services/url.service';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SamlService {
|
export class SamlService {
|
||||||
|
@ -55,7 +55,7 @@ export class SamlService {
|
||||||
loginLabel: 'SAML',
|
loginLabel: 'SAML',
|
||||||
wantAssertionsSigned: true,
|
wantAssertionsSigned: true,
|
||||||
wantMessageSigned: true,
|
wantMessageSigned: true,
|
||||||
relayState: getInstanceBaseUrl(),
|
relayState: this.urlService.getInstanceBaseUrl(),
|
||||||
signatureConfig: {
|
signatureConfig: {
|
||||||
prefix: 'ds',
|
prefix: 'ds',
|
||||||
location: {
|
location: {
|
||||||
|
@ -73,7 +73,10 @@ export class SamlService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private readonly logger: Logger) {}
|
constructor(
|
||||||
|
private readonly logger: Logger,
|
||||||
|
private readonly urlService: UrlService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async init(): Promise<void> {
|
async init(): Promise<void> {
|
||||||
// load preferences first but do not apply so as to not load samlify unnecessarily
|
// load preferences first but do not apply so as to not load samlify unnecessarily
|
||||||
|
@ -143,14 +146,14 @@ export class SamlService {
|
||||||
|
|
||||||
private getRedirectLoginRequestUrl(relayState?: string): BindingContext {
|
private getRedirectLoginRequestUrl(relayState?: string): BindingContext {
|
||||||
const sp = this.getServiceProviderInstance();
|
const sp = this.getServiceProviderInstance();
|
||||||
sp.entitySetting.relayState = relayState ?? getInstanceBaseUrl();
|
sp.entitySetting.relayState = relayState ?? this.urlService.getInstanceBaseUrl();
|
||||||
const loginRequest = sp.createLoginRequest(this.getIdentityProviderInstance(), 'redirect');
|
const loginRequest = sp.createLoginRequest(this.getIdentityProviderInstance(), 'redirect');
|
||||||
return loginRequest;
|
return loginRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPostLoginRequestUrl(relayState?: string): PostBindingContext {
|
private getPostLoginRequestUrl(relayState?: string): PostBindingContext {
|
||||||
const sp = this.getServiceProviderInstance();
|
const sp = this.getServiceProviderInstance();
|
||||||
sp.entitySetting.relayState = relayState ?? getInstanceBaseUrl();
|
sp.entitySetting.relayState = relayState ?? this.urlService.getInstanceBaseUrl();
|
||||||
const loginRequest = sp.createLoginRequest(
|
const loginRequest = sp.createLoginRequest(
|
||||||
this.getIdentityProviderInstance(),
|
this.getIdentityProviderInstance(),
|
||||||
'post',
|
'post',
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
|
import { Container } from 'typedi';
|
||||||
import type { ServiceProviderInstance } from 'samlify';
|
import type { ServiceProviderInstance } from 'samlify';
|
||||||
|
import { UrlService } from '@/services/url.service';
|
||||||
import { SamlUrls } from './constants';
|
import { SamlUrls } from './constants';
|
||||||
import type { SamlPreferences } from './types/samlPreferences';
|
import type { SamlPreferences } from './types/samlPreferences';
|
||||||
|
|
||||||
let serviceProviderInstance: ServiceProviderInstance | undefined;
|
let serviceProviderInstance: ServiceProviderInstance | undefined;
|
||||||
|
|
||||||
export function getServiceProviderEntityId(): string {
|
export function getServiceProviderEntityId(): string {
|
||||||
return getInstanceBaseUrl() + SamlUrls.restMetadata;
|
return Container.get(UrlService).getInstanceBaseUrl() + SamlUrls.restMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getServiceProviderReturnUrl(): string {
|
export function getServiceProviderReturnUrl(): string {
|
||||||
return getInstanceBaseUrl() + SamlUrls.restAcs;
|
return Container.get(UrlService).getInstanceBaseUrl() + SamlUrls.restAcs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getServiceProviderConfigTestReturnUrl(): string {
|
export function getServiceProviderConfigTestReturnUrl(): string {
|
||||||
return getInstanceBaseUrl() + SamlUrls.configTestReturn;
|
return Container.get(UrlService).getInstanceBaseUrl() + SamlUrls.configTestReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO:SAML: make these configurable for the end user
|
// TODO:SAML: make these configurable for the end user
|
||||||
|
|
Loading…
Reference in a new issue