2023-07-13 01:14:48 -07:00
|
|
|
import { Container } from 'typedi';
|
|
|
|
import cookieParser from 'cookie-parser';
|
|
|
|
import express from 'express';
|
|
|
|
import type superagent from 'superagent';
|
|
|
|
import request from 'supertest';
|
|
|
|
import { URL } from 'url';
|
|
|
|
|
|
|
|
import config from '@/config';
|
|
|
|
import { ExternalHooks } from '@/ExternalHooks';
|
|
|
|
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
|
|
|
import { workflowsController } from '@/workflows/workflows.controller';
|
|
|
|
import { AUTH_COOKIE_NAME } from '@/constants';
|
|
|
|
import { credentialsController } from '@/credentials/credentials.controller';
|
|
|
|
import type { User } from '@db/entities/User';
|
|
|
|
import { loadPublicApiVersions } from '@/PublicApi/';
|
|
|
|
import { issueJWT } from '@/auth/jwt';
|
|
|
|
import { UserManagementMailer } from '@/UserManagement/email/UserManagementMailer';
|
|
|
|
import { licenseController } from '@/license/license.controller';
|
|
|
|
import { registerController } from '@/decorators';
|
|
|
|
import {
|
|
|
|
AuthController,
|
|
|
|
LdapController,
|
2023-08-23 19:59:16 -07:00
|
|
|
MFAController,
|
2023-07-13 01:14:48 -07:00
|
|
|
MeController,
|
|
|
|
OwnerController,
|
|
|
|
PasswordResetController,
|
2023-07-20 02:34:45 -07:00
|
|
|
TagsController,
|
2023-07-13 01:14:48 -07:00
|
|
|
UsersController,
|
|
|
|
} from '@/controllers';
|
2023-08-11 00:18:33 -07:00
|
|
|
import { rawBodyReader, bodyParser, setupAuthMiddlewares } from '@/middlewares';
|
2023-07-13 01:14:48 -07:00
|
|
|
|
|
|
|
import { InternalHooks } from '@/InternalHooks';
|
|
|
|
import { PostHogClient } from '@/posthog';
|
|
|
|
import { variablesController } from '@/environments/variables/variables.controller';
|
|
|
|
import { LdapManager } from '@/Ldap/LdapManager.ee';
|
|
|
|
import { handleLdapInit } from '@/Ldap/helpers';
|
|
|
|
import { setSamlLoginEnabled } from '@/sso/saml/samlHelpers';
|
|
|
|
import { SamlController } from '@/sso/saml/routes/saml.controller.ee';
|
|
|
|
import { EventBusController } from '@/eventbus/eventBus.controller';
|
2023-09-05 04:32:09 -07:00
|
|
|
import { EventBusControllerEE } from '@/eventbus/eventBus.controller.ee';
|
2023-07-13 01:14:48 -07:00
|
|
|
import { License } from '@/License';
|
|
|
|
import { SourceControlController } from '@/environments/sourceControl/sourceControl.controller.ee';
|
|
|
|
|
|
|
|
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';
|
2023-08-25 01:33:46 -07:00
|
|
|
import { ExternalSecretsController } from '@/ExternalSecrets/ExternalSecrets.controller.ee';
|
2023-08-23 19:59:16 -07:00
|
|
|
import { MfaService } from '@/Mfa/mfa.service';
|
2023-08-04 11:51:07 -07:00
|
|
|
import { MetricsService } from '@/services/metrics.service';
|
2023-08-25 04:23:22 -07:00
|
|
|
import {
|
|
|
|
SettingsRepository,
|
|
|
|
SharedCredentialsRepository,
|
|
|
|
SharedWorkflowRepository,
|
|
|
|
} from '@/databases/repositories';
|
|
|
|
import { JwtService } from '@/services/jwt.service';
|
|
|
|
import { RoleService } from '@/services/role.service';
|
|
|
|
import { UserService } from '@/services/user.service';
|
2023-09-20 06:21:42 -07:00
|
|
|
import { executionsController } from '@/executions/executions.controller';
|
2023-09-27 07:22:39 -07:00
|
|
|
import { WorkflowHistoryController } from '@/workflows/workflowHistory/workflowHistory.controller.ee';
|
2023-10-06 07:21:13 -07:00
|
|
|
import { BinaryDataController } from '@/controllers/binaryData.controller';
|
2023-10-25 07:35:22 -07:00
|
|
|
import { Logger } from '@/Logger';
|
2023-07-13 01:14:48 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Plugin to prefix a path segment into a request URL pathname.
|
|
|
|
*
|
|
|
|
* Example: http://127.0.0.1:62100/me/password → http://127.0.0.1:62100/rest/me/password
|
|
|
|
*/
|
|
|
|
function prefix(pathSegment: string) {
|
|
|
|
return async function (request: superagent.SuperAgentRequest) {
|
|
|
|
const url = new URL(request.url);
|
|
|
|
|
|
|
|
// enforce consistency at call sites
|
|
|
|
if (url.pathname[0] !== '/') {
|
|
|
|
throw new Error('Pathname must start with a forward slash');
|
|
|
|
}
|
|
|
|
|
|
|
|
url.pathname = pathSegment + url.pathname;
|
|
|
|
request.url = url.toString();
|
|
|
|
return request;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Classify endpoint groups into `routerEndpoints` (newest, using `express.Router`),
|
|
|
|
* and `functionEndpoints` (legacy, namespaced inside a function).
|
|
|
|
*/
|
|
|
|
const classifyEndpointGroups = (endpointGroups: EndpointGroup[]) => {
|
|
|
|
const routerEndpoints: EndpointGroup[] = [];
|
|
|
|
const functionEndpoints: EndpointGroup[] = [];
|
|
|
|
|
2023-09-20 06:21:42 -07:00
|
|
|
const ROUTER_GROUP = [
|
|
|
|
'credentials',
|
|
|
|
'workflows',
|
|
|
|
'publicApi',
|
|
|
|
'license',
|
|
|
|
'variables',
|
|
|
|
'executions',
|
|
|
|
];
|
2023-07-13 01:14:48 -07:00
|
|
|
|
|
|
|
endpointGroups.forEach((group) =>
|
|
|
|
(ROUTER_GROUP.includes(group) ? routerEndpoints : functionEndpoints).push(group),
|
|
|
|
);
|
|
|
|
|
|
|
|
return [routerEndpoints, functionEndpoints];
|
|
|
|
};
|
|
|
|
|
|
|
|
function createAgent(app: express.Application, options?: { auth: boolean; user: User }) {
|
|
|
|
const agent = request.agent(app);
|
|
|
|
void agent.use(prefix(REST_PATH_SEGMENT));
|
|
|
|
if (options?.auth && options?.user) {
|
2023-08-03 05:03:26 -07:00
|
|
|
const { token } = issueJWT(options.user);
|
|
|
|
agent.jar.setCookie(`${AUTH_COOKIE_NAME}=${token}`);
|
2023-07-13 01:14:48 -07:00
|
|
|
}
|
|
|
|
return agent;
|
|
|
|
}
|
|
|
|
|
|
|
|
function publicApiAgent(
|
|
|
|
app: express.Application,
|
|
|
|
{ user, version = 1 }: { user: User; version?: number },
|
|
|
|
) {
|
|
|
|
const agent = request.agent(app);
|
|
|
|
void agent.use(prefix(`${PUBLIC_API_REST_PATH_SEGMENT}/v${version}`));
|
|
|
|
if (user.apiKey) {
|
|
|
|
void agent.set({ 'X-N8N-API-KEY': user.apiKey });
|
|
|
|
}
|
|
|
|
return agent;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const setupTestServer = ({
|
|
|
|
endpointGroups,
|
|
|
|
applyAuth = true,
|
|
|
|
enabledFeatures,
|
|
|
|
}: SetupProps): TestServer => {
|
|
|
|
const app = express();
|
2023-08-11 00:18:33 -07:00
|
|
|
app.use(rawBodyReader);
|
2023-08-01 08:32:30 -07:00
|
|
|
app.use(cookieParser());
|
|
|
|
|
2023-10-25 07:35:22 -07:00
|
|
|
// Mock all telemetry and logging
|
|
|
|
const logger = mockInstance(Logger);
|
|
|
|
mockInstance(InternalHooks);
|
|
|
|
mockInstance(PostHogClient);
|
2023-09-20 06:21:42 -07:00
|
|
|
|
2023-07-13 01:14:48 -07:00
|
|
|
const testServer: TestServer = {
|
|
|
|
app,
|
|
|
|
httpServer: app.listen(0),
|
|
|
|
authAgentFor: (user: User) => createAgent(app, { auth: true, user }),
|
|
|
|
authlessAgent: createAgent(app),
|
|
|
|
publicApiAgentFor: (user) => publicApiAgent(app, { user }),
|
|
|
|
};
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
await testDb.init();
|
|
|
|
|
|
|
|
config.set('userManagement.jwtSecret', 'My JWT secret');
|
|
|
|
config.set('userManagement.isInstanceOwnerSetUp', true);
|
|
|
|
|
|
|
|
if (enabledFeatures) {
|
|
|
|
Container.get(License).isFeatureEnabled = (feature) => enabledFeatures.includes(feature);
|
|
|
|
}
|
|
|
|
|
|
|
|
const enablePublicAPI = endpointGroups?.includes('publicApi');
|
|
|
|
if (applyAuth && !enablePublicAPI) {
|
|
|
|
setupAuthMiddlewares(app, AUTHLESS_ENDPOINTS, REST_PATH_SEGMENT);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!endpointGroups) return;
|
|
|
|
|
2023-08-11 00:18:33 -07:00
|
|
|
app.use(bodyParser);
|
2023-08-01 08:32:30 -07:00
|
|
|
|
2023-07-13 01:14:48 -07:00
|
|
|
const [routerEndpoints, functionEndpoints] = classifyEndpointGroups(endpointGroups);
|
|
|
|
|
|
|
|
if (routerEndpoints.length) {
|
|
|
|
const map: Record<string, express.Router | express.Router[] | any> = {
|
|
|
|
credentials: { controller: credentialsController, path: 'credentials' },
|
|
|
|
workflows: { controller: workflowsController, path: 'workflows' },
|
|
|
|
license: { controller: licenseController, path: 'license' },
|
|
|
|
variables: { controller: variablesController, path: 'variables' },
|
2023-09-20 06:21:42 -07:00
|
|
|
executions: { controller: executionsController, path: 'executions' },
|
2023-07-13 01:14:48 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
if (enablePublicAPI) {
|
|
|
|
const { apiRouters } = await loadPublicApiVersions(PUBLIC_API_REST_PATH_SEGMENT);
|
|
|
|
map.publicApi = apiRouters;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const group of routerEndpoints) {
|
|
|
|
if (group === 'publicApi') {
|
|
|
|
app.use(...(map[group] as express.Router[]));
|
|
|
|
} else {
|
|
|
|
app.use(`/${REST_PATH_SEGMENT}/${map[group].path}`, map[group].controller);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (functionEndpoints.length) {
|
|
|
|
const externalHooks = Container.get(ExternalHooks);
|
|
|
|
const internalHooks = Container.get(InternalHooks);
|
|
|
|
const mailer = Container.get(UserManagementMailer);
|
2023-10-23 04:39:35 -07:00
|
|
|
const mfaService = Container.get(MfaService);
|
2023-08-25 04:23:22 -07:00
|
|
|
const userService = Container.get(UserService);
|
2023-07-13 01:14:48 -07:00
|
|
|
|
|
|
|
for (const group of functionEndpoints) {
|
|
|
|
switch (group) {
|
2023-08-04 11:51:07 -07:00
|
|
|
case 'metrics':
|
|
|
|
await Container.get(MetricsService).configureMetrics(app);
|
|
|
|
break;
|
2023-07-13 01:14:48 -07:00
|
|
|
case 'eventBus':
|
|
|
|
registerController(app, config, new EventBusController());
|
2023-09-05 04:32:09 -07:00
|
|
|
registerController(app, config, new EventBusControllerEE());
|
2023-07-13 01:14:48 -07:00
|
|
|
break;
|
|
|
|
case 'auth':
|
2023-10-25 07:35:22 -07:00
|
|
|
registerController(app, config, Container.get(AuthController));
|
2023-07-13 01:14:48 -07:00
|
|
|
break;
|
2023-08-23 19:59:16 -07:00
|
|
|
case 'mfa':
|
|
|
|
registerController(app, config, new MFAController(mfaService));
|
2023-07-13 01:14:48 -07:00
|
|
|
case 'ldap':
|
|
|
|
Container.get(License).isLdapEnabled = () => true;
|
|
|
|
await handleLdapInit();
|
|
|
|
const { service, sync } = LdapManager.getInstance();
|
|
|
|
registerController(app, config, new LdapController(service, sync, internalHooks));
|
|
|
|
break;
|
|
|
|
case 'saml':
|
|
|
|
await setSamlLoginEnabled(true);
|
|
|
|
registerController(app, config, Container.get(SamlController));
|
|
|
|
break;
|
|
|
|
case 'sourceControl':
|
|
|
|
registerController(app, config, Container.get(SourceControlController));
|
|
|
|
break;
|
2023-10-09 07:09:23 -07:00
|
|
|
case 'community-packages':
|
|
|
|
const { CommunityPackagesController } = await import(
|
|
|
|
'@/controllers/communityPackages.controller'
|
2023-07-13 01:14:48 -07:00
|
|
|
);
|
2023-10-09 07:09:23 -07:00
|
|
|
registerController(app, config, Container.get(CommunityPackagesController));
|
2023-07-13 01:14:48 -07:00
|
|
|
case 'me':
|
2023-10-25 07:35:22 -07:00
|
|
|
registerController(app, config, Container.get(MeController));
|
2023-07-13 01:14:48 -07:00
|
|
|
break;
|
|
|
|
case 'passwordReset':
|
2023-11-07 06:35:43 -08:00
|
|
|
registerController(app, config, Container.get(PasswordResetController));
|
2023-07-13 01:14:48 -07:00
|
|
|
break;
|
|
|
|
case 'owner':
|
|
|
|
registerController(
|
|
|
|
app,
|
|
|
|
config,
|
2023-08-25 04:23:22 -07:00
|
|
|
new OwnerController(
|
2023-08-22 06:58:05 -07:00
|
|
|
config,
|
|
|
|
logger,
|
|
|
|
internalHooks,
|
2023-08-25 04:23:22 -07:00
|
|
|
Container.get(SettingsRepository),
|
|
|
|
userService,
|
|
|
|
),
|
2023-07-13 01:14:48 -07:00
|
|
|
);
|
|
|
|
break;
|
|
|
|
case 'users':
|
|
|
|
registerController(
|
|
|
|
app,
|
|
|
|
config,
|
2023-08-25 04:23:22 -07:00
|
|
|
new UsersController(
|
2023-07-13 01:14:48 -07:00
|
|
|
config,
|
2023-08-25 04:23:22 -07:00
|
|
|
logger,
|
2023-07-13 01:14:48 -07:00
|
|
|
externalHooks,
|
|
|
|
internalHooks,
|
2023-08-25 04:23:22 -07:00
|
|
|
Container.get(SharedCredentialsRepository),
|
|
|
|
Container.get(SharedWorkflowRepository),
|
|
|
|
Container.get(ActiveWorkflowRunner),
|
|
|
|
mailer,
|
|
|
|
Container.get(JwtService),
|
|
|
|
Container.get(RoleService),
|
|
|
|
userService,
|
|
|
|
),
|
2023-07-13 01:14:48 -07:00
|
|
|
);
|
2023-07-20 02:34:45 -07:00
|
|
|
break;
|
|
|
|
case 'tags':
|
2023-08-08 05:08:56 -07:00
|
|
|
registerController(app, config, Container.get(TagsController));
|
2023-07-20 02:34:45 -07:00
|
|
|
break;
|
2023-08-25 01:33:46 -07:00
|
|
|
case 'externalSecrets':
|
|
|
|
registerController(app, config, Container.get(ExternalSecretsController));
|
|
|
|
break;
|
2023-09-27 07:22:39 -07:00
|
|
|
case 'workflowHistory':
|
|
|
|
registerController(app, config, Container.get(WorkflowHistoryController));
|
|
|
|
break;
|
2023-10-06 07:21:13 -07:00
|
|
|
case 'binaryData':
|
|
|
|
registerController(app, config, Container.get(BinaryDataController));
|
|
|
|
break;
|
2023-07-13 01:14:48 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
await testDb.terminate();
|
|
|
|
testServer.httpServer.close();
|
|
|
|
});
|
|
|
|
|
|
|
|
return testServer;
|
|
|
|
};
|