refactor(core): Switch over all user-management routes to use decorators (#5115)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2023-01-27 11:19:47 +01:00
committed by GitHub
parent 08a90d7e09
commit 845f0f9d20
71 changed files with 1803 additions and 1667 deletions

View File

@@ -5,8 +5,8 @@ import * as Db from '@/Db';
import { toReportTitle } from '@/audit/utils';
import * as constants from '@/constants';
import type { Risk } from '@/audit/types';
import type { InstalledNodes } from '@/databases/entities/InstalledNodes';
import type { InstalledPackages } from '@/databases/entities/InstalledPackages';
import type { InstalledNodes } from '@db/entities/InstalledNodes';
import type { InstalledPackages } from '@db/entities/InstalledPackages';
type GetSectionKind<C extends Risk.Category> = C extends 'instance'
? Risk.InstanceSection

View File

@@ -16,16 +16,12 @@ let globalMemberRole: Role;
let authAgent: AuthAgent;
beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['auth'], applyAuth: true });
await testDb.init();
app = await utils.initTestServer({ endpointGroups: ['auth'] });
globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole();
authAgent = utils.createAuthAgent(app);
utils.initTestLogger();
utils.initTestTelemetry();
});
beforeEach(async () => {
@@ -276,6 +272,60 @@ test('GET /login should return logged-in member', async () => {
expect(authToken).toBeUndefined();
});
test('GET /resolve-signup-token should validate invite token', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const memberShell = await testDb.createUserShell(globalMemberRole);
const response = await authAgent(owner)
.get('/resolve-signup-token')
.query({ inviterId: owner.id })
.query({ inviteeId: memberShell.id });
expect(response.statusCode).toBe(200);
expect(response.body).toEqual({
data: {
inviter: {
firstName: owner.firstName,
lastName: owner.lastName,
},
},
});
});
test('GET /resolve-signup-token should fail with invalid inputs', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const authOwnerAgent = authAgent(owner);
const { id: inviteeId } = await testDb.createUser({ globalRole: globalMemberRole });
const first = await authOwnerAgent.get('/resolve-signup-token').query({ inviterId: owner.id });
const second = await authOwnerAgent.get('/resolve-signup-token').query({ inviteeId });
const third = await authOwnerAgent.get('/resolve-signup-token').query({
inviterId: '5531199e-b7ae-425b-a326-a95ef8cca59d',
inviteeId: 'cb133beb-7729-4c34-8cd1-a06be8834d9d',
});
// user is already set up, so call should error
const fourth = await authOwnerAgent
.get('/resolve-signup-token')
.query({ inviterId: owner.id })
.query({ inviteeId });
// cause inconsistent DB state
await Db.collections.User.update(owner.id, { email: '' });
const fifth = await authOwnerAgent
.get('/resolve-signup-token')
.query({ inviterId: owner.id })
.query({ inviteeId });
for (const response of [first, second, third, fourth, fifth]) {
expect(response.statusCode).toBe(400);
}
});
test('POST /logout should log user out', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole });

View File

@@ -16,18 +16,11 @@ let globalMemberRole: Role;
let authAgent: AuthAgent;
beforeAll(async () => {
app = await utils.initTestServer({
applyAuth: true,
endpointGroups: ['me', 'auth', 'owner', 'users'],
});
await testDb.init();
app = await utils.initTestServer({ endpointGroups: ['me', 'auth', 'owner', 'users'] });
globalMemberRole = await testDb.getGlobalMemberRole();
authAgent = utils.createAuthAgent(app);
utils.initTestLogger();
utils.initTestTelemetry();
});
afterAll(async () => {

View File

@@ -1,16 +1,11 @@
import express from 'express';
import * as Db from '@/Db';
import { Reset } from '@/commands/user-management/reset';
import type { Role } from '@db/entities/Role';
import * as utils from '../shared/utils';
import * as testDb from '../shared/testDb';
let app: express.Application;
let globalOwnerRole: Role;
beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['owner'], applyAuth: true });
await testDb.init();
globalOwnerRole = await testDb.getGlobalOwnerRole();

View File

@@ -22,11 +22,7 @@ let authAgent: AuthAgent;
let sharingSpy: jest.SpyInstance<boolean>;
beforeAll(async () => {
app = await utils.initTestServer({
endpointGroups: ['credentials'],
applyAuth: true,
});
await testDb.init();
app = await utils.initTestServer({ endpointGroups: ['credentials'] });
utils.initConfigFile();
@@ -37,9 +33,6 @@ beforeAll(async () => {
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
authAgent = utils.createAuthAgent(app);
utils.initTestLogger();
utils.initTestTelemetry();
sharingSpy = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true);
});

View File

@@ -25,11 +25,7 @@ let saveCredential: SaveCredentialFunction;
let authAgent: AuthAgent;
beforeAll(async () => {
app = await utils.initTestServer({
endpointGroups: ['credentials'],
applyAuth: true,
});
await testDb.init();
app = await utils.initTestServer({ endpointGroups: ['credentials'] });
utils.initConfigFile();
@@ -40,9 +36,6 @@ beforeAll(async () => {
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
authAgent = utils.createAuthAgent(app);
utils.initTestLogger();
utils.initTestTelemetry();
});
beforeEach(async () => {

View File

@@ -81,12 +81,11 @@ async function confirmIdSent(id: string) {
}
beforeAll(async () => {
await testDb.init();
app = await utils.initTestServer({ endpointGroups: ['eventBus'] });
globalOwnerRole = await testDb.getGlobalOwnerRole();
owner = await testDb.createUser({ globalRole: globalOwnerRole });
app = await utils.initTestServer({ endpointGroups: ['eventBus'], applyAuth: true });
unAuthOwnerAgent = utils.createAgent(app, {
apiPath: 'internal',
auth: false,
@@ -104,7 +103,6 @@ beforeAll(async () => {
mockedSyslog.createClient.mockImplementation(() => new syslog.Client());
utils.initConfigFile();
utils.initTestLogger();
config.set('eventBus.logWriter.logBaseName', 'n8n-test-logwriter');
config.set('eventBus.logWriter.keepLogCount', '1');
config.set('enterprise.features.logStreaming', true);

View File

@@ -40,8 +40,7 @@ const defaultLdapConfig = {
};
beforeAll(async () => {
await testDb.init();
app = await utils.initTestServer({ endpointGroups: ['auth', 'ldap'], applyAuth: true });
app = await utils.initTestServer({ endpointGroups: ['auth', 'ldap'] });
const [fetchedGlobalOwnerRole, fetchedGlobalMemberRole] = await testDb.getAllRoles();
@@ -56,8 +55,6 @@ beforeAll(async () => {
);
utils.initConfigFile();
utils.initTestLogger();
utils.initTestTelemetry();
await utils.initLdapManager();
});

View File

@@ -19,17 +19,13 @@ let authAgent: AuthAgent;
let license: License;
beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['license'], applyAuth: true });
await testDb.init();
app = await utils.initTestServer({ endpointGroups: ['license'] });
globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole();
authAgent = utils.createAuthAgent(app);
utils.initTestLogger();
utils.initTestTelemetry();
config.set('license.serverUrl', MOCK_SERVER_URL);
config.set('license.autoRenewEnabled', true);
config.set('license.autoRenewOffset', MOCK_RENEW_OFFSET);

View File

@@ -23,16 +23,12 @@ let globalMemberRole: Role;
let authAgent: AuthAgent;
beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['me'], applyAuth: true });
await testDb.init();
app = await utils.initTestServer({ endpointGroups: ['me'] });
globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole();
authAgent = utils.createAuthAgent(app);
utils.initTestLogger();
utils.initTestTelemetry();
});
beforeEach(async () => {

View File

@@ -47,16 +47,13 @@ let globalOwnerRole: Role;
let authAgent: AuthAgent;
beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['nodes'], applyAuth: true });
await testDb.init();
app = await utils.initTestServer({ endpointGroups: ['nodes'] });
globalOwnerRole = await testDb.getGlobalOwnerRole();
authAgent = utils.createAuthAgent(app);
utils.initConfigFile();
utils.initTestLogger();
utils.initTestTelemetry();
});
beforeEach(async () => {

View File

@@ -19,15 +19,11 @@ let globalOwnerRole: Role;
let authAgent: AuthAgent;
beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['owner'], applyAuth: true });
await testDb.init();
app = await utils.initTestServer({ endpointGroups: ['owner'] });
globalOwnerRole = await testDb.getGlobalOwnerRole();
authAgent = utils.createAuthAgent(app);
utils.initTestLogger();
utils.initTestTelemetry();
});
beforeEach(async () => {

View File

@@ -21,14 +21,10 @@ let globalOwnerRole: Role;
let globalMemberRole: Role;
beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['passwordReset'], applyAuth: true });
await testDb.init();
app = await utils.initTestServer({ endpointGroups: ['passwordReset'] });
globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole();
utils.initTestTelemetry();
utils.initTestLogger();
});
beforeEach(async () => {

View File

@@ -23,7 +23,6 @@ beforeAll(async () => {
applyAuth: false,
enablePublicAPI: true,
});
await testDb.init();
utils.initConfigFile();
@@ -36,8 +35,6 @@ beforeAll(async () => {
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
utils.initTestLogger();
utils.initTestTelemetry();
utils.initCredentialsTypes();
});

View File

@@ -18,13 +18,9 @@ beforeAll(async () => {
applyAuth: false,
enablePublicAPI: true,
});
await testDb.init();
globalOwnerRole = await testDb.getGlobalOwnerRole();
utils.initTestTelemetry();
utils.initTestLogger();
await utils.initBinaryManager();
await utils.initNodeTypes();

View File

@@ -22,7 +22,6 @@ beforeAll(async () => {
applyAuth: false,
enablePublicAPI: true,
});
await testDb.init();
const [fetchedGlobalOwnerRole, fetchedGlobalMemberRole, fetchedWorkflowOwnerRole] =
await testDb.getAllRoles();
@@ -31,8 +30,6 @@ beforeAll(async () => {
globalMemberRole = fetchedGlobalMemberRole;
workflowOwnerRole = fetchedWorkflowOwnerRole;
utils.initTestTelemetry();
utils.initTestLogger();
utils.initConfigFile();
await utils.initNodeTypes();
workflowRunner = await utils.initActiveWorkflowRunner();

View File

@@ -22,7 +22,6 @@ import {
toCronExpression,
TriggerTime,
} from 'n8n-workflow';
import type { N8nApp } from '@/UserManagement/Interfaces';
import superagent from 'superagent';
import request from 'supertest';
import { URL } from 'url';
@@ -34,11 +33,6 @@ import { ExternalHooks } from '@/ExternalHooks';
import { InternalHooksManager } from '@/InternalHooksManager';
import { NodeTypes } from '@/NodeTypes';
import * as ActiveWorkflowRunner from '@/ActiveWorkflowRunner';
import { meNamespace as meEndpoints } from '@/UserManagement/routes/me';
import { usersNamespace as usersEndpoints } from '@/UserManagement/routes/users';
import { authenticationMethods as authEndpoints } from '@/UserManagement/routes/auth';
import { ownerNamespace as ownerEndpoints } from '@/UserManagement/routes/owner';
import { passwordResetNamespace as passwordResetEndpoints } from '@/UserManagement/routes/passwordReset';
import { nodesController } from '@/api/nodes.api';
import { workflowsController } from '@/workflows/workflows.controller';
import { AUTH_COOKIE_NAME, NODE_PACKAGE_PREFIX } from '@/constants';
@@ -47,8 +41,8 @@ import { InstalledPackages } from '@db/entities/InstalledPackages';
import type { User } from '@db/entities/User';
import { getLogger } from '@/Logger';
import { loadPublicApiVersions } from '@/PublicApi/';
import { issueJWT } from '@/UserManagement/auth/jwt';
import { addRoutes as authMiddleware } from '@/UserManagement/routes';
import { issueJWT } from '@/auth/jwt';
import * as UserManagementMailer from '@/UserManagement/email/UserManagementMailer';
import {
AUTHLESS_ENDPOINTS,
COMMUNITY_NODE_VERSION,
@@ -66,9 +60,19 @@ import type {
} from './types';
import { licenseController } from '@/license/license.controller';
import { eventBusRouter } from '@/eventbus/eventBusRoutes';
import { registerController } from '@/decorators';
import {
AuthController,
MeController,
OwnerController,
PasswordResetController,
UsersController,
} from '@/controllers';
import { setupAuthMiddlewares } from '@/middlewares';
import * as testDb from '../shared/testDb';
import { v4 as uuid } from 'uuid';
import { handleLdapInit } from '../../../src/Ldap/helpers';
import { handleLdapInit } from '@/Ldap/helpers';
import { ldapController } from '@/Ldap/routes/ldap.controller.ee';
const loadNodesAndCredentials: INodesAndCredentials = {
@@ -84,14 +88,15 @@ CredentialTypes(loadNodesAndCredentials);
* Initialize a test server.
*/
export async function initTestServer({
applyAuth,
applyAuth = true,
endpointGroups,
enablePublicAPI = false,
}: {
applyAuth: boolean;
applyAuth?: boolean;
endpointGroups?: EndpointGroup[];
enablePublicAPI?: boolean;
}) {
await testDb.init();
const testServer = {
app: express(),
restEndpoint: REST_PATH_SEGMENT,
@@ -99,6 +104,12 @@ export async function initTestServer({
externalHooks: {},
};
const logger = getLogger();
LoggerProxy.init(logger);
// Pre-requisite: Mock the telemetry module before calling.
await InternalHooksManager.init('test-instance-id', mockNodeTypes);
testServer.app.use(bodyParser.json());
testServer.app.use(bodyParser.urlencoded({ extended: true }));
@@ -106,7 +117,12 @@ export async function initTestServer({
config.set('userManagement.isInstanceOwnerSetUp', false);
if (applyAuth) {
authMiddleware.apply(testServer, [AUTHLESS_ENDPOINTS, REST_PATH_SEGMENT]);
setupAuthMiddlewares(
testServer.app,
AUTHLESS_ENDPOINTS,
REST_PATH_SEGMENT,
Db.collections.User,
);
}
if (!endpointGroups) return testServer.app;
@@ -147,36 +163,75 @@ export async function initTestServer({
}
if (functionEndpoints.length) {
const map: Record<string, (this: N8nApp) => void> = {
me: meEndpoints,
users: usersEndpoints,
auth: authEndpoints,
owner: ownerEndpoints,
passwordReset: passwordResetEndpoints,
};
const externalHooks = ExternalHooks();
const internalHooks = InternalHooksManager.getInstance();
const mailer = UserManagementMailer.getInstance();
const repositories = Db.collections;
for (const group of functionEndpoints) {
map[group].apply(testServer);
switch (group) {
case 'auth':
registerController(
testServer.app,
config,
new AuthController({ config, logger, internalHooks, repositories }),
);
break;
case 'me':
registerController(
testServer.app,
config,
new MeController({ logger, externalHooks, internalHooks, repositories }),
);
break;
case 'passwordReset':
registerController(
testServer.app,
config,
new PasswordResetController({
config,
logger,
externalHooks,
internalHooks,
repositories,
}),
);
break;
case 'owner':
registerController(
testServer.app,
config,
new OwnerController({ config, logger, internalHooks, repositories }),
);
break;
case 'users':
registerController(
testServer.app,
config,
new UsersController({
config,
mailer,
externalHooks,
internalHooks,
repositories,
activeWorkflowRunner: ActiveWorkflowRunner.getInstance(),
logger,
}),
);
}
}
}
return testServer.app;
}
/**
* Pre-requisite: Mock the telemetry module before calling.
*/
export function initTestTelemetry() {
void InternalHooksManager.init('test-instance-id', mockNodeTypes);
}
/**
* Classify endpoint groups into `routerEndpoints` (newest, using `express.Router`),
* and `functionEndpoints` (legacy, namespaced inside a function).
*/
const classifyEndpointGroups = (endpointGroups: string[]) => {
const routerEndpoints: string[] = [];
const functionEndpoints: string[] = [];
const classifyEndpointGroups = (endpointGroups: EndpointGroup[]) => {
const routerEndpoints: EndpointGroup[] = [];
const functionEndpoints: EndpointGroup[] = [];
const ROUTER_GROUP = [
'credentials',
@@ -559,13 +614,6 @@ export async function initNodeTypes() {
};
}
/**
* Initialize a logger for test runs.
*/
export function initTestLogger() {
LoggerProxy.init(getLogger());
}
/**
* Initialize a BinaryManager for test runs.
*/

View File

@@ -33,8 +33,7 @@ let credentialOwnerRole: Role;
let authAgent: AuthAgent;
beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['users'], applyAuth: true });
await testDb.init();
app = await utils.initTestServer({ endpointGroups: ['users'] });
const [
fetchedGlobalOwnerRole,
@@ -49,9 +48,6 @@ beforeAll(async () => {
credentialOwnerRole = fetchedCredentialOwnerRole;
authAgent = utils.createAuthAgent(app);
utils.initTestTelemetry();
utils.initTestLogger();
});
beforeEach(async () => {
@@ -241,60 +237,6 @@ test('DELETE /users/:id with transferId should perform transfer', async () => {
expect(deletedUser).toBeNull();
});
test('GET /resolve-signup-token should validate invite token', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const memberShell = await testDb.createUserShell(globalMemberRole);
const response = await authAgent(owner)
.get('/resolve-signup-token')
.query({ inviterId: owner.id })
.query({ inviteeId: memberShell.id });
expect(response.statusCode).toBe(200);
expect(response.body).toEqual({
data: {
inviter: {
firstName: owner.firstName,
lastName: owner.lastName,
},
},
});
});
test('GET /resolve-signup-token should fail with invalid inputs', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const authOwnerAgent = authAgent(owner);
const { id: inviteeId } = await testDb.createUser({ globalRole: globalMemberRole });
const first = await authOwnerAgent.get('/resolve-signup-token').query({ inviterId: owner.id });
const second = await authOwnerAgent.get('/resolve-signup-token').query({ inviteeId });
const third = await authOwnerAgent.get('/resolve-signup-token').query({
inviterId: '5531199e-b7ae-425b-a326-a95ef8cca59d',
inviteeId: 'cb133beb-7729-4c34-8cd1-a06be8834d9d',
});
// user is already set up, so call should error
const fourth = await authOwnerAgent
.get('/resolve-signup-token')
.query({ inviterId: owner.id })
.query({ inviteeId });
// cause inconsistent DB state
await Db.collections.User.update(owner.id, { email: '' });
const fifth = await authOwnerAgent
.get('/resolve-signup-token')
.query({ inviterId: owner.id })
.query({ inviteeId });
for (const response of [first, second, third, fourth, fifth]) {
expect(response.statusCode).toBe(400);
}
});
test('POST /users/:id should fill out a user shell', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole });

View File

@@ -24,11 +24,7 @@ let workflowRunner: ActiveWorkflowRunner;
let sharingSpy: jest.SpyInstance<boolean>;
beforeAll(async () => {
app = await utils.initTestServer({
endpointGroups: ['workflows'],
applyAuth: true,
});
await testDb.init();
app = await utils.initTestServer({ endpointGroups: ['workflows'] });
globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole();
@@ -38,9 +34,6 @@ beforeAll(async () => {
authAgent = utils.createAuthAgent(app);
utils.initTestLogger();
utils.initTestTelemetry();
isSharingEnabled = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true);
await utils.initNodeTypes();

View File

@@ -15,16 +15,9 @@ let globalOwnerRole: Role;
jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(false);
beforeAll(async () => {
app = await utils.initTestServer({
endpointGroups: ['workflows'],
applyAuth: true,
});
await testDb.init();
app = await utils.initTestServer({ endpointGroups: ['workflows'] });
globalOwnerRole = await testDb.getGlobalOwnerRole();
utils.initTestLogger();
utils.initTestTelemetry();
});
beforeEach(async () => {