Skip to content

Commit

Permalink
fix(core): Do not return inviteAcceptUrl in response if email was s…
Browse files Browse the repository at this point in the history
…ent (n8n-io#7465)
  • Loading branch information
netroy authored Oct 19, 2023
1 parent ab6a9bb commit 55c6a1b
Show file tree
Hide file tree
Showing 11 changed files with 74 additions and 102 deletions.
1 change: 0 additions & 1 deletion packages/cli/src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,6 @@ export class Server extends AbstractServer {
new MeController(logger, externalHooks, internalHooks, userService),
new NodeTypesController(config, nodeTypes),
new PasswordResetController(
config,
logger,
externalHooks,
internalHooks,
Expand Down
7 changes: 0 additions & 7 deletions packages/cli/src/UserManagement/UserManagementHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,6 @@ import { License } from '@/License';
import { getWebhookBaseUrl } from '@/WebhookHelpers';
import { RoleService } from '@/services/role.service';

export function isEmailSetUp(): boolean {
const smtp = config.getEnv('userManagement.emails.mode') === 'smtp';
const host = !!config.getEnv('userManagement.emails.smtp.host');

return smtp && host;
}

export function isSharingEnabled(): boolean {
return Container.get(License).isSharingEnabled();
}
Expand Down
11 changes: 7 additions & 4 deletions packages/cli/src/UserManagement/email/UserManagementMailer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,17 @@ async function getTemplate(

@Service()
export class UserManagementMailer {
readonly isEmailSetUp: boolean;

private mailer: NodeMailer | undefined;

constructor() {
// Other implementations can be used in the future.
if (
this.isEmailSetUp =
config.getEnv('userManagement.emails.mode') === 'smtp' &&
config.getEnv('userManagement.emails.smtp.host') !== ''
) {
config.getEnv('userManagement.emails.smtp.host') !== '';

// Other implementations can be used in the future.
if (this.isEmailSetUp) {
this.mailer = new NodeMailer();
}
}
Expand Down
4 changes: 1 addition & 3 deletions packages/cli/src/controllers/passwordReset.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { UserManagementMailer } from '@/UserManagement/email';

import { Response } from 'express';
import { ILogger } from 'n8n-workflow';
import { Config } from '@/config';
import { PasswordResetRequest } from '@/requests';
import { IExternalHooksClass, IInternalHooksClass } from '@/Interfaces';
import { issueCookie } from '@/auth/jwt';
Expand All @@ -35,7 +34,6 @@ import { MfaService } from '@/Mfa/mfa.service';
@RestController()
export class PasswordResetController {
constructor(
private readonly config: Config,
private readonly logger: ILogger,
private readonly externalHooks: IExternalHooksClass,
private readonly internalHooks: IInternalHooksClass,
Expand All @@ -50,7 +48,7 @@ export class PasswordResetController {
*/
@Post('/forgot-password')
async forgotPassword(req: PasswordResetRequest.Email) {
if (this.config.getEnv('userManagement.emails.mode') === '') {
if (!this.mailer.isEmailSetUp) {
this.logger.debug(
'Request to send password reset email failed because emailing was not set up',
);
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/controllers/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
generateUserInviteUrl,
getInstanceBaseUrl,
hashPassword,
isEmailSetUp,
validatePassword,
} from '@/UserManagement/UserManagementHelper';
import { issueCookie } from '@/auth/jwt';
Expand Down Expand Up @@ -184,7 +183,7 @@ export class UsersController {
}
const inviteAcceptUrl = generateUserInviteUrl(req.user.id, id);
const resp: {
user: { id: string | null; email: string; inviteAcceptUrl: string; emailSent: boolean };
user: { id: string | null; email: string; inviteAcceptUrl?: string; emailSent: boolean };
error?: string;
} = {
user: {
Expand All @@ -202,6 +201,7 @@ export class UsersController {
});
if (result.emailSent) {
resp.user.emailSent = true;
delete resp.user.inviteAcceptUrl;
void this.internalHooks.onUserTransactionalEmail({
user_id: id,
message_type: 'New user invite',
Expand Down Expand Up @@ -619,7 +619,7 @@ export class UsersController {
throw new UnauthorizedError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
}

if (!isEmailSetUp()) {
if (!this.mailer.isEmailSetUp) {
this.logger.error('Request to reinvite a user failed because email sending was not set up');
throw new InternalServerError('Email sending must be set up in order to invite other users');
}
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/services/frontend.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { CredentialsOverwrites } from '@/CredentialsOverwrites';
import { CredentialTypes } from '@/CredentialTypes';
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import { License } from '@/License';
import { getInstanceBaseUrl, isEmailSetUp } from '@/UserManagement/UserManagementHelper';
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
import * as WebhookHelpers from '@/WebhookHelpers';
import { LoggerProxy } from 'n8n-workflow';
import config from '@/config';
Expand All @@ -28,6 +28,7 @@ import {
getWorkflowHistoryLicensePruneTime,
getWorkflowHistoryPruneTime,
} from '@/workflows/workflowHistory/workflowHistoryHelper.ee';
import { UserManagementMailer } from '@/UserManagement/email';

@Service()
export class FrontendService {
Expand All @@ -38,6 +39,7 @@ export class FrontendService {
private readonly credentialTypes: CredentialTypes,
private readonly credentialsOverwrites: CredentialsOverwrites,
private readonly license: License,
private readonly mailer: UserManagementMailer,
) {
this.initSettings();
}
Expand Down Expand Up @@ -103,7 +105,7 @@ export class FrontendService {
userManagement: {
quota: this.license.getUsersLimit(),
showSetupOnFirstLoad: !config.getEnv('userManagement.isInstanceOwnerSetUp'),
smtpSetup: isEmailSetUp(),
smtpSetup: this.mailer.isEmailSetUp,
authenticationMethod: getCurrentAuthenticationMethod(),
},
sso: {
Expand Down
11 changes: 4 additions & 7 deletions packages/cli/test/integration/ldap/ldap.api.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { SuperAgentTest } from 'supertest';
import type { Entry as LdapUser } from 'ldapts';
import { Not } from 'typeorm';
import { jsonParse } from 'n8n-workflow';
import { type ILogger, jsonParse, LoggerProxy } from 'n8n-workflow';
import { mock } from 'jest-mock-extended';

import config from '@/config';
import * as Db from '@/Db';
import type { Role } from '@db/entities/Role';
Expand All @@ -17,17 +19,13 @@ import { randomEmail, randomName, uniqueId } from './../shared/random';
import * as testDb from './../shared/testDb';
import * as utils from '../shared/utils/';

import { LoggerProxy } from 'n8n-workflow';
import { getLogger } from '@/Logger';

jest.mock('@/telemetry');
jest.mock('@/UserManagement/email/NodeMailer');

let globalMemberRole: Role;
let owner: User;
let authOwnerAgent: SuperAgentTest;

LoggerProxy.init(getLogger());
LoggerProxy.init(mock<ILogger>());

const defaultLdapConfig = {
...LDAP_DEFAULT_CONFIGURATION,
Expand Down Expand Up @@ -80,7 +78,6 @@ beforeEach(async () => {
jest.mock('@/telemetry');

config.set('userManagement.isInstanceOwnerSetUp', true);
config.set('userManagement.emails.mode', '');
});

const createLdapConfig = async (attributes: Partial<LdapConfig> = {}): Promise<LdapConfig> => {
Expand Down
27 changes: 9 additions & 18 deletions packages/cli/test/integration/passwordReset.api.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { v4 as uuid } from 'uuid';
import { compare } from 'bcryptjs';
import { Container } from 'typedi';
import { License } from '@/License';

import * as Db from '@/Db';
import config from '@/config';
import type { Role } from '@db/entities/Role';
import type { User } from '@db/entities/User';
import { setCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
import { ExternalHooks } from '@/ExternalHooks';
import { JwtService } from '@/services/jwt.service';
import { UserManagementMailer } from '@/UserManagement/email';

import * as utils from './shared/utils/';
import {
randomEmail,
Expand All @@ -15,12 +21,7 @@ import {
randomValidPassword,
} from './shared/random';
import * as testDb from './shared/testDb';
import { setCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
import { ExternalHooks } from '@/ExternalHooks';
import { JwtService } from '@/services/jwt.service';
import { Container } from 'typedi';

jest.mock('@/UserManagement/email/NodeMailer');
config.set('userManagement.jwtSecret', randomString(5, 10));

let globalOwnerRole: Role;
Expand All @@ -29,6 +30,7 @@ let owner: User;
let member: User;

const externalHooks = utils.mockInstance(ExternalHooks);
const mailer = utils.mockInstance(UserManagementMailer, { isEmailSetUp: true });
const testServer = utils.setupTestServer({ endpointGroups: ['passwordReset'] });
const jwtService = Container.get(JwtService);

Expand All @@ -43,6 +45,7 @@ beforeEach(async () => {
owner = await testDb.createUser({ globalRole: globalOwnerRole });
member = await testDb.createUser({ globalRole: globalMemberRole });
externalHooks.run.mockReset();
jest.replaceProperty(mailer, 'isEmailSetUp', true);
});

describe('POST /forgot-password', () => {
Expand All @@ -52,8 +55,6 @@ describe('POST /forgot-password', () => {
globalRole: globalMemberRole,
});

config.set('userManagement.emails.mode', 'smtp');

await Promise.all(
[{ email: owner.email }, { email: member.email.toUpperCase() }].map(async (payload) => {
const response = await testServer.authlessAgent.post('/forgot-password').send(payload);
Expand All @@ -65,7 +66,7 @@ describe('POST /forgot-password', () => {
});

test('should fail if emailing is not set up', async () => {
config.set('userManagement.emails.mode', '');
jest.replaceProperty(mailer, 'isEmailSetUp', false);

await testServer.authlessAgent
.post('/forgot-password')
Expand All @@ -75,7 +76,6 @@ describe('POST /forgot-password', () => {

test('should fail if SAML is authentication method', async () => {
await setCurrentAuthenticationMethod('saml');
config.set('userManagement.emails.mode', 'smtp');
const member = await testDb.createUser({
email: 'test@test.com',
globalRole: globalMemberRole,
Expand All @@ -91,7 +91,6 @@ describe('POST /forgot-password', () => {

test('should succeed if SAML is authentication method and requestor is owner', async () => {
await setCurrentAuthenticationMethod('saml');
config.set('userManagement.emails.mode', 'smtp');

const response = await testServer.authlessAgent
.post('/forgot-password')
Expand All @@ -104,8 +103,6 @@ describe('POST /forgot-password', () => {
});

test('should fail with invalid inputs', async () => {
config.set('userManagement.emails.mode', 'smtp');

const invalidPayloads = [
randomEmail(),
[randomEmail()],
Expand All @@ -121,8 +118,6 @@ describe('POST /forgot-password', () => {
});

test('should fail if user is not found', async () => {
config.set('userManagement.emails.mode', 'smtp');

const response = await testServer.authlessAgent
.post('/forgot-password')
.send({ email: randomEmail() });
Expand All @@ -132,10 +127,6 @@ describe('POST /forgot-password', () => {
});

describe('GET /resolve-password-token', () => {
beforeEach(() => {
config.set('userManagement.emails.mode', 'smtp');
});

test('should succeed with valid inputs', async () => {
const resetPasswordToken = jwtService.signData({ sub: owner.id });

Expand Down
1 change: 0 additions & 1 deletion packages/cli/test/integration/shared/utils/testServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,6 @@ export const setupTestServer = ({
app,
config,
new PasswordResetController(
config,
logger,
externalHooks,
internalHooks,
Expand Down
Loading

0 comments on commit 55c6a1b

Please sign in to comment.