feat(core): Add MFA (#4767)

https://linear.app/n8n/issue/ADO-947/sync-branch-with-master-and-fix-fe-e2e-tets

---------

Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
Ricardo Espinoza
2023-08-23 22:59:16 -04:00
committed by GitHub
parent a01c3fbc19
commit 2b7ba6fdf1
61 changed files with 2301 additions and 105 deletions

View File

@@ -21,7 +21,6 @@ import type { TagEntity } from '@db/entities/TagEntity';
import type { User } from '@db/entities/User';
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
import type { ICredentialsDb } from '@/Interfaces';
import { DB_INITIALIZATION_TIMEOUT } from './constants';
import { randomApiKey, randomEmail, randomName, randomString, randomValidPassword } from './random';
import type {
@@ -38,6 +37,10 @@ import { VariablesService } from '@/environments/variables/variables.service';
import { TagRepository, WorkflowTagMappingRepository } from '@/databases/repositories';
import { separate } from '@/utils';
import { randomPassword } from '@/Ldap/helpers';
import { TOTPService } from '@/Mfa/totp.service';
import { MfaService } from '@/Mfa/mfa.service';
export type TestDBType = 'postgres' | 'mysql';
export const testDbPrefix = 'n8n_test_';
@@ -204,6 +207,41 @@ export async function createLdapUser(attributes: Partial<User>, ldapId: string):
return user;
}
export async function createUserWithMfaEnabled(
data: { numberOfRecoveryCodes: number } = { numberOfRecoveryCodes: 10 },
) {
const encryptionKey = await UserSettings.getEncryptionKey();
const email = randomEmail();
const password = randomPassword();
const toptService = new TOTPService();
const secret = toptService.generateSecret();
const mfaService = new MfaService(Db.collections.User, toptService, encryptionKey);
const recoveryCodes = mfaService.generateRecoveryCodes(data.numberOfRecoveryCodes);
const { encryptedSecret, encryptedRecoveryCodes } = mfaService.encryptSecretAndRecoveryCodes(
secret,
recoveryCodes,
);
return {
user: await createUser({
mfaEnabled: true,
password,
email,
mfaSecret: encryptedSecret,
mfaRecoveryCodes: encryptedRecoveryCodes,
}),
rawPassword: password,
rawSecret: secret,
rawRecoveryCodes: recoveryCodes,
};
}
export async function createOwner() {
return createUser({ globalRole: await getGlobalOwnerRole() });
}
@@ -592,13 +630,12 @@ const baseOptions = (type: TestDBType) => ({
/**
* Generate options for a bootstrap DB connection, to create and drop test databases.
*/
export const getBootstrapDBOptions = (type: TestDBType) =>
({
type,
name: type,
database: type,
...baseOptions(type),
}) as const;
export const getBootstrapDBOptions = (type: TestDBType) => ({
type,
name: type,
database: type,
...baseOptions(type),
});
const getDBOptions = (type: TestDBType, name: string) => ({
type,

View File

@@ -26,6 +26,7 @@ export type EndpointGroup =
| 'license'
| 'variables'
| 'tags'
| 'mfa'
| 'metrics';
export interface SetupProps {

View File

@@ -23,6 +23,7 @@ import { registerController } from '@/decorators';
import {
AuthController,
LdapController,
MFAController,
MeController,
NodesController,
OwnerController,
@@ -49,7 +50,9 @@ import * as testDb from '../../shared/testDb';
import { AUTHLESS_ENDPOINTS, PUBLIC_API_REST_PATH_SEGMENT, REST_PATH_SEGMENT } from '../constants';
import type { EndpointGroup, SetupProps, TestServer } from '../types';
import { mockInstance } from './mocking';
import { JwtService } from '@/services/jwt.service';
import { MfaService } from '@/Mfa/mfa.service';
import { TOTPService } from '@/Mfa/totp.service';
import { UserSettings } from 'n8n-core';
import { MetricsService } from '@/services/metrics.service';
/**
@@ -179,11 +182,12 @@ export const setupTestServer = ({
}
if (functionEndpoints.length) {
const encryptionKey = await UserSettings.getEncryptionKey();
const repositories = Db.collections;
const externalHooks = Container.get(ExternalHooks);
const internalHooks = Container.get(InternalHooks);
const mailer = Container.get(UserManagementMailer);
const jwtService = Container.get(JwtService);
const repositories = Db.collections;
const mfaService = new MfaService(repositories.User, new TOTPService(), encryptionKey);
for (const group of functionEndpoints) {
switch (group) {
@@ -197,14 +201,11 @@ export const setupTestServer = ({
registerController(
app,
config,
new AuthController({
config,
logger,
internalHooks,
repositories,
}),
new AuthController({ config, logger, internalHooks, repositories, mfaService }),
);
break;
case 'mfa':
registerController(app, config, new MFAController(mfaService));
case 'ldap':
Container.get(License).isLdapEnabled = () => true;
await handleLdapInit();
@@ -250,6 +251,7 @@ export const setupTestServer = ({
externalHooks,
internalHooks,
mailer,
mfaService,
}),
);
break;