diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 5dcf1c9dfb28e..c57989138a540 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -170,10 +170,12 @@ import { DEFAULT_EXECUTIONS_GET_ALL_LIMIT, validateEntity } from './GenericHelpe import { ExecutionEntity } from './databases/entities/ExecutionEntity'; import { SharedWorkflow } from './databases/entities/SharedWorkflow'; import { AUTH_COOKIE_NAME, RESPONSE_ERROR_MESSAGES } from './constants'; -import { credentialsEndpoints } from './api/namespaces/credentials'; +import { credentialsController } from './api/credentials.api'; require('body-parser-xml')(bodyParser); +export const externalHooks: IExternalHooksClass = ExternalHooks(); + class App { app: express.Application; @@ -263,7 +265,7 @@ class App { this.sslKey = config.get('ssl_key'); this.sslCert = config.get('ssl_cert'); - this.externalHooks = ExternalHooks(); + this.externalHooks = externalHooks; this.presetCredentialsLoaded = false; this.endpointPresetCredentials = config.get('credentials.overwrite.endpoint') as string; @@ -659,6 +661,8 @@ class App { // ---------------------------------------- await userManagementRouter.addRoutes.apply(this, [ignoredEndpoints, this.restEndpoint]); + this.app.use(`/${this.restEndpoint}/credentials`, credentialsController); + // ---------------------------------------- // Healthcheck // ---------------------------------------- @@ -1559,12 +1563,6 @@ class App { }), ); - // ---------------------------------------- - // Credentials - // ---------------------------------------- - - credentialsEndpoints.apply(this); - // ---------------------------------------- // Credential-Types // ---------------------------------------- diff --git a/packages/cli/src/api/credentials.api.ts b/packages/cli/src/api/credentials.api.ts new file mode 100644 index 0000000000000..4c612246d04a3 --- /dev/null +++ b/packages/cli/src/api/credentials.api.ts @@ -0,0 +1,382 @@ +/* eslint-disable @typescript-eslint/no-shadow */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +/* eslint-disable import/no-cycle */ +import express = require('express'); +import { getConnection, In } from 'typeorm'; +import { UserSettings, Credentials } from 'n8n-core'; +import { INodeCredentialTestResult } from 'n8n-workflow'; + +import { + CredentialsHelper, + Db, + GenericHelpers, + ICredentialsDb, + ICredentialsResponse, + whereClause, +} from '..'; +import * as ResponseHelper from '../ResponseHelper'; + +import { RESPONSE_ERROR_MESSAGES } from '../constants'; +import { CredentialsEntity } from '../databases/entities/CredentialsEntity'; +import { SharedCredentials } from '../databases/entities/SharedCredentials'; +import { validateEntity } from '../GenericHelpers'; +import type { CredentialRequest } from '../requests'; +import config = require('../../config'); +import { externalHooks } from '../Server'; + +export const credentialsController = express.Router(); + +/** + * GET /credentials + */ +credentialsController.get( + '/', + ResponseHelper.send(async (req: CredentialRequest.GetAll): Promise => { + let credentials: ICredentialsDb[] = []; + + const filter = req.query.filter ? (JSON.parse(req.query.filter) as Record) : {}; + + if (req.user.globalRole.name === 'owner') { + credentials = await Db.collections.Credentials!.find({ + select: ['id', 'name', 'type', 'nodesAccess', 'createdAt', 'updatedAt'], + where: filter, + }); + } else { + const shared = await Db.collections.SharedCredentials!.find({ + where: whereClause({ + user: req.user, + entityType: 'credentials', + }), + }); + + if (!shared.length) return []; + + credentials = await Db.collections.Credentials!.find({ + select: ['id', 'name', 'type', 'nodesAccess', 'createdAt', 'updatedAt'], + where: { + id: In(shared.map(({ credentialId }) => credentialId)), + ...filter, + }, + }); + } + + return credentials.map((credential) => { + // eslint-disable-next-line no-param-reassign + credential.id = credential.id.toString(); + return credential as ICredentialsResponse; + }); + }), +); + +/** + * GET /credentials/new + * + * Generate a unique credential name. + */ +credentialsController.get( + '/new', + ResponseHelper.send(async (req: CredentialRequest.NewName): Promise<{ name: string }> => { + const { name: newName } = req.query; + + return GenericHelpers.generateUniqueName( + newName ?? config.get('credentials.defaultName'), + 'credentials', + ); + }), +); + +/** + * POST /credentials/test + * + * Test if a credential is valid. + */ +credentialsController.post( + '/test', + ResponseHelper.send(async (req: CredentialRequest.Test): Promise => { + const { credentials, nodeToTestWith } = req.body; + + const encryptionKey = await UserSettings.getEncryptionKey(); + + if (!encryptionKey) { + return { + status: 'Error', + message: RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY, + }; + } + + const helper = new CredentialsHelper(encryptionKey); + + return helper.testCredentials(req.user, credentials.type, credentials, nodeToTestWith); + }), +); + +/** + * POST /credentials + */ +credentialsController.post( + '/', + ResponseHelper.send(async (req: CredentialRequest.Create) => { + delete req.body.id; // delete if sent + + const newCredential = new CredentialsEntity(); + + Object.assign(newCredential, req.body); + + await validateEntity(newCredential); + + // Add the added date for node access permissions + for (const nodeAccess of newCredential.nodesAccess) { + nodeAccess.date = new Date(); + } + + const encryptionKey = await UserSettings.getEncryptionKey(); + + if (!encryptionKey) { + throw new ResponseHelper.ResponseError( + RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY, + undefined, + 500, + ); + } + + // Encrypt the data + const coreCredential = new Credentials( + { id: null, name: newCredential.name }, + newCredential.type, + newCredential.nodesAccess, + ); + + // @ts-ignore + coreCredential.setData(newCredential.data, encryptionKey); + + const encryptedData = coreCredential.getDataToSave() as ICredentialsDb; + + Object.assign(newCredential, encryptedData); + + await externalHooks.run('credentials.create', [encryptedData]); + + const role = await Db.collections.Role!.findOneOrFail({ + name: 'owner', + scope: 'credential', + }); + + const { id, ...rest } = await getConnection().transaction(async (transactionManager) => { + const savedCredential = await transactionManager.save(newCredential); + + savedCredential.data = newCredential.data; + + const newSharedCredential = new SharedCredentials(); + + Object.assign(newSharedCredential, { + role, + user: req.user, + credentials: savedCredential, + }); + + await transactionManager.save(newSharedCredential); + + return savedCredential; + }); + + return { id: id.toString(), ...rest }; + }), +); + +/** + * DELETE /credentials/:id + */ +credentialsController.delete( + '/:id', + ResponseHelper.send(async (req: CredentialRequest.Delete) => { + const { id: credentialId } = req.params; + + const shared = await Db.collections.SharedCredentials!.findOne({ + relations: ['credentials'], + where: whereClause({ + user: req.user, + entityType: 'credentials', + entityId: credentialId, + }), + }); + + if (!shared) { + throw new ResponseHelper.ResponseError( + `Credential with ID "${credentialId}" could not be found to be deleted.`, + undefined, + 404, + ); + } + + await externalHooks.run('credentials.delete', [credentialId]); + + await Db.collections.Credentials!.remove(shared.credentials); + + return true; + }), +); + +/** + * PATCH /credentials/:id + */ +credentialsController.patch( + '/:id', + ResponseHelper.send(async (req: CredentialRequest.Update): Promise => { + const { id: credentialId } = req.params; + + const updateData = new CredentialsEntity(); + Object.assign(updateData, req.body); + + await validateEntity(updateData); + + const shared = await Db.collections.SharedCredentials!.findOne({ + relations: ['credentials'], + where: whereClause({ + user: req.user, + entityType: 'credentials', + entityId: credentialId, + }), + }); + + if (!shared) { + throw new ResponseHelper.ResponseError( + `Credential with ID "${credentialId}" could not be found to be updated.`, + undefined, + 404, + ); + } + + const { credentials: credential } = shared; + + // Add the date for newly added node access permissions + for (const nodeAccess of updateData.nodesAccess) { + if (!nodeAccess.date) { + nodeAccess.date = new Date(); + } + } + + const encryptionKey = await UserSettings.getEncryptionKey(); + + if (!encryptionKey) { + throw new ResponseHelper.ResponseError( + RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY, + undefined, + 500, + ); + } + + const coreCredential = new Credentials( + { id: credential.id.toString(), name: credential.name }, + credential.type, + credential.nodesAccess, + credential.data, + ); + + const decryptedData = coreCredential.getData(encryptionKey); + + // Do not overwrite the oauth data else data like the access or refresh token would get lost + // everytime anybody changes anything on the credentials even if it is just the name. + if (decryptedData.oauthTokenData) { + // @ts-ignore + updateData.data.oauthTokenData = decryptedData.oauthTokenData; + } + + // Encrypt the data + const credentials = new Credentials( + { id: credentialId, name: updateData.name }, + updateData.type, + updateData.nodesAccess, + ); + + // @ts-ignore + credentials.setData(updateData.data, encryptionKey); + + const newCredentialData = credentials.getDataToSave() as ICredentialsDb; + + // Add special database related data + newCredentialData.updatedAt = new Date(); + + await externalHooks.run('credentials.update', [newCredentialData]); + + // Update the credentials in DB + await Db.collections.Credentials!.update(credentialId, newCredentialData); + + // We sadly get nothing back from "update". Neither if it updated a record + // nor the new value. So query now the updated entry. + const responseData = await Db.collections.Credentials!.findOne(credentialId); + + if (responseData === undefined) { + throw new ResponseHelper.ResponseError( + `Credential ID "${credentialId}" could not be found to be updated.`, + undefined, + 404, + ); + } + + // Remove the encrypted data as it is not needed in the frontend + const { id, data, ...rest } = responseData; + + return { + id: id.toString(), + ...rest, + }; + }), +); + +/** + * GET /credentials/:id + */ +credentialsController.get( + '/:id', + ResponseHelper.send(async (req: CredentialRequest.Get) => { + const { id: credentialId } = req.params; + + const shared = await Db.collections.SharedCredentials!.findOne({ + relations: ['credentials'], + where: whereClause({ + user: req.user, + entityType: 'credentials', + entityId: credentialId, + }), + }); + + if (!shared) return {}; + + const { credentials: credential } = shared; + + if (req.query.includeData !== 'true') { + const { data, id, ...rest } = credential; + + return { + id: id.toString(), + ...rest, + }; + } + + const { data, id, ...rest } = credential; + + const encryptionKey = await UserSettings.getEncryptionKey(); + + if (!encryptionKey) { + throw new ResponseHelper.ResponseError( + RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY, + undefined, + 500, + ); + } + + const coreCredential = new Credentials( + { id: credential.id.toString(), name: credential.name }, + credential.type, + credential.nodesAccess, + credential.data, + ); + + return { + id: id.toString(), + data: coreCredential.getData(encryptionKey), + ...rest, + }; + }), +); diff --git a/packages/cli/src/api/namespaces/credentials.ts b/packages/cli/src/api/namespaces/credentials.ts deleted file mode 100644 index e941fba7b52f0..0000000000000 --- a/packages/cli/src/api/namespaces/credentials.ts +++ /dev/null @@ -1,373 +0,0 @@ -/* eslint-disable @typescript-eslint/no-shadow */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable no-restricted-syntax */ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable import/no-cycle */ -import { getConnection, In } from 'typeorm'; -import { UserSettings, Credentials } from 'n8n-core'; -import { INodeCredentialTestResult } from 'n8n-workflow'; - -import { - CredentialsHelper, - Db, - GenericHelpers, - ICredentialsDb, - ICredentialsResponse, - ResponseHelper, - whereClause, -} from '../..'; -import { RESPONSE_ERROR_MESSAGES } from '../../constants'; -import { CredentialsEntity } from '../../databases/entities/CredentialsEntity'; -import { SharedCredentials } from '../../databases/entities/SharedCredentials'; -import { validateEntity } from '../../GenericHelpers'; -import type { CredentialRequest } from '../../requests'; -import type { N8nApp } from '../../UserManagement/Interfaces'; - -export function credentialsEndpoints(this: N8nApp): void { - /** - * Generate a unique credential name. - */ - this.app.get( - `/${this.restEndpoint}/credentials/new`, - ResponseHelper.send(async (req: CredentialRequest.NewName): Promise<{ name: string }> => { - const { name: newName } = req.query; - - return GenericHelpers.generateUniqueName( - newName ?? this.defaultCredentialsName, - 'credentials', - ); - }), - ); - - /** - * Test a credential. - */ - this.app.post( - `/${this.restEndpoint}/credentials-test`, - ResponseHelper.send(async (req: CredentialRequest.Test): Promise => { - const { credentials, nodeToTestWith } = req.body; - - const encryptionKey = await UserSettings.getEncryptionKey(); - - if (!encryptionKey) { - return { - status: 'Error', - message: RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY, - }; - } - - const helper = new CredentialsHelper(encryptionKey); - - return helper.testCredentials(req.user, credentials.type, credentials, nodeToTestWith); - }), - ); - - /** - * Create a credential. - */ - this.app.post( - `/${this.restEndpoint}/credentials`, - ResponseHelper.send(async (req: CredentialRequest.Create) => { - delete req.body.id; // delete if sent - - const newCredential = new CredentialsEntity(); - - Object.assign(newCredential, req.body); - - await validateEntity(newCredential); - - // Add the added date for node access permissions - for (const nodeAccess of newCredential.nodesAccess) { - nodeAccess.date = new Date(); - } - - const encryptionKey = await UserSettings.getEncryptionKey(); - - if (!encryptionKey) { - throw new ResponseHelper.ResponseError( - RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY, - undefined, - 500, - ); - } - - // Encrypt the data - const coreCredential = new Credentials( - { id: null, name: newCredential.name }, - newCredential.type, - newCredential.nodesAccess, - ); - - // @ts-ignore - coreCredential.setData(newCredential.data, encryptionKey); - - const encryptedData = coreCredential.getDataToSave() as ICredentialsDb; - - Object.assign(newCredential, encryptedData); - - await this.externalHooks.run('credentials.create', [encryptedData]); - - const role = await Db.collections.Role!.findOneOrFail({ - name: 'owner', - scope: 'credential', - }); - - const { id, ...rest } = await getConnection().transaction(async (transactionManager) => { - const savedCredential = await transactionManager.save(newCredential); - - savedCredential.data = newCredential.data; - - const newSharedCredential = new SharedCredentials(); - - Object.assign(newSharedCredential, { - role, - user: req.user, - credentials: savedCredential, - }); - - await transactionManager.save(newSharedCredential); - - return savedCredential; - }); - - return { id: id.toString(), ...rest }; - }), - ); - - /** - * Delete a credential. - */ - this.app.delete( - `/${this.restEndpoint}/credentials/:id`, - ResponseHelper.send(async (req: CredentialRequest.Delete) => { - const { id: credentialId } = req.params; - - const shared = await Db.collections.SharedCredentials!.findOne({ - relations: ['credentials'], - where: whereClause({ - user: req.user, - entityType: 'credentials', - entityId: credentialId, - }), - }); - - if (!shared) { - throw new ResponseHelper.ResponseError( - `Credential with ID "${credentialId}" could not be found to be deleted.`, - undefined, - 404, - ); - } - - await this.externalHooks.run('credentials.delete', [credentialId]); - - await Db.collections.Credentials!.remove(shared.credentials); - - return true; - }), - ); - - /** - * Update a credential. - */ - this.app.patch( - `/${this.restEndpoint}/credentials/:id`, - ResponseHelper.send(async (req: CredentialRequest.Update): Promise => { - const { id: credentialId } = req.params; - - const updateData = new CredentialsEntity(); - Object.assign(updateData, req.body); - - await validateEntity(updateData); - - const shared = await Db.collections.SharedCredentials!.findOne({ - relations: ['credentials'], - where: whereClause({ - user: req.user, - entityType: 'credentials', - entityId: credentialId, - }), - }); - - if (!shared) { - throw new ResponseHelper.ResponseError( - `Credential with ID "${credentialId}" could not be found to be updated.`, - undefined, - 404, - ); - } - - const { credentials: credential } = shared; - - // Add the date for newly added node access permissions - for (const nodeAccess of updateData.nodesAccess) { - if (!nodeAccess.date) { - nodeAccess.date = new Date(); - } - } - - const encryptionKey = await UserSettings.getEncryptionKey(); - - if (!encryptionKey) { - throw new ResponseHelper.ResponseError( - RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY, - undefined, - 500, - ); - } - - const coreCredential = new Credentials( - { id: credential.id.toString(), name: credential.name }, - credential.type, - credential.nodesAccess, - credential.data, - ); - - const decryptedData = coreCredential.getData(encryptionKey); - - // Do not overwrite the oauth data else data like the access or refresh token would get lost - // everytime anybody changes anything on the credentials even if it is just the name. - if (decryptedData.oauthTokenData) { - // @ts-ignore - updateData.data.oauthTokenData = decryptedData.oauthTokenData; - } - - // Encrypt the data - const credentials = new Credentials( - { id: credentialId, name: updateData.name }, - updateData.type, - updateData.nodesAccess, - ); - - // @ts-ignore - credentials.setData(updateData.data, encryptionKey); - - const newCredentialData = credentials.getDataToSave() as ICredentialsDb; - - // Add special database related data - newCredentialData.updatedAt = new Date(); - - await this.externalHooks.run('credentials.update', [newCredentialData]); - - // Update the credentials in DB - await Db.collections.Credentials!.update(credentialId, newCredentialData); - - // We sadly get nothing back from "update". Neither if it updated a record - // nor the new value. So query now the updated entry. - const responseData = await Db.collections.Credentials!.findOne(credentialId); - - if (responseData === undefined) { - throw new ResponseHelper.ResponseError( - `Credential ID "${credentialId}" could not be found to be updated.`, - undefined, - 404, - ); - } - - // Remove the encrypted data as it is not needed in the frontend - const { id, data, ...rest } = responseData; - - return { - id: id.toString(), - ...rest, - }; - }), - ); - - /** - * Retrieve a credential. - */ - this.app.get( - `/${this.restEndpoint}/credentials/:id`, - ResponseHelper.send(async (req: CredentialRequest.Get) => { - const { id: credentialId } = req.params; - - const shared = await Db.collections.SharedCredentials!.findOne({ - relations: ['credentials'], - where: whereClause({ - user: req.user, - entityType: 'credentials', - entityId: credentialId, - }), - }); - - if (!shared) return {}; - - const { credentials: credential } = shared; - - if (req.query.includeData !== 'true') { - const { data, id, ...rest } = credential; - - return { - id: id.toString(), - ...rest, - }; - } - - const { data, id, ...rest } = credential; - - const encryptionKey = await UserSettings.getEncryptionKey(); - - if (!encryptionKey) { - throw new ResponseHelper.ResponseError( - RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY, - undefined, - 500, - ); - } - - const coreCredential = new Credentials( - { id: credential.id.toString(), name: credential.name }, - credential.type, - credential.nodesAccess, - credential.data, - ); - - return { - id: id.toString(), - data: coreCredential.getData(encryptionKey), - ...rest, - }; - }), - ); - - /** - * Retrieve all credentials. - */ - this.app.get( - `/${this.restEndpoint}/credentials`, - ResponseHelper.send(async (req: CredentialRequest.GetAll): Promise => { - let credentials: ICredentialsDb[] = []; - - const filter = req.query.filter - ? (JSON.parse(req.query.filter) as Record) - : {}; - - if (req.user.globalRole.name === 'owner') { - credentials = await Db.collections.Credentials!.find({ - select: ['id', 'name', 'type', 'nodesAccess', 'createdAt', 'updatedAt'], - where: filter, - }); - } else { - const shared = await Db.collections.SharedCredentials!.find({ - where: whereClause({ - user: req.user, - entityType: 'credentials', - }), - }); - - if (!shared.length) return []; - - credentials = await Db.collections.Credentials!.find({ - select: ['id', 'name', 'type', 'nodesAccess', 'createdAt', 'updatedAt'], - where: { - id: In(shared.map(({ credentialId }) => credentialId)), - ...filter, - }, - }); - } - - return credentials.map(({ id, ...rest }) => ({ id: id.toString(), ...rest })); - }), - ); -} diff --git a/packages/cli/test/integration/auth.endpoints.test.ts b/packages/cli/test/integration/auth.endpoints.test.ts index b9d7ea8b308b1..5d9510041cde9 100644 --- a/packages/cli/test/integration/auth.endpoints.test.ts +++ b/packages/cli/test/integration/auth.endpoints.test.ts @@ -17,7 +17,7 @@ let globalOwnerRole: Role; let app: express.Application; beforeAll(async () => { - app = utils.initTestServer({ namespaces: ['auth'], applyAuth: true }); + app = utils.initTestServer({ endpointGroups: ['auth'], applyAuth: true }); await utils.initTestDb(); await utils.truncate(['User']); diff --git a/packages/cli/test/integration/auth.middleware.test.ts b/packages/cli/test/integration/auth.middleware.test.ts index daf588219ea74..d2de8f727d8c9 100644 --- a/packages/cli/test/integration/auth.middleware.test.ts +++ b/packages/cli/test/integration/auth.middleware.test.ts @@ -12,7 +12,7 @@ import * as utils from './shared/utils'; let app: express.Application; beforeAll(async () => { - app = utils.initTestServer({ applyAuth: true, namespaces: ['me', 'auth', 'owner', 'users'] }); + app = utils.initTestServer({ applyAuth: true, endpointGroups: ['me', 'auth', 'owner', 'users'] }); await utils.initTestDb(); utils.initLogger(); }); diff --git a/packages/cli/test/integration/credentials.endpoints.test.ts b/packages/cli/test/integration/credentials.api.test.ts similarity index 99% rename from packages/cli/test/integration/credentials.endpoints.test.ts rename to packages/cli/test/integration/credentials.api.test.ts index 157e960123ce1..8ff1d35d9d261 100644 --- a/packages/cli/test/integration/credentials.endpoints.test.ts +++ b/packages/cli/test/integration/credentials.api.test.ts @@ -11,11 +11,7 @@ let app: express.Application; let saveCredential: SaveCredentialFunction; beforeAll(async () => { - app = utils.initTestServer({ - namespaces: ['credentials'], - applyAuth: true, - externalHooks: true, - }); + app = utils.initTestServer({ endpointGroups: ['credentials'], applyAuth: true }); await utils.initTestDb(); utils.initConfigFile(); diff --git a/packages/cli/test/integration/me.endpoints.test.ts b/packages/cli/test/integration/me.endpoints.test.ts index 639c815bbbb1d..b931b3a5a7550 100644 --- a/packages/cli/test/integration/me.endpoints.test.ts +++ b/packages/cli/test/integration/me.endpoints.test.ts @@ -23,7 +23,7 @@ let app: express.Application; let globalOwnerRole: Role; beforeAll(async () => { - app = utils.initTestServer({ namespaces: ['me'], applyAuth: true }); + app = utils.initTestServer({ endpointGroups: ['me'], applyAuth: true }); await utils.initTestDb(); globalOwnerRole = await getGlobalOwnerRole(); utils.initLogger(); diff --git a/packages/cli/test/integration/owner.endpoints.test.ts b/packages/cli/test/integration/owner.endpoints.test.ts index ce4725912eb2f..d9f81008b4831 100644 --- a/packages/cli/test/integration/owner.endpoints.test.ts +++ b/packages/cli/test/integration/owner.endpoints.test.ts @@ -15,7 +15,7 @@ import { let app: express.Application; beforeAll(async () => { - app = utils.initTestServer({ namespaces: ['owner'], applyAuth: true }); + app = utils.initTestServer({ endpointGroups: ['owner'], applyAuth: true }); await utils.initTestDb(); utils.initLogger(); diff --git a/packages/cli/test/integration/passwordReset.endpoints.test.ts b/packages/cli/test/integration/passwordReset.endpoints.test.ts index 12fadbc398d6a..d449283c6de76 100644 --- a/packages/cli/test/integration/passwordReset.endpoints.test.ts +++ b/packages/cli/test/integration/passwordReset.endpoints.test.ts @@ -18,7 +18,7 @@ let app: express.Application; let globalOwnerRole: Role; beforeAll(async () => { - app = utils.initTestServer({ namespaces: ['passwordReset'], applyAuth: true }); + app = utils.initTestServer({ endpointGroups: ['passwordReset'], applyAuth: true }); await utils.initTestDb(); await utils.truncate(['User']); diff --git a/packages/cli/test/integration/shared/types.d.ts b/packages/cli/test/integration/shared/types.d.ts index cd88521434d56..b160faea01a8a 100644 --- a/packages/cli/test/integration/shared/types.d.ts +++ b/packages/cli/test/integration/shared/types.d.ts @@ -3,8 +3,6 @@ import type { ICredentialsDb } from '../../../src'; import type { CredentialsEntity } from '../../../src/databases/entities/CredentialsEntity'; import type { User } from '../../../src/databases/entities/User'; -import type { N8nApp } from '../../../src/UserManagement/Interfaces'; - export type SmtpTestAccount = { user: string; pass: string; @@ -15,9 +13,7 @@ export type SmtpTestAccount = { }; }; -export type EndpointNamespace = 'me' | 'users' | 'auth' | 'owner' | 'passwordReset' | 'credentials'; - -export type NamespacesMap = Readonly void>>; +type EndpointGroup = 'me' | 'users' | 'auth' | 'owner' | 'passwordReset' | 'credentials'; export type CredentialPayload = { name: string; diff --git a/packages/cli/test/integration/shared/utils.ts b/packages/cli/test/integration/shared/utils.ts index 6e7f49e57115f..ec3d75c0dd0ea 100644 --- a/packages/cli/test/integration/shared/utils.ts +++ b/packages/cli/test/integration/shared/utils.ts @@ -22,15 +22,18 @@ import { usersNamespace as usersEndpoints } from '../../../src/UserManagement/ro import { authenticationMethods as authEndpoints } from '../../../src/UserManagement/routes/auth'; import { ownerNamespace as ownerEndpoints } from '../../../src/UserManagement/routes/owner'; import { passwordResetNamespace as passwordResetEndpoints } from '../../../src/UserManagement/routes/passwordReset'; -import { credentialsEndpoints } from '../../../src/api/namespaces/credentials'; + import { issueJWT } from '../../../src/UserManagement/auth/jwt'; +import { credentialsController } from '../../../src/api/credentials.api'; import { randomEmail, randomValidPassword, randomName } from './random'; import { getLogger } from '../../../src/Logger'; import { CredentialsEntity } from '../../../src/databases/entities/CredentialsEntity'; import { RESPONSE_ERROR_MESSAGES } from '../../../src/constants'; + import type { Role } from '../../../src/databases/entities/Role'; import type { User } from '../../../src/databases/entities/User'; -import type { CredentialPayload, EndpointNamespace, NamespacesMap, SmtpTestAccount } from './types'; +import type { N8nApp } from '../../../src/UserManagement/Interfaces'; +import type { CredentialPayload, SmtpTestAccount, EndpointGroup } from './types'; export const isTestRun = process.argv[1].split('/').includes('jest'); // TODO: Phase out @@ -47,21 +50,19 @@ export const initLogger = () => { * Initialize a test server to make requests to. * * @param applyAuth Whether to apply auth middleware to the test server. - * @param namespaces Namespaces of endpoints to apply to the test server. + * @param endpointGroups Groups of endpoints to apply to the test server. */ export function initTestServer({ applyAuth, - namespaces, - externalHooks, + endpointGroups, }: { applyAuth: boolean; - externalHooks?: true; - namespaces?: EndpointNamespace[]; + endpointGroups?: EndpointGroup[]; }) { const testServer = { app: express(), restEndpoint: REST_PATH_SEGMENT, - ...(externalHooks ? { externalHooks: ExternalHooks() } : {}), + ...(endpointGroups?.includes('credentials') ? { externalHooks: ExternalHooks() } : {}), }; testServer.app.use(bodyParser.json()); @@ -74,24 +75,48 @@ export function initTestServer({ authMiddleware.apply(testServer, [AUTHLESS_ENDPOINTS, REST_PATH_SEGMENT]); } - if (namespaces) { - const map: NamespacesMap = { + if (!endpointGroups) return testServer.app; + + const [routerEndpoints, functionEndpoints] = classifyEndpointGroups(endpointGroups); + + if (routerEndpoints.length) { + const map: Record = { + credentials: credentialsController, + }; + + for (const group of routerEndpoints) { + testServer.app.use(`/${testServer.restEndpoint}/${group}`, map[group]); + } + } + + if (functionEndpoints.length) { + const map: Record void> = { me: meEndpoints, users: usersEndpoints, auth: authEndpoints, owner: ownerEndpoints, passwordReset: passwordResetEndpoints, - credentials: credentialsEndpoints, }; - for (const namespace of namespaces) { - map[namespace].apply(testServer); + for (const group of functionEndpoints) { + map[group].apply(testServer); } } return testServer.app; } +const classifyEndpointGroups = (endpointGroups: string[]) => { + const routerEndpoints: string[] = []; + const functionEndpoints: string[] = []; + + endpointGroups.forEach((group) => + (group === 'credentials' ? routerEndpoints : functionEndpoints).push(group), + ); + + return [routerEndpoints, functionEndpoints]; +}; + // ---------------------------------- // test logger // ---------------------------------- diff --git a/packages/cli/test/integration/users.endpoints.test.ts b/packages/cli/test/integration/users.endpoints.test.ts index d6a785a542f60..3ad22a987b3a1 100644 --- a/packages/cli/test/integration/users.endpoints.test.ts +++ b/packages/cli/test/integration/users.endpoints.test.ts @@ -27,7 +27,7 @@ let workflowOwnerRole: Role; let credentialOwnerRole: Role; beforeAll(async () => { - app = utils.initTestServer({ namespaces: ['users'], applyAuth: true }); + app = utils.initTestServer({ endpointGroups: ['users'], applyAuth: true }); await utils.initTestDb(); const [ diff --git a/packages/editor-ui/src/api/credentials.ts b/packages/editor-ui/src/api/credentials.ts index e8fd347c2d510..9193cc1c01f77 100644 --- a/packages/editor-ui/src/api/credentials.ts +++ b/packages/editor-ui/src/api/credentials.ts @@ -49,5 +49,5 @@ export async function oAuth2CredentialAuthorize(context: IRestApiContext, data: } export async function testCredential(context: IRestApiContext, data: INodeCredentialTestRequest): Promise { - return makeRestApiRequest(context, 'POST', '/credentials-test', data as unknown as IDataObject); + return makeRestApiRequest(context, 'POST', '/test', data as unknown as IDataObject); }