Skip to content

Commit

Permalink
refactor: Move getInstaceBaseUrl to the InstanceService (no-changelog)
Browse files Browse the repository at this point in the history
  • Loading branch information
RicardoE105 committed Nov 21, 2023
1 parent b7c5c74 commit 0a14156
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 56 deletions.
6 changes: 4 additions & 2 deletions packages/cli/src/PublicApi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import type { OpenAPIV3 } from 'openapi-types';
import type { JsonObject } from 'swagger-ui-express';

import config from '@/config';
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
import { Container } from 'typedi';
import { InternalHooks } from '@/InternalHooks';
import { License } from '@/License';
import { UserRepository } from '@db/repositories/user.repository';
import { InstanceService } from '@/services/instance.service';

async function createApiRouter(
version: string,
Expand All @@ -29,7 +29,9 @@ async function createApiRouter(
// from the Swagger UI
swaggerDocument.server = [
{
url: `${getInstanceBaseUrl()}/${publicApiEndpoint}/${version}}`,
url: `${Container.get(
InstanceService,
).getInstanceBaseUrl()}/${publicApiEndpoint}/${version}}`,
},
];
const apiController = express.Router();
Expand Down
15 changes: 0 additions & 15 deletions packages/cli/src/UserManagement/UserManagementHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import * as ResponseHelper from '@/ResponseHelper';
import type { WhereClause } from '@/Interfaces';
import type { User } from '@db/entities/User';
import { MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH } from '@db/entities/User';
import config from '@/config';
import { License } from '@/License';
import { getWebhookBaseUrl } from '@/WebhookHelpers';
import { RoleService } from '@/services/role.service';
import { UserRepository } from '@db/repositories/user.repository';

Expand All @@ -27,19 +25,6 @@ export async function getInstanceOwner() {
});
}

/**
* 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}`;
}

// TODO: Enforce at model level
export function validatePassword(password?: string): string {
if (!password) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import type { User } from '@db/entities/User';
import { CredentialsRepository } from '@db/repositories/credentials.repository';
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
import type { ICredentialsDb } from '@/Interfaces';
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
import type { OAuthRequest } from '@/requests';
import { BadRequestError, NotFoundError } from '@/ResponseHelper';
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
import { CredentialsHelper } from '@/CredentialsHelper';
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
import { Logger } from '@/Logger';
import { ExternalHooks } from '@/ExternalHooks';
import { InstanceService } from '@/services/instance.service';

@Service()
export abstract class AbstractOAuthController {
Expand All @@ -26,10 +26,13 @@ export abstract class AbstractOAuthController {
private readonly credentialsHelper: CredentialsHelper,
private readonly credentialsRepository: CredentialsRepository,
private readonly sharedCredentialsRepository: SharedCredentialsRepository,
private readonly instanceService: InstanceService,
) {}

get baseUrl() {
const restUrl = `${getInstanceBaseUrl()}/${config.getEnv('endpoints.rest')}`;
const restUrl = `${this.instanceService.getInstanceBaseUrl()}/${config.getEnv(
'endpoints.rest',
)}`;
return `${restUrl}/oauth${this.oauthVersion}-credential`;
}

Expand Down
10 changes: 4 additions & 6 deletions packages/cli/src/controllers/passwordReset.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@ import {
UnauthorizedError,
UnprocessableRequestError,
} from '@/ResponseHelper';
import {
getInstanceBaseUrl,
hashPassword,
validatePassword,
} from '@/UserManagement/UserManagementHelper';
import { hashPassword, validatePassword } from '@/UserManagement/UserManagementHelper';
import { UserManagementMailer } from '@/UserManagement/email';
import { PasswordResetRequest } from '@/requests';
import { issueCookie } from '@/auth/jwt';
Expand All @@ -29,6 +25,7 @@ import { MfaService } from '@/Mfa/mfa.service';
import { Logger } from '@/Logger';
import { ExternalHooks } from '@/ExternalHooks';
import { InternalHooks } from '@/InternalHooks';
import { InstanceService } from '@/services/instance.service';

const throttle = rateLimit({
windowMs: 5 * 60 * 1000, // 5 minutes
Expand All @@ -47,6 +44,7 @@ export class PasswordResetController {
private readonly userService: UserService,
private readonly mfaService: MfaService,
private readonly license: License,
private readonly instanceService: InstanceService,
) {}

/**
Expand Down Expand Up @@ -131,7 +129,7 @@ export class PasswordResetController {
firstName,
lastName,
passwordResetUrl: url,
domain: getInstanceBaseUrl(),
domain: this.instanceService.getInstanceBaseUrl(),
});
} catch (error) {
void this.internalHooks.onEmailFailed({
Expand Down
7 changes: 4 additions & 3 deletions packages/cli/src/services/frontend.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { CredentialsOverwrites } from '@/CredentialsOverwrites';
import { CredentialTypes } from '@/CredentialTypes';
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import { License } from '@/License';
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
import * as WebhookHelpers from '@/WebhookHelpers';
import config from '@/config';
import { getCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
Expand All @@ -31,6 +30,7 @@ import {
import { UserManagementMailer } from '@/UserManagement/email';
import type { CommunityPackagesService } from '@/services/communityPackages.service';
import { Logger } from '@/Logger';
import { InstanceService } from '@/services/instance.service';

@Service()
export class FrontendService {
Expand All @@ -46,6 +46,7 @@ export class FrontendService {
private readonly license: License,
private readonly mailer: UserManagementMailer,
private readonly instanceSettings: InstanceSettings,
private readonly instanceService: InstanceService,
) {
loadNodesAndCredentials.addPostProcessor(async () => this.generateTypes());
void this.generateTypes();
Expand All @@ -61,7 +62,7 @@ export class FrontendService {
}

private initSettings() {
const instanceBaseUrl = getInstanceBaseUrl();
const instanceBaseUrl = this.instanceService.getInstanceBaseUrl();
const restEndpoint = config.getEnv('endpoints.rest');

const telemetrySettings: ITelemetrySettings = {
Expand Down Expand Up @@ -218,7 +219,7 @@ export class FrontendService {
const restEndpoint = config.getEnv('endpoints.rest');

// Update all urls, in case `WEBHOOK_URL` was updated by `--tunnel`
const instanceBaseUrl = getInstanceBaseUrl();
const instanceBaseUrl = this.instanceService.getInstanceBaseUrl();
this.settings.urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
this.settings.urlBaseEditor = instanceBaseUrl;
this.settings.oauthCallbackUrls = {
Expand Down
36 changes: 36 additions & 0 deletions packages/cli/src/services/instance.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Service } from 'typedi';
import config from '@/config';

@Service()
export class InstanceService {
/**
* 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;
}

getWebhookBaseUrl() {
let urlBaseWebhook = process.env.WEBHOOK_URL ?? this.getBaseUrl();
if (!urlBaseWebhook.endsWith('/')) {
urlBaseWebhook += '/';
}
return urlBaseWebhook;
}

/**
* Returns the base URL n8n is reachable from
*/
private getBaseUrl() {
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}`;
}
}
15 changes: 10 additions & 5 deletions packages/cli/src/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { In } from 'typeorm';
import { User } from '@db/entities/User';
import type { IUserSettings } from 'n8n-workflow';
import { UserRepository } from '@db/repositories/user.repository';
import { generateUserInviteUrl, getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
import type { PublicUser } from '@/Interfaces';
import type { PostHogClient } from '@/posthog';
import { type JwtPayload, JwtService } from './jwt.service';
Expand All @@ -17,6 +16,7 @@ import { RoleService } from '@/services/role.service';
import { ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
import { InternalServerError } from '@/ResponseHelper';
import type { UserRequest } from '@/requests';
import { InstanceService } from '@/services/instance.service';

@Service()
export class UserService {
Expand All @@ -26,6 +26,7 @@ export class UserService {
private readonly jwtService: JwtService,
private readonly mailer: UserManagementMailer,
private readonly roleService: RoleService,
private readonly instanceService: InstanceService,
) {}

async findOne(options: FindOneOptions<User>) {
Expand Down Expand Up @@ -78,7 +79,7 @@ export class UserService {
}

generatePasswordResetUrl(user: User) {
const instanceBaseUrl = getInstanceBaseUrl();
const instanceBaseUrl = this.instanceService.getInstanceBaseUrl();
const url = new URL(`${instanceBaseUrl}/change-password`);

url.searchParams.append('token', this.generatePasswordResetToken(user));
Expand Down Expand Up @@ -151,7 +152,7 @@ export class UserService {
}

private addInviteUrl(user: PublicUser, inviterId: string) {
const url = new URL(getInstanceBaseUrl());
const url = new URL(this.instanceService.getInstanceBaseUrl());
url.pathname = '/signup';
url.searchParams.set('inviterId', inviterId);
url.searchParams.set('inviteeId', user.id);
Expand Down Expand Up @@ -179,11 +180,11 @@ export class UserService {
}

private async sendEmails(owner: User, toInviteUsers: { [key: string]: string }) {
const domain = getInstanceBaseUrl();
const domain = this.instanceService.getInstanceBaseUrl();

return Promise.all(
Object.entries(toInviteUsers).map(async ([email, id]) => {
const inviteAcceptUrl = generateUserInviteUrl(owner.id, id);
const inviteAcceptUrl = this.generateUserInviteUrl(owner.id, id);
const invitedUser: UserRequest.InviteResponse = {
user: {
id,
Expand Down Expand Up @@ -287,4 +288,8 @@ export class UserService {

return { usersInvited, usersCreated: toCreateUsers };
}

private generateUserInviteUrl(inviterId: string, inviteeId: string): string {
return `${this.instanceService.getInstanceBaseUrl()}/signup?inviterId=${inviterId}&inviteeId=${inviteeId}`;
}
}
22 changes: 15 additions & 7 deletions packages/cli/src/sso/saml/routes/saml.controller.ee.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import express from 'express';
import { Container, Service } from 'typedi';
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
import { Authorized, Get, NoAuthRequired, Post, RestController } from '@/decorators';
import { SamlUrls } from '../constants';
import {
Expand All @@ -27,11 +26,15 @@ import { getSamlConnectionTestFailedView } from '../views/samlConnectionTestFail
import { InternalHooks } from '@/InternalHooks';
import url from 'url';
import querystring from 'querystring';
import { InstanceService } from '@/services/instance.service';

@Service()
@RestController('/sso/saml')
export class SamlController {
constructor(private samlService: SamlService) {}
constructor(
private samlService: SamlService,
private instanceService: InstanceService,
) {}

@NoAuthRequired()
@Get(SamlUrls.metadata)
Expand All @@ -51,8 +54,8 @@ export class SamlController {
const prefs = this.samlService.samlPreferences;
return {
...prefs,
entityID: getServiceProviderEntityId(),
returnUrl: getServiceProviderReturnUrl(),
entityID: getServiceProviderEntityId(this.instanceService.getInstanceBaseUrl()),
returnUrl: getServiceProviderReturnUrl(this.instanceService.getInstanceBaseUrl()),
};
}

Expand Down Expand Up @@ -119,6 +122,8 @@ export class SamlController {
res: express.Response,
binding: SamlLoginBinding,
) {
const instanceBaseUrl = this.instanceService.getInstanceBaseUrl();

try {
const loginResult = await this.samlService.handleSamlLogin(req, binding);
// if RelayState is set to the test connection Url, this is a test connection
Expand All @@ -138,10 +143,10 @@ export class SamlController {
if (isSamlLicensedAndEnabled()) {
await issueCookie(res, loginResult.authenticatedUser);
if (loginResult.onboardingRequired) {
return res.redirect(getInstanceBaseUrl() + SamlUrls.samlOnboarding);
return res.redirect(instanceBaseUrl + SamlUrls.samlOnboarding);
} else {
const redirectUrl = req.body?.RelayState ?? SamlUrls.defaultRedirect;
return res.redirect(getInstanceBaseUrl() + redirectUrl);
return res.redirect(instanceBaseUrl + redirectUrl);
}
} else {
return res.status(202).send(loginResult.attributes);
Expand Down Expand Up @@ -198,7 +203,10 @@ export class SamlController {
@Authorized(['global', 'owner'])
@Get(SamlUrls.configTest, { middlewares: [samlLicensedMiddleware] })
async configTestGet(req: AuthenticatedRequest, res: express.Response) {
return this.handleInitSSO(res, getServiceProviderConfigTestReturnUrl());
return this.handleInitSSO(
res,
getServiceProviderConfigTestReturnUrl(this.instanceService.getInstanceBaseUrl()),
);
}

private async handleInitSSO(res: express.Response, relayState?: string) {
Expand Down
13 changes: 8 additions & 5 deletions packages/cli/src/sso/saml/saml.service.ee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ import axios from 'axios';
import https from 'https';
import type { SamlLoginBinding } from './types';
import { validateMetadata, validateResponse } from './samlValidator';
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
import { Logger } from '@/Logger';
import { UserRepository } from '@db/repositories/user.repository';
import { SettingsRepository } from '@db/repositories/settings.repository';
import { InstanceService } from '@/services/instance.service';

@Service()
export class SamlService {
Expand All @@ -54,7 +54,7 @@ export class SamlService {
loginLabel: 'SAML',
wantAssertionsSigned: true,
wantMessageSigned: true,
relayState: getInstanceBaseUrl(),
relayState: undefined,
signatureConfig: {
prefix: 'ds',
location: {
Expand All @@ -72,7 +72,10 @@ export class SamlService {
};
}

constructor(private readonly logger: Logger) {}
constructor(
private readonly logger: Logger,
private readonly instanceService: InstanceService,
) {}

async init(): Promise<void> {
// load preferences first but do not apply so as to not load samlify unnecessarily
Expand Down Expand Up @@ -142,14 +145,14 @@ export class SamlService {

private getRedirectLoginRequestUrl(relayState?: string): BindingContext {
const sp = this.getServiceProviderInstance();
sp.entitySetting.relayState = relayState ?? getInstanceBaseUrl();
sp.entitySetting.relayState = relayState ?? this.instanceService.getInstanceBaseUrl();
const loginRequest = sp.createLoginRequest(this.getIdentityProviderInstance(), 'redirect');
return loginRequest;
}

private getPostLoginRequestUrl(relayState?: string): PostBindingContext {
const sp = this.getServiceProviderInstance();
sp.entitySetting.relayState = relayState ?? getInstanceBaseUrl();
sp.entitySetting.relayState = relayState ?? this.instanceService.getInstanceBaseUrl();
const loginRequest = sp.createLoginRequest(
this.getIdentityProviderInstance(),
'post',
Expand Down
Loading

0 comments on commit 0a14156

Please sign in to comment.