refactor(core): Reorganize error hierarchy in cli package (no-changelog) (#7839)

Ensure all errors in `cli` inherit from `ApplicationError` to continue
normalizing all the errors we report to Sentry

Follow-up to: https://github.com/n8n-io/n8n/pull/7820
This commit is contained in:
Iván Ovejero 2023-11-28 10:19:27 +01:00 committed by GitHub
parent 38f24a6184
commit 1c6178759c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
81 changed files with 346 additions and 297 deletions

View file

@ -11,13 +11,14 @@ import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
import * as Db from '@/Db'; import * as Db from '@/Db';
import type { N8nInstanceType, IExternalHooksClass } from '@/Interfaces'; import type { N8nInstanceType, IExternalHooksClass } from '@/Interfaces';
import { ExternalHooks } from '@/ExternalHooks'; import { ExternalHooks } from '@/ExternalHooks';
import { send, sendErrorResponse, ServiceUnavailableError } from '@/ResponseHelper'; import { send, sendErrorResponse } from '@/ResponseHelper';
import { rawBodyReader, bodyParser, corsMiddleware } from '@/middlewares'; import { rawBodyReader, bodyParser, corsMiddleware } from '@/middlewares';
import { TestWebhooks } from '@/TestWebhooks'; import { TestWebhooks } from '@/TestWebhooks';
import { WaitingWebhooks } from '@/WaitingWebhooks'; import { WaitingWebhooks } from '@/WaitingWebhooks';
import { webhookRequestHandler } from '@/WebhookHelpers'; import { webhookRequestHandler } from '@/WebhookHelpers';
import { generateHostInstanceId } from './databases/utils/generators'; import { generateHostInstanceId } from './databases/utils/generators';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { ServiceUnavailableError } from './errors/response-errors/service-unavailable.error';
export abstract class AbstractServer { export abstract class AbstractServer {
protected logger: Logger; protected logger: Logger;

View file

@ -42,7 +42,6 @@ import type {
WebhookAccessControlOptions, WebhookAccessControlOptions,
WebhookRequest, WebhookRequest,
} from '@/Interfaces'; } from '@/Interfaces';
import * as ResponseHelper from '@/ResponseHelper';
import * as WebhookHelpers from '@/WebhookHelpers'; import * as WebhookHelpers from '@/WebhookHelpers';
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData'; import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
@ -68,6 +67,7 @@ import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.reposi
import { WorkflowRepository } from '@db/repositories/workflow.repository'; import { WorkflowRepository } from '@db/repositories/workflow.repository';
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee'; import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
import { ActivationErrorsService } from '@/ActivationErrors.service'; import { ActivationErrorsService } from '@/ActivationErrors.service';
import { NotFoundError } from './errors/response-errors/not-found.error';
const WEBHOOK_PROD_UNREGISTERED_HINT = const WEBHOOK_PROD_UNREGISTERED_HINT =
"The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)"; "The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)";
@ -165,9 +165,7 @@ export class ActiveWorkflowRunner implements IWebhookManager {
}); });
if (workflowData === null) { if (workflowData === null) {
throw new ResponseHelper.NotFoundError( throw new NotFoundError(`Could not find workflow with id "${webhook.workflowId}"`);
`Could not find workflow with id "${webhook.workflowId}"`,
);
} }
const workflow = new Workflow({ const workflow = new Workflow({
@ -196,7 +194,7 @@ export class ActiveWorkflowRunner implements IWebhookManager {
const workflowStartNode = workflow.getNode(webhookData.node); const workflowStartNode = workflow.getNode(webhookData.node);
if (workflowStartNode === null) { if (workflowStartNode === null) {
throw new ResponseHelper.NotFoundError('Could not find node to process webhook.'); throw new NotFoundError('Could not find node to process webhook.');
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -252,7 +250,7 @@ export class ActiveWorkflowRunner implements IWebhookManager {
const webhook = await this.webhookService.findWebhook(httpMethod, path); const webhook = await this.webhookService.findWebhook(httpMethod, path);
if (webhook === null) { if (webhook === null) {
throw new ResponseHelper.NotFoundError( throw new NotFoundError(
webhookNotFoundErrorMessage(path, httpMethod), webhookNotFoundErrorMessage(path, httpMethod),
WEBHOOK_PROD_UNREGISTERED_HINT, WEBHOOK_PROD_UNREGISTERED_HINT,
); );

View file

@ -32,7 +32,6 @@ import type {
INodeTypes, INodeTypes,
IWorkflowExecuteAdditionalData, IWorkflowExecuteAdditionalData,
ICredentialTestFunctions, ICredentialTestFunctions,
Severity,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
ICredentialsHelper, ICredentialsHelper,
@ -55,6 +54,7 @@ import { isObjectLiteral } from './utils';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { CredentialsRepository } from '@db/repositories/credentials.repository'; import { CredentialsRepository } from '@db/repositories/credentials.repository';
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
import { CredentialNotFoundError } from './errors/credential-not-found.error';
const { OAUTH2_CREDENTIAL_TEST_SUCCEEDED, OAUTH2_CREDENTIAL_TEST_FAILED } = RESPONSE_ERROR_MESSAGES; const { OAUTH2_CREDENTIAL_TEST_SUCCEEDED, OAUTH2_CREDENTIAL_TEST_FAILED } = RESPONSE_ERROR_MESSAGES;
@ -87,15 +87,6 @@ const mockNodeTypes: INodeTypes = {
}, },
}; };
class CredentialNotFoundError extends Error {
severity: Severity;
constructor(credentialId: string, credentialType: string) {
super(`Credential with ID "${credentialId}" does not exist for type "${credentialType}".`);
this.severity = 'warning';
}
}
@Service() @Service()
export class CredentialsHelper extends ICredentialsHelper { export class CredentialsHelper extends ICredentialsHelper {
constructor( constructor(

View file

@ -1,9 +1,10 @@
import { Authorized, Get, Post, RestController } from '@/decorators'; import { Authorized, Get, Post, RestController } from '@/decorators';
import { ExternalSecretsRequest } from '@/requests'; import { ExternalSecretsRequest } from '@/requests';
import { NotFoundError } from '@/ResponseHelper';
import { Response } from 'express'; import { Response } from 'express';
import { Service } from 'typedi'; import { Service } from 'typedi';
import { ProviderNotFoundError, ExternalSecretsService } from './ExternalSecrets.service.ee'; import { ExternalSecretsService } from './ExternalSecrets.service.ee';
import { ExternalSecretsProviderNotFoundError } from '@/errors/external-secrets-provider-not-found.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
@Service() @Service()
@Authorized(['global', 'owner']) @Authorized(['global', 'owner'])
@ -22,7 +23,7 @@ export class ExternalSecretsController {
try { try {
return this.secretsService.getProvider(providerName); return this.secretsService.getProvider(providerName);
} catch (e) { } catch (e) {
if (e instanceof ProviderNotFoundError) { if (e instanceof ExternalSecretsProviderNotFoundError) {
throw new NotFoundError(`Could not find provider "${e.providerName}"`); throw new NotFoundError(`Could not find provider "${e.providerName}"`);
} }
throw e; throw e;
@ -41,7 +42,7 @@ export class ExternalSecretsController {
} }
return result; return result;
} catch (e) { } catch (e) {
if (e instanceof ProviderNotFoundError) { if (e instanceof ExternalSecretsProviderNotFoundError) {
throw new NotFoundError(`Could not find provider "${e.providerName}"`); throw new NotFoundError(`Could not find provider "${e.providerName}"`);
} }
throw e; throw e;
@ -54,7 +55,7 @@ export class ExternalSecretsController {
try { try {
await this.secretsService.saveProviderSettings(providerName, req.body, req.user.id); await this.secretsService.saveProviderSettings(providerName, req.body, req.user.id);
} catch (e) { } catch (e) {
if (e instanceof ProviderNotFoundError) { if (e instanceof ExternalSecretsProviderNotFoundError) {
throw new NotFoundError(`Could not find provider "${e.providerName}"`); throw new NotFoundError(`Could not find provider "${e.providerName}"`);
} }
throw e; throw e;
@ -68,7 +69,7 @@ export class ExternalSecretsController {
try { try {
await this.secretsService.saveProviderConnected(providerName, req.body.connected); await this.secretsService.saveProviderConnected(providerName, req.body.connected);
} catch (e) { } catch (e) {
if (e instanceof ProviderNotFoundError) { if (e instanceof ExternalSecretsProviderNotFoundError) {
throw new NotFoundError(`Could not find provider "${e.providerName}"`); throw new NotFoundError(`Could not find provider "${e.providerName}"`);
} }
throw e; throw e;
@ -88,7 +89,7 @@ export class ExternalSecretsController {
} }
return { updated: resp }; return { updated: resp };
} catch (e) { } catch (e) {
if (e instanceof ProviderNotFoundError) { if (e instanceof ExternalSecretsProviderNotFoundError) {
throw new NotFoundError(`Could not find provider "${e.providerName}"`); throw new NotFoundError(`Could not find provider "${e.providerName}"`);
} }
throw e; throw e;

View file

@ -5,12 +5,7 @@ import type { IDataObject } from 'n8n-workflow';
import { deepCopy } from 'n8n-workflow'; import { deepCopy } from 'n8n-workflow';
import Container, { Service } from 'typedi'; import Container, { Service } from 'typedi';
import { ExternalSecretsManager } from './ExternalSecretsManager.ee'; import { ExternalSecretsManager } from './ExternalSecretsManager.ee';
import { ExternalSecretsProviderNotFoundError } from '@/errors/external-secrets-provider-not-found.error';
export class ProviderNotFoundError extends Error {
constructor(public providerName: string) {
super(undefined);
}
}
@Service() @Service()
export class ExternalSecretsService { export class ExternalSecretsService {
@ -18,7 +13,7 @@ export class ExternalSecretsService {
const providerAndSettings = const providerAndSettings =
Container.get(ExternalSecretsManager).getProviderWithSettings(providerName); Container.get(ExternalSecretsManager).getProviderWithSettings(providerName);
if (!providerAndSettings) { if (!providerAndSettings) {
throw new ProviderNotFoundError(providerName); throw new ExternalSecretsProviderNotFoundError(providerName);
} }
const { provider, settings } = providerAndSettings; const { provider, settings } = providerAndSettings;
return { return {
@ -110,7 +105,7 @@ export class ExternalSecretsService {
const providerAndSettings = const providerAndSettings =
Container.get(ExternalSecretsManager).getProviderWithSettings(providerName); Container.get(ExternalSecretsManager).getProviderWithSettings(providerName);
if (!providerAndSettings) { if (!providerAndSettings) {
throw new ProviderNotFoundError(providerName); throw new ExternalSecretsProviderNotFoundError(providerName);
} }
const { settings } = providerAndSettings; const { settings } = providerAndSettings;
const newData = this.unredact(data, settings.settings); const newData = this.unredact(data, settings.settings);
@ -121,7 +116,7 @@ export class ExternalSecretsService {
const providerAndSettings = const providerAndSettings =
Container.get(ExternalSecretsManager).getProviderWithSettings(providerName); Container.get(ExternalSecretsManager).getProviderWithSettings(providerName);
if (!providerAndSettings) { if (!providerAndSettings) {
throw new ProviderNotFoundError(providerName); throw new ExternalSecretsProviderNotFoundError(providerName);
} }
await Container.get(ExternalSecretsManager).setProviderConnected(providerName, connected); await Container.get(ExternalSecretsManager).setProviderConnected(providerName, connected);
return this.getProvider(providerName); return this.getProvider(providerName);
@ -135,7 +130,7 @@ export class ExternalSecretsService {
const providerAndSettings = const providerAndSettings =
Container.get(ExternalSecretsManager).getProviderWithSettings(providerName); Container.get(ExternalSecretsManager).getProviderWithSettings(providerName);
if (!providerAndSettings) { if (!providerAndSettings) {
throw new ProviderNotFoundError(providerName); throw new ExternalSecretsProviderNotFoundError(providerName);
} }
const { settings } = providerAndSettings; const { settings } = providerAndSettings;
const newData = this.unredact(data, settings.settings); const newData = this.unredact(data, settings.settings);
@ -146,7 +141,7 @@ export class ExternalSecretsService {
const providerAndSettings = const providerAndSettings =
Container.get(ExternalSecretsManager).getProviderWithSettings(providerName); Container.get(ExternalSecretsManager).getProviderWithSettings(providerName);
if (!providerAndSettings) { if (!providerAndSettings) {
throw new ProviderNotFoundError(providerName); throw new ExternalSecretsProviderNotFoundError(providerName);
} }
return Container.get(ExternalSecretsManager).updateProvider(providerName); return Container.get(ExternalSecretsManager).updateProvider(providerName);
} }

View file

@ -11,7 +11,6 @@ import { Container } from 'typedi';
import { Like } from 'typeorm'; import { Like } from 'typeorm';
import config from '@/config'; import config from '@/config';
import type { ExecutionPayload, ICredentialsDb, IWorkflowDb } from '@/Interfaces'; import type { ExecutionPayload, ICredentialsDb, IWorkflowDb } from '@/Interfaces';
import * as ResponseHelper from '@/ResponseHelper';
import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
import type { CredentialsEntity } from '@db/entities/CredentialsEntity'; import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
import type { TagEntity } from '@db/entities/TagEntity'; import type { TagEntity } from '@db/entities/TagEntity';
@ -20,6 +19,7 @@ import type { UserUpdatePayload } from '@/requests';
import { CredentialsRepository } from '@db/repositories/credentials.repository'; import { CredentialsRepository } from '@db/repositories/credentials.repository';
import { ExecutionRepository } from '@db/repositories/execution.repository'; import { ExecutionRepository } from '@db/repositories/execution.repository';
import { WorkflowRepository } from '@db/repositories/workflow.repository'; import { WorkflowRepository } from '@db/repositories/workflow.repository';
import { BadRequestError } from './errors/response-errors/bad-request.error';
/** /**
* Returns the base URL n8n is reachable from * Returns the base URL n8n is reachable from
@ -109,7 +109,7 @@ export async function validateEntity(
.join(' | '); .join(' | ');
if (errorMessages) { if (errorMessages) {
throw new ResponseHelper.BadRequestError(errorMessages); throw new BadRequestError(errorMessages);
} }
} }

View file

@ -29,13 +29,14 @@ import {
isLdapCurrentAuthenticationMethod, isLdapCurrentAuthenticationMethod,
setCurrentAuthenticationMethod, setCurrentAuthenticationMethod,
} from '@/sso/ssoHelpers'; } from '@/sso/ssoHelpers';
import { BadRequestError, InternalServerError } from '../ResponseHelper';
import { RoleService } from '@/services/role.service'; import { RoleService } from '@/services/role.service';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { UserRepository } from '@db/repositories/user.repository'; import { UserRepository } from '@db/repositories/user.repository';
import { SettingsRepository } from '@db/repositories/settings.repository'; import { SettingsRepository } from '@db/repositories/settings.repository';
import { AuthProviderSyncHistoryRepository } from '@db/repositories/authProviderSyncHistory.repository'; import { AuthProviderSyncHistoryRepository } from '@db/repositories/authProviderSyncHistory.repository';
import { AuthIdentityRepository } from '@db/repositories/authIdentity.repository'; import { AuthIdentityRepository } from '@db/repositories/authIdentity.repository';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
/** /**
* Check whether the LDAP feature is disabled in the instance * Check whether the LDAP feature is disabled in the instance

View file

@ -24,14 +24,6 @@ type FeatureReturnType = Partial<
} & { [K in NumericLicenseFeature]: number } & { [K in BooleanLicenseFeature]: boolean } } & { [K in NumericLicenseFeature]: number } & { [K in BooleanLicenseFeature]: boolean }
>; >;
export class FeatureNotLicensedError extends Error {
constructor(feature: (typeof LICENSE_FEATURES)[keyof typeof LICENSE_FEATURES]) {
super(
`Your license does not allow for ${feature}. To enable ${feature}, please upgrade to a license that supports this feature.`,
);
}
}
@Service() @Service()
export class License { export class License {
private manager: LicenseManager | undefined; private manager: LicenseManager | undefined;

View file

@ -12,14 +12,7 @@ import { LoadNodesAndCredentials } from './LoadNodesAndCredentials';
import { join, dirname } from 'path'; import { join, dirname } from 'path';
import { readdir } from 'fs/promises'; import { readdir } from 'fs/promises';
import type { Dirent } from 'fs'; import type { Dirent } from 'fs';
import { UnrecognizedNodeTypeError } from './errors/unrecognized-node-type.error';
class UnrecognizedNodeError extends Error {
severity = 'warning';
constructor(nodeType: string) {
super(`Unrecognized node type: ${nodeType}".`);
}
}
@Service() @Service()
export class NodeTypes implements INodeTypes { export class NodeTypes implements INodeTypes {
@ -75,7 +68,7 @@ export class NodeTypes implements INodeTypes {
return loadedNodes[type]; return loadedNodes[type];
} }
throw new UnrecognizedNodeError(type); throw new UnrecognizedNodeTypeError(type);
} }
async getNodeTranslationPath({ async getNodeTranslationPath({

View file

@ -12,76 +12,7 @@ import type {
IWorkflowDb, IWorkflowDb,
} from '@/Interfaces'; } from '@/Interfaces';
import { inDevelopment } from '@/constants'; import { inDevelopment } from '@/constants';
import { ResponseError } from './errors/response-errors/abstract/response.error';
/**
* Special Error which allows to return also an error code and http status code
*/
abstract class ResponseError extends Error {
/**
* Creates an instance of ResponseError.
* Must be used inside a block with `ResponseHelper.send()`.
*/
constructor(
message: string,
// The HTTP status code of response
readonly httpStatusCode: number,
// The error code in the response
readonly errorCode: number = httpStatusCode,
// The error hint the response
readonly hint: string | undefined = undefined,
) {
super(message);
this.name = 'ResponseError';
}
}
export class BadRequestError extends ResponseError {
constructor(message: string, errorCode?: number) {
super(message, 400, errorCode);
}
}
export class AuthError extends ResponseError {
constructor(message: string, errorCode?: number) {
super(message, 401, errorCode);
}
}
export class UnauthorizedError extends ResponseError {
constructor(message: string, hint: string | undefined = undefined) {
super(message, 403, 403, hint);
}
}
export class NotFoundError extends ResponseError {
constructor(message: string, hint: string | undefined = undefined) {
super(message, 404, 404, hint);
}
}
export class ConflictError extends ResponseError {
constructor(message: string, hint: string | undefined = undefined) {
super(message, 409, 409, hint);
}
}
export class UnprocessableRequestError extends ResponseError {
constructor(message: string, hint: string | undefined = undefined) {
super(message, 422, 422, hint);
}
}
export class InternalServerError extends ResponseError {
constructor(message: string, errorCode = 500) {
super(message, 500, errorCode);
}
}
export class ServiceUnavailableError extends ResponseError {
constructor(message: string, errorCode = 503) {
super(message, 503, errorCode);
}
}
export function sendSuccessResponse( export function sendSuccessResponse(
res: Response, res: Response,

View file

@ -117,6 +117,8 @@ import { OrchestrationController } from './controllers/orchestration.controller'
import { WorkflowHistoryController } from './workflows/workflowHistory/workflowHistory.controller.ee'; import { WorkflowHistoryController } from './workflows/workflowHistory/workflowHistory.controller.ee';
import { InvitationController } from './controllers/invitation.controller'; import { InvitationController } from './controllers/invitation.controller';
import { CollaborationService } from './collaboration/collaboration.service'; import { CollaborationService } from './collaboration/collaboration.service';
import { BadRequestError } from './errors/response-errors/bad-request.error';
import { NotFoundError } from './errors/response-errors/not-found.error';
const exec = promisify(callbackExec); const exec = promisify(callbackExec);
@ -466,9 +468,7 @@ export class Server extends AbstractServer {
userId: req.user.id, userId: req.user.id,
}); });
throw new ResponseHelper.BadRequestError( throw new BadRequestError(`Workflow with ID "${workflowId}" could not be found.`);
`Workflow with ID "${workflowId}" could not be found.`,
);
} }
return this.activeWorkflowRunner.getActivationError(workflowId); return this.activeWorkflowRunner.getActivationError(workflowId);
@ -491,7 +491,7 @@ export class Server extends AbstractServer {
const parameters = toHttpNodeParameters(curlCommand); const parameters = toHttpNodeParameters(curlCommand);
return ResponseHelper.flattenObject(parameters, 'parameters'); return ResponseHelper.flattenObject(parameters, 'parameters');
} catch (e) { } catch (e) {
throw new ResponseHelper.BadRequestError('Invalid cURL command'); throw new BadRequestError('Invalid cURL command');
} }
}, },
), ),
@ -624,7 +624,7 @@ export class Server extends AbstractServer {
const sharedWorkflowIds = await getSharedWorkflowIds(req.user); const sharedWorkflowIds = await getSharedWorkflowIds(req.user);
if (!sharedWorkflowIds.length) { if (!sharedWorkflowIds.length) {
throw new ResponseHelper.NotFoundError('Execution not found'); throw new NotFoundError('Execution not found');
} }
const fullExecutionData = await Container.get(ExecutionRepository).findSingleExecution( const fullExecutionData = await Container.get(ExecutionRepository).findSingleExecution(
@ -637,7 +637,7 @@ export class Server extends AbstractServer {
); );
if (!fullExecutionData) { if (!fullExecutionData) {
throw new ResponseHelper.NotFoundError('Execution not found'); throw new NotFoundError('Execution not found');
} }
if (config.getEnv('executions.mode') === 'queue') { if (config.getEnv('executions.mode') === 'queue') {

View file

@ -20,9 +20,9 @@ import type {
} from '@/Interfaces'; } from '@/Interfaces';
import { Push } from '@/push'; import { Push } from '@/push';
import { NodeTypes } from '@/NodeTypes'; import { NodeTypes } from '@/NodeTypes';
import * as ResponseHelper from '@/ResponseHelper';
import * as WebhookHelpers from '@/WebhookHelpers'; import * as WebhookHelpers from '@/WebhookHelpers';
import { webhookNotFoundErrorMessage } from './utils'; import { webhookNotFoundErrorMessage } from './utils';
import { NotFoundError } from './errors/response-errors/not-found.error';
const WEBHOOK_TEST_UNREGISTERED_HINT = const WEBHOOK_TEST_UNREGISTERED_HINT =
"Click the 'Execute workflow' button on the canvas, then try again. (In test mode, the webhook only works for one call after you click this button)"; "Click the 'Execute workflow' button on the canvas, then try again. (In test mode, the webhook only works for one call after you click this button)";
@ -80,7 +80,7 @@ export class TestWebhooks implements IWebhookManager {
if (webhookData === undefined) { if (webhookData === undefined) {
// The requested webhook is not registered // The requested webhook is not registered
const methods = await this.getWebhookMethods(path); const methods = await this.getWebhookMethods(path);
throw new ResponseHelper.NotFoundError( throw new NotFoundError(
webhookNotFoundErrorMessage(path, httpMethod, methods), webhookNotFoundErrorMessage(path, httpMethod, methods),
WEBHOOK_TEST_UNREGISTERED_HINT, WEBHOOK_TEST_UNREGISTERED_HINT,
); );
@ -108,7 +108,7 @@ export class TestWebhooks implements IWebhookManager {
if (testWebhookData[webhookKey] === undefined) { if (testWebhookData[webhookKey] === undefined) {
// The requested webhook is not registered // The requested webhook is not registered
const methods = await this.getWebhookMethods(path); const methods = await this.getWebhookMethods(path);
throw new ResponseHelper.NotFoundError( throw new NotFoundError(
webhookNotFoundErrorMessage(path, httpMethod, methods), webhookNotFoundErrorMessage(path, httpMethod, methods),
WEBHOOK_TEST_UNREGISTERED_HINT, WEBHOOK_TEST_UNREGISTERED_HINT,
); );
@ -121,7 +121,7 @@ export class TestWebhooks implements IWebhookManager {
// get additional data // get additional data
const workflowStartNode = workflow.getNode(webhookData.node); const workflowStartNode = workflow.getNode(webhookData.node);
if (workflowStartNode === null) { if (workflowStartNode === null) {
throw new ResponseHelper.NotFoundError('Could not find node to process webhook.'); throw new NotFoundError('Could not find node to process webhook.');
} }
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
@ -168,10 +168,7 @@ export class TestWebhooks implements IWebhookManager {
const webhookMethods = this.activeWebhooks.getWebhookMethods(path); const webhookMethods = this.activeWebhooks.getWebhookMethods(path);
if (!webhookMethods.length) { if (!webhookMethods.length) {
// The requested webhook is not registered // The requested webhook is not registered
throw new ResponseHelper.NotFoundError( throw new NotFoundError(webhookNotFoundErrorMessage(path), WEBHOOK_TEST_UNREGISTERED_HINT);
webhookNotFoundErrorMessage(path),
WEBHOOK_TEST_UNREGISTERED_HINT,
);
} }
return webhookMethods; return webhookMethods;

View file

@ -2,7 +2,6 @@ import { In } from 'typeorm';
import { compare, genSaltSync, hash } from 'bcryptjs'; import { compare, genSaltSync, hash } from 'bcryptjs';
import { Container } from 'typedi'; import { Container } from 'typedi';
import * as ResponseHelper from '@/ResponseHelper';
import type { WhereClause } from '@/Interfaces'; import type { WhereClause } from '@/Interfaces';
import type { User } from '@db/entities/User'; import type { User } from '@db/entities/User';
import { MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH } from '@db/entities/User'; import { MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH } from '@db/entities/User';
@ -11,6 +10,7 @@ import { License } from '@/License';
import { getWebhookBaseUrl } from '@/WebhookHelpers'; import { getWebhookBaseUrl } from '@/WebhookHelpers';
import { RoleService } from '@/services/role.service'; import { RoleService } from '@/services/role.service';
import { UserRepository } from '@db/repositories/user.repository'; import { UserRepository } from '@db/repositories/user.repository';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
export function isSharingEnabled(): boolean { export function isSharingEnabled(): boolean {
return Container.get(License).isSharingEnabled(); return Container.get(License).isSharingEnabled();
@ -43,7 +43,7 @@ export function generateUserInviteUrl(inviterId: string, inviteeId: string): str
// TODO: Enforce at model level // TODO: Enforce at model level
export function validatePassword(password?: string): string { export function validatePassword(password?: string): string {
if (!password) { if (!password) {
throw new ResponseHelper.BadRequestError('Password is mandatory'); throw new BadRequestError('Password is mandatory');
} }
const hasInvalidLength = const hasInvalidLength =
@ -70,7 +70,7 @@ export function validatePassword(password?: string): string {
message.push('Password must contain at least 1 uppercase letter.'); message.push('Password must contain at least 1 uppercase letter.');
} }
throw new ResponseHelper.BadRequestError(message.join(' ')); throw new BadRequestError(message.join(' '));
} }
return password; return password;

View file

@ -2,7 +2,6 @@ import { NodeHelpers, Workflow } from 'n8n-workflow';
import { Service } from 'typedi'; import { Service } from 'typedi';
import type express from 'express'; import type express from 'express';
import * as ResponseHelper from '@/ResponseHelper';
import * as WebhookHelpers from '@/WebhookHelpers'; import * as WebhookHelpers from '@/WebhookHelpers';
import { NodeTypes } from '@/NodeTypes'; import { NodeTypes } from '@/NodeTypes';
import type { import type {
@ -15,6 +14,8 @@ import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData'
import { ExecutionRepository } from '@db/repositories/execution.repository'; import { ExecutionRepository } from '@db/repositories/execution.repository';
import { OwnershipService } from './services/ownership.service'; import { OwnershipService } from './services/ownership.service';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { ConflictError } from './errors/response-errors/conflict.error';
import { NotFoundError } from './errors/response-errors/not-found.error';
@Service() @Service()
export class WaitingWebhooks implements IWebhookManager { export class WaitingWebhooks implements IWebhookManager {
@ -43,11 +44,11 @@ export class WaitingWebhooks implements IWebhookManager {
}); });
if (!execution) { if (!execution) {
throw new ResponseHelper.NotFoundError(`The execution "${executionId} does not exist.`); throw new NotFoundError(`The execution "${executionId} does not exist.`);
} }
if (execution.finished || execution.data.resultData.error) { if (execution.finished || execution.data.resultData.error) {
throw new ResponseHelper.ConflictError(`The execution "${executionId} has finished already.`); throw new ConflictError(`The execution "${executionId} has finished already.`);
} }
const lastNodeExecuted = execution.data.resultData.lastNodeExecuted as string; const lastNodeExecuted = execution.data.resultData.lastNodeExecuted as string;
@ -79,12 +80,12 @@ export class WaitingWebhooks implements IWebhookManager {
try { try {
workflowOwner = await this.ownershipService.getWorkflowOwnerCached(workflowData.id!); workflowOwner = await this.ownershipService.getWorkflowOwnerCached(workflowData.id!);
} catch (error) { } catch (error) {
throw new ResponseHelper.NotFoundError('Could not find workflow'); throw new NotFoundError('Could not find workflow');
} }
const workflowStartNode = workflow.getNode(lastNodeExecuted); const workflowStartNode = workflow.getNode(lastNodeExecuted);
if (workflowStartNode === null) { if (workflowStartNode === null) {
throw new ResponseHelper.NotFoundError('Could not find node to process webhook.'); throw new NotFoundError('Could not find node to process webhook.');
} }
const additionalData = await WorkflowExecuteAdditionalData.getBase(workflowOwner.id); const additionalData = await WorkflowExecuteAdditionalData.getBase(workflowOwner.id);
@ -103,7 +104,7 @@ export class WaitingWebhooks implements IWebhookManager {
// If no data got found it means that the execution can not be started via a webhook. // If no data got found it means that the execution can not be started via a webhook.
// Return 404 because we do not want to give any data if the execution exists or not. // Return 404 because we do not want to give any data if the execution exists or not.
const errorMessage = `The workflow for execution "${executionId}" does not contain a waiting webhook with a matching path/method.`; const errorMessage = `The workflow for execution "${executionId}" does not contain a waiting webhook with a matching path/method.`;
throw new ResponseHelper.NotFoundError(errorMessage); throw new NotFoundError(errorMessage);
} }
const runExecutionData = execution.data; const runExecutionData = execution.data;

View file

@ -63,6 +63,9 @@ import { OwnershipService } from './services/ownership.service';
import { parseBody } from './middlewares'; import { parseBody } from './middlewares';
import { WorkflowsService } from './workflows/workflows.services'; import { WorkflowsService } from './workflows/workflows.services';
import { Logger } from './Logger'; import { Logger } from './Logger';
import { NotFoundError } from './errors/response-errors/not-found.error';
import { InternalServerError } from './errors/response-errors/internal-server.error';
import { UnprocessableRequestError } from './errors/response-errors/unprocessable.error';
const pipeline = promisify(stream.pipeline); const pipeline = promisify(stream.pipeline);
@ -237,7 +240,7 @@ export async function executeWebhook(
if (nodeType === undefined) { if (nodeType === undefined) {
const errorMessage = `The type of the webhook node "${workflowStartNode.name}" is not known`; const errorMessage = `The type of the webhook node "${workflowStartNode.name}" is not known`;
responseCallback(new Error(errorMessage), {}); responseCallback(new Error(errorMessage), {});
throw new ResponseHelper.InternalServerError(errorMessage); throw new InternalServerError(errorMessage);
} }
const additionalKeys: IWorkflowDataProxyAdditionalKeys = { const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
@ -254,7 +257,7 @@ export async function executeWebhook(
try { try {
user = await Container.get(OwnershipService).getWorkflowOwnerCached(workflowData.id); user = await Container.get(OwnershipService).getWorkflowOwnerCached(workflowData.id);
} catch (error) { } catch (error) {
throw new ResponseHelper.NotFoundError('Cannot find workflow'); throw new NotFoundError('Cannot find workflow');
} }
} }
@ -294,7 +297,7 @@ export async function executeWebhook(
// that something does not resolve properly. // that something does not resolve properly.
const errorMessage = `The response mode '${responseMode}' is not valid!`; const errorMessage = `The response mode '${responseMode}' is not valid!`;
responseCallback(new Error(errorMessage), {}); responseCallback(new Error(errorMessage), {});
throw new ResponseHelper.InternalServerError(errorMessage); throw new InternalServerError(errorMessage);
} }
// Add the Response and Request so that this data can be accessed in the node // Add the Response and Request so that this data can be accessed in the node
@ -781,13 +784,13 @@ export async function executeWebhook(
responseCallback(new Error('There was a problem executing the workflow'), {}); responseCallback(new Error('There was a problem executing the workflow'), {});
} }
throw new ResponseHelper.InternalServerError(e.message); throw new InternalServerError(e.message);
}); });
} }
return executionId; return executionId;
} catch (e) { } catch (e) {
const error = const error =
e instanceof ResponseHelper.UnprocessableRequestError e instanceof UnprocessableRequestError
? e ? e
: new Error('There was a problem executing the workflow', { cause: e }); : new Error('There was a problem executing the workflow', { cause: e });
if (didSendResponse) throw error; if (didSendResponse) throw error;

View file

@ -4,11 +4,12 @@ import { AUTH_COOKIE_NAME, RESPONSE_ERROR_MESSAGES } from '@/constants';
import type { JwtPayload, JwtToken } from '@/Interfaces'; import type { JwtPayload, JwtToken } from '@/Interfaces';
import type { User } from '@db/entities/User'; import type { User } from '@db/entities/User';
import config from '@/config'; import config from '@/config';
import * as ResponseHelper from '@/ResponseHelper';
import { License } from '@/License'; import { License } from '@/License';
import { Container } from 'typedi'; import { Container } from 'typedi';
import { UserRepository } from '@db/repositories/user.repository'; import { UserRepository } from '@db/repositories/user.repository';
import { JwtService } from '@/services/jwt.service'; import { JwtService } from '@/services/jwt.service';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
import { AuthError } from '@/errors/response-errors/auth.error';
export function issueJWT(user: User): JwtToken { export function issueJWT(user: User): JwtToken {
const { id, email, password } = user; const { id, email, password } = user;
@ -26,7 +27,7 @@ export function issueJWT(user: User): JwtToken {
!user.isOwner && !user.isOwner &&
!isWithinUsersLimit !isWithinUsersLimit
) { ) {
throw new ResponseHelper.UnauthorizedError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED); throw new UnauthorizedError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
} }
if (password) { if (password) {
payload.password = createHash('sha256') payload.password = createHash('sha256')
@ -63,7 +64,7 @@ export async function resolveJwtContent(jwtPayload: JwtPayload): Promise<User> {
// currently only LDAP users during synchronization // currently only LDAP users during synchronization
// can be set to disabled // can be set to disabled
if (user?.disabled) { if (user?.disabled) {
throw new ResponseHelper.AuthError('Unauthorized'); throw new AuthError('Unauthorized');
} }
if (!user || jwtPayload.password !== passwordHash || user.email !== jwtPayload.email) { if (!user || jwtPayload.password !== passwordHash || user.email !== jwtPayload.email) {

View file

@ -1,10 +1,10 @@
import type { User } from '@db/entities/User'; import type { User } from '@db/entities/User';
import { compareHash } from '@/UserManagement/UserManagementHelper'; import { compareHash } from '@/UserManagement/UserManagementHelper';
import * as ResponseHelper from '@/ResponseHelper';
import { Container } from 'typedi'; import { Container } from 'typedi';
import { InternalHooks } from '@/InternalHooks'; import { InternalHooks } from '@/InternalHooks';
import { isLdapLoginEnabled } from '@/Ldap/helpers'; import { isLdapLoginEnabled } from '@/Ldap/helpers';
import { UserRepository } from '@db/repositories/user.repository'; import { UserRepository } from '@db/repositories/user.repository';
import { AuthError } from '@/errors/response-errors/auth.error';
export const handleEmailLogin = async ( export const handleEmailLogin = async (
email: string, email: string,
@ -27,7 +27,7 @@ export const handleEmailLogin = async (
user_id: user.id, user_id: user.id,
}); });
throw new ResponseHelper.AuthError('Reset your password to gain access to the instance.'); throw new AuthError('Reset your password to gain access to the instance.');
} }
return undefined; return undefined;

View file

@ -23,7 +23,7 @@ import { EDITOR_UI_DIST_DIR, LICENSE_FEATURES } from '@/constants';
import { eventBus } from '@/eventbus'; import { eventBus } from '@/eventbus';
import { BaseCommand } from './BaseCommand'; import { BaseCommand } from './BaseCommand';
import { InternalHooks } from '@/InternalHooks'; import { InternalHooks } from '@/InternalHooks';
import { License, FeatureNotLicensedError } from '@/License'; import { License } from '@/License';
import type { IConfig } from '@oclif/config'; import type { IConfig } from '@oclif/config';
import { SingleMainSetup } from '@/services/orchestration/main/SingleMainSetup'; import { SingleMainSetup } from '@/services/orchestration/main/SingleMainSetup';
import { OrchestrationHandlerMainService } from '@/services/orchestration/main/orchestration.handler.main.service'; import { OrchestrationHandlerMainService } from '@/services/orchestration/main/orchestration.handler.main.service';
@ -31,6 +31,7 @@ import { PruningService } from '@/services/pruning.service';
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee'; import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
import { SettingsRepository } from '@db/repositories/settings.repository'; import { SettingsRepository } from '@db/repositories/settings.repository';
import { ExecutionRepository } from '@db/repositories/execution.repository'; import { ExecutionRepository } from '@db/repositories/execution.repository';
import { FeatureNotLicensedError } from '@/errors/feature-not-licensed.error';
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
const open = require('open'); const open = require('open');

View file

@ -40,6 +40,7 @@ import type { IConfig } from '@oclif/config';
import { OrchestrationHandlerWorkerService } from '@/services/orchestration/worker/orchestration.handler.worker.service'; import { OrchestrationHandlerWorkerService } from '@/services/orchestration/worker/orchestration.handler.worker.service';
import { OrchestrationWorkerService } from '@/services/orchestration/worker/orchestration.worker.service'; import { OrchestrationWorkerService } from '@/services/orchestration/worker/orchestration.worker.service';
import type { WorkerJobStatusSummary } from '../services/orchestration/worker/types'; import type { WorkerJobStatusSummary } from '../services/orchestration/worker/types';
import { ServiceUnavailableError } from '@/errors/response-errors/service-unavailable.error';
export class Worker extends BaseCommand { export class Worker extends BaseCommand {
static description = '\nStarts a n8n worker'; static description = '\nStarts a n8n worker';
@ -413,7 +414,7 @@ export class Worker extends BaseCommand {
await connection.query('SELECT 1'); await connection.query('SELECT 1');
} catch (e) { } catch (e) {
this.logger.error('No Database connection!', e as Error); this.logger.error('No Database connection!', e as Error);
const error = new ResponseHelper.ServiceUnavailableError('No Database connection!'); const error = new ServiceUnavailableError('No Database connection!');
return ResponseHelper.sendErrorResponse(res, error); return ResponseHelper.sendErrorResponse(res, error);
} }
@ -424,7 +425,7 @@ export class Worker extends BaseCommand {
await Worker.jobQueue.ping(); await Worker.jobQueue.ping();
} catch (e) { } catch (e) {
this.logger.error('No Redis connection!', e as Error); this.logger.error('No Redis connection!', e as Error);
const error = new ResponseHelper.ServiceUnavailableError('No Redis connection!'); const error = new ServiceUnavailableError('No Redis connection!');
return ResponseHelper.sendErrorResponse(res, error); return ResponseHelper.sendErrorResponse(res, error);
} }

View file

@ -1,11 +1,6 @@
import { NotStringArrayError } from '@/errors/not-string-array.error';
import type { SchemaObj } from 'convict'; import type { SchemaObj } from 'convict';
class NotStringArrayError extends Error {
constructor(env: string) {
super(`${env} is not a string array.`);
}
}
export const ensureStringArray = (values: string[], { env }: SchemaObj<string>) => { export const ensureStringArray = (values: string[], { env }: SchemaObj<string>) => {
if (!env) throw new Error(`Missing env: ${env}`); if (!env) throw new Error(`Missing env: ${env}`);

View file

@ -2,12 +2,6 @@ import validator from 'validator';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { Service } from 'typedi'; import { Service } from 'typedi';
import { Authorized, Get, Post, RestController } from '@/decorators'; import { Authorized, Get, Post, RestController } from '@/decorators';
import {
AuthError,
BadRequestError,
InternalServerError,
UnauthorizedError,
} from '@/ResponseHelper';
import { issueCookie, resolveJwt } from '@/auth/jwt'; import { issueCookie, resolveJwt } from '@/auth/jwt';
import { AUTH_COOKIE_NAME, RESPONSE_ERROR_MESSAGES } from '@/constants'; import { AUTH_COOKIE_NAME, RESPONSE_ERROR_MESSAGES } from '@/constants';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
@ -27,6 +21,10 @@ import { License } from '@/License';
import { UserService } from '@/services/user.service'; import { UserService } from '@/services/user.service';
import { MfaService } from '@/Mfa/mfa.service'; import { MfaService } from '@/Mfa/mfa.service';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { AuthError } from '@/errors/response-errors/auth.error';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
@Service() @Service()
@RestController() @RestController()

View file

@ -8,12 +8,13 @@ import {
} from '@/constants'; } from '@/constants';
import { Authorized, Delete, Get, Middleware, Patch, Post, RestController } from '@/decorators'; import { Authorized, Delete, Get, Middleware, Patch, Post, RestController } from '@/decorators';
import { NodeRequest } from '@/requests'; import { NodeRequest } from '@/requests';
import { BadRequestError, InternalServerError } from '@/ResponseHelper';
import type { InstalledPackages } from '@db/entities/InstalledPackages'; import type { InstalledPackages } from '@db/entities/InstalledPackages';
import type { CommunityPackages } from '@/Interfaces'; import type { CommunityPackages } from '@/Interfaces';
import { InternalHooks } from '@/InternalHooks'; import { InternalHooks } from '@/InternalHooks';
import { Push } from '@/push'; import { Push } from '@/push';
import { CommunityPackagesService } from '@/services/communityPackages.service'; import { CommunityPackagesService } from '@/services/communityPackages.service';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
const { const {
PACKAGE_NOT_INSTALLED, PACKAGE_NOT_INSTALLED,

View file

@ -12,7 +12,7 @@ import { Authorized, Get, Middleware, RestController } from '@/decorators';
import { getBase } from '@/WorkflowExecuteAdditionalData'; import { getBase } from '@/WorkflowExecuteAdditionalData';
import { DynamicNodeParametersService } from '@/services/dynamicNodeParameters.service'; import { DynamicNodeParametersService } from '@/services/dynamicNodeParameters.service';
import { DynamicNodeParametersRequest } from '@/requests'; import { DynamicNodeParametersRequest } from '@/requests';
import { BadRequestError } from '@/ResponseHelper'; import { BadRequestError } from '@/errors/response-errors/bad-request.error';
const assertMethodName: RequestHandler = (req, res, next) => { const assertMethodName: RequestHandler = (req, res, next) => {
const { methodName } = req.query as DynamicNodeParametersRequest.BaseRequest['query']; const { methodName } = req.query as DynamicNodeParametersRequest.BaseRequest['query'];

View file

@ -1,7 +1,6 @@
import { In } from 'typeorm'; import { In } from 'typeorm';
import Container, { Service } from 'typedi'; import Container, { Service } from 'typedi';
import { Authorized, NoAuthRequired, Post, RestController } from '@/decorators'; import { Authorized, NoAuthRequired, Post, RestController } from '@/decorators';
import { BadRequestError, UnauthorizedError } from '@/ResponseHelper';
import { issueCookie } from '@/auth/jwt'; import { issueCookie } from '@/auth/jwt';
import { RESPONSE_ERROR_MESSAGES } from '@/constants'; import { RESPONSE_ERROR_MESSAGES } from '@/constants';
import { Response } from 'express'; import { Response } from 'express';
@ -16,6 +15,8 @@ import { hashPassword, validatePassword } from '@/UserManagement/UserManagementH
import { PostHogClient } from '@/posthog'; import { PostHogClient } from '@/posthog';
import type { User } from '@/databases/entities/User'; import type { User } from '@/databases/entities/User';
import validator from 'validator'; import validator from 'validator';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
@Service() @Service()
@RestController('/invitations') @RestController('/invitations')

View file

@ -4,9 +4,9 @@ import { getLdapConfig, getLdapSynchronizations, updateLdapConfig } from '@/Ldap
import { LdapService } from '@/Ldap/LdapService.ee'; import { LdapService } from '@/Ldap/LdapService.ee';
import { LdapSync } from '@/Ldap/LdapSync.ee'; import { LdapSync } from '@/Ldap/LdapSync.ee';
import { LdapConfiguration } from '@/Ldap/types'; import { LdapConfiguration } from '@/Ldap/types';
import { BadRequestError } from '@/ResponseHelper';
import { NON_SENSIBLE_LDAP_CONFIG_PROPERTIES } from '@/Ldap/constants'; import { NON_SENSIBLE_LDAP_CONFIG_PROPERTIES } from '@/Ldap/constants';
import { InternalHooks } from '@/InternalHooks'; import { InternalHooks } from '@/InternalHooks';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
@Authorized(['global', 'owner']) @Authorized(['global', 'owner'])
@RestController('/ldap') @RestController('/ldap')

View file

@ -5,7 +5,6 @@ import { Service } from 'typedi';
import { randomBytes } from 'crypto'; import { randomBytes } from 'crypto';
import { Authorized, Delete, Get, Patch, Post, RestController } from '@/decorators'; import { Authorized, Delete, Get, Patch, Post, RestController } from '@/decorators';
import { compareHash, hashPassword, validatePassword } from '@/UserManagement/UserManagementHelper'; import { compareHash, hashPassword, validatePassword } from '@/UserManagement/UserManagementHelper';
import { BadRequestError } from '@/ResponseHelper';
import { validateEntity } from '@/GenericHelpers'; import { validateEntity } from '@/GenericHelpers';
import { issueCookie } from '@/auth/jwt'; import { issueCookie } from '@/auth/jwt';
import type { User } from '@db/entities/User'; import type { User } from '@db/entities/User';
@ -21,6 +20,7 @@ import { UserService } from '@/services/user.service';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { ExternalHooks } from '@/ExternalHooks'; import { ExternalHooks } from '@/ExternalHooks';
import { InternalHooks } from '@/InternalHooks'; import { InternalHooks } from '@/InternalHooks';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
@Service() @Service()
@Authorized() @Authorized()

View file

@ -1,8 +1,8 @@
import { Service } from 'typedi'; import { Service } from 'typedi';
import { Authorized, Delete, Get, Post, RestController } from '@/decorators'; import { Authorized, Delete, Get, Post, RestController } from '@/decorators';
import { AuthenticatedRequest, MFA } from '@/requests'; import { AuthenticatedRequest, MFA } from '@/requests';
import { BadRequestError } from '@/ResponseHelper';
import { MfaService } from '@/Mfa/mfa.service'; import { MfaService } from '@/Mfa/mfa.service';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
@Service() @Service()
@Authorized() @Authorized()

View file

@ -9,12 +9,13 @@ import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.
import type { ICredentialsDb } from '@/Interfaces'; import type { ICredentialsDb } from '@/Interfaces';
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper'; import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
import type { OAuthRequest } from '@/requests'; import type { OAuthRequest } from '@/requests';
import { BadRequestError, NotFoundError } from '@/ResponseHelper';
import { RESPONSE_ERROR_MESSAGES } from '@/constants'; import { RESPONSE_ERROR_MESSAGES } from '@/constants';
import { CredentialsHelper } from '@/CredentialsHelper'; import { CredentialsHelper } from '@/CredentialsHelper';
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData'; import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { ExternalHooks } from '@/ExternalHooks'; import { ExternalHooks } from '@/ExternalHooks';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
@Service() @Service()
export abstract class AbstractOAuthController { export abstract class AbstractOAuthController {

View file

@ -8,8 +8,10 @@ import { createHmac } from 'crypto';
import { RESPONSE_ERROR_MESSAGES } from '@/constants'; import { RESPONSE_ERROR_MESSAGES } from '@/constants';
import { Authorized, Get, RestController } from '@/decorators'; import { Authorized, Get, RestController } from '@/decorators';
import { OAuthRequest } from '@/requests'; import { OAuthRequest } from '@/requests';
import { NotFoundError, sendErrorResponse, ServiceUnavailableError } from '@/ResponseHelper'; import { sendErrorResponse } from '@/ResponseHelper';
import { AbstractOAuthController } from './abstractOAuth.controller'; import { AbstractOAuthController } from './abstractOAuth.controller';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { ServiceUnavailableError } from '@/errors/response-errors/service-unavailable.error';
interface OAuth1CredentialData { interface OAuth1CredentialData {
signatureMethod: 'HMAC-SHA256' | 'HMAC-SHA512' | 'HMAC-SHA1'; signatureMethod: 'HMAC-SHA256' | 'HMAC-SHA512' | 'HMAC-SHA1';

View file

@ -1,7 +1,6 @@
import validator from 'validator'; import validator from 'validator';
import { validateEntity } from '@/GenericHelpers'; import { validateEntity } from '@/GenericHelpers';
import { Authorized, Post, RestController } from '@/decorators'; import { Authorized, Post, RestController } from '@/decorators';
import { BadRequestError } from '@/ResponseHelper';
import { hashPassword, validatePassword } from '@/UserManagement/UserManagementHelper'; import { hashPassword, validatePassword } from '@/UserManagement/UserManagementHelper';
import { issueCookie } from '@/auth/jwt'; import { issueCookie } from '@/auth/jwt';
import { Response } from 'express'; import { Response } from 'express';
@ -12,6 +11,7 @@ import { SettingsRepository } from '@db/repositories/settings.repository';
import { PostHogClient } from '@/posthog'; import { PostHogClient } from '@/posthog';
import { UserService } from '@/services/user.service'; import { UserService } from '@/services/user.service';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
@Authorized(['global', 'owner']) @Authorized(['global', 'owner'])
@RestController('/owner') @RestController('/owner')

View file

@ -5,13 +5,6 @@ import { IsNull, Not } from 'typeorm';
import validator from 'validator'; import validator from 'validator';
import { Get, Post, RestController } from '@/decorators'; import { Get, Post, RestController } from '@/decorators';
import {
BadRequestError,
InternalServerError,
NotFoundError,
UnauthorizedError,
UnprocessableRequestError,
} from '@/ResponseHelper';
import { import {
getInstanceBaseUrl, getInstanceBaseUrl,
hashPassword, hashPassword,
@ -29,6 +22,11 @@ import { MfaService } from '@/Mfa/mfa.service';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { ExternalHooks } from '@/ExternalHooks'; import { ExternalHooks } from '@/ExternalHooks';
import { InternalHooks } from '@/InternalHooks'; import { InternalHooks } from '@/InternalHooks';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { UnprocessableRequestError } from '@/errors/response-errors/unprocessable.error';
const throttle = rateLimit({ const throttle = rateLimit({
windowMs: 5 * 60 * 1000, // 5 minutes windowMs: 5 * 60 * 1000, // 5 minutes

View file

@ -2,9 +2,9 @@ import { Request, Response, NextFunction } from 'express';
import config from '@/config'; import config from '@/config';
import { Authorized, Delete, Get, Middleware, Patch, Post, RestController } from '@/decorators'; import { Authorized, Delete, Get, Middleware, Patch, Post, RestController } from '@/decorators';
import { TagService } from '@/services/tag.service'; import { TagService } from '@/services/tag.service';
import { BadRequestError } from '@/ResponseHelper';
import { TagsRequest } from '@/requests'; import { TagsRequest } from '@/requests';
import { Service } from 'typedi'; import { Service } from 'typedi';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
@Authorized() @Authorized()
@RestController('/tags') @RestController('/tags')

View file

@ -3,9 +3,10 @@ import { ICredentialTypes } from 'n8n-workflow';
import { join } from 'path'; import { join } from 'path';
import { access } from 'fs/promises'; import { access } from 'fs/promises';
import { Authorized, Get, RestController } from '@/decorators'; import { Authorized, Get, RestController } from '@/decorators';
import { BadRequestError, InternalServerError } from '@/ResponseHelper';
import { Config } from '@/config'; import { Config } from '@/config';
import { NODES_BASE_DIR } from '@/constants'; import { NODES_BASE_DIR } from '@/constants';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
export const CREDENTIAL_TRANSLATIONS_DIR = 'n8n-nodes-base/dist/credentials/translations'; export const CREDENTIAL_TRANSLATIONS_DIR = 'n8n-nodes-base/dist/credentials/translations';
export const NODE_HEADERS_PATH = join(NODES_BASE_DIR, 'dist/nodes/headers'); export const NODE_HEADERS_PATH = join(NODES_BASE_DIR, 'dist/nodes/headers');

View file

@ -4,7 +4,6 @@ import { User } from '@db/entities/User';
import { SharedCredentials } from '@db/entities/SharedCredentials'; import { SharedCredentials } from '@db/entities/SharedCredentials';
import { SharedWorkflow } from '@db/entities/SharedWorkflow'; import { SharedWorkflow } from '@db/entities/SharedWorkflow';
import { Authorized, Delete, Get, RestController, Patch } from '@/decorators'; import { Authorized, Delete, Get, RestController, Patch } from '@/decorators';
import { BadRequestError, NotFoundError, UnauthorizedError } from '@/ResponseHelper';
import { ListQuery, UserRequest, UserSettingsUpdatePayload } from '@/requests'; import { ListQuery, UserRequest, UserSettingsUpdatePayload } from '@/requests';
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
import { IExternalHooksClass, IInternalHooksClass } from '@/Interfaces'; import { IExternalHooksClass, IInternalHooksClass } from '@/Interfaces';
@ -17,6 +16,9 @@ import { RoleService } from '@/services/role.service';
import { UserService } from '@/services/user.service'; import { UserService } from '@/services/user.service';
import { listQueryMiddleware } from '@/middlewares'; import { listQueryMiddleware } from '@/middlewares';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
@Authorized() @Authorized()
@RestController('/users') @RestController('/users')

View file

@ -7,9 +7,9 @@ import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.reposi
import { WorkflowStatisticsRepository } from '@db/repositories/workflowStatistics.repository'; import { WorkflowStatisticsRepository } from '@db/repositories/workflowStatistics.repository';
import { ExecutionRequest } from '@/requests'; import { ExecutionRequest } from '@/requests';
import { whereClause } from '@/UserManagement/UserManagementHelper'; import { whereClause } from '@/UserManagement/UserManagementHelper';
import { NotFoundError } from '@/ResponseHelper';
import type { IWorkflowStatisticsDataLoaded } from '@/Interfaces'; import type { IWorkflowStatisticsDataLoaded } from '@/Interfaces';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
interface WorkflowStatisticsData<T> { interface WorkflowStatisticsData<T> {
productionSuccess: T; productionSuccess: T;

View file

@ -11,6 +11,9 @@ import { OwnershipService } from '@/services/ownership.service';
import { Container } from 'typedi'; import { Container } from 'typedi';
import { InternalHooks } from '@/InternalHooks'; import { InternalHooks } from '@/InternalHooks';
import type { CredentialsEntity } from '@db/entities/CredentialsEntity'; import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
export const EECredentialsController = express.Router(); export const EECredentialsController = express.Router();
@ -40,7 +43,7 @@ EECredentialsController.get(
)) as CredentialsEntity; )) as CredentialsEntity;
if (!credential) { if (!credential) {
throw new ResponseHelper.NotFoundError( throw new NotFoundError(
'Could not load the credential. If you think this is an error, ask the owner to share it with you again', 'Could not load the credential. If you think this is an error, ask the owner to share it with you again',
); );
} }
@ -48,7 +51,7 @@ EECredentialsController.get(
const userSharing = credential.shared?.find((shared) => shared.user.id === req.user.id); const userSharing = credential.shared?.find((shared) => shared.user.id === req.user.id);
if (!userSharing && req.user.globalRole.name !== 'owner') { if (!userSharing && req.user.globalRole.name !== 'owner') {
throw new ResponseHelper.UnauthorizedError('Forbidden.'); throw new UnauthorizedError('Forbidden.');
} }
credential = Container.get(OwnershipService).addOwnedByAndSharedWith(credential); credential = Container.get(OwnershipService).addOwnedByAndSharedWith(credential);
@ -82,7 +85,7 @@ EECredentialsController.post(
const sharing = await EECredentials.getSharing(req.user, credentialId); const sharing = await EECredentials.getSharing(req.user, credentialId);
if (!ownsCredential) { if (!ownsCredential) {
if (!sharing) { if (!sharing) {
throw new ResponseHelper.UnauthorizedError('Forbidden'); throw new UnauthorizedError('Forbidden');
} }
const decryptedData = EECredentials.decrypt(sharing.credentials); const decryptedData = EECredentials.decrypt(sharing.credentials);
@ -115,12 +118,12 @@ EECredentialsController.put(
!Array.isArray(shareWithIds) || !Array.isArray(shareWithIds) ||
!shareWithIds.every((userId) => typeof userId === 'string') !shareWithIds.every((userId) => typeof userId === 'string')
) { ) {
throw new ResponseHelper.BadRequestError('Bad request'); throw new BadRequestError('Bad request');
} }
const { ownsCredential, credential } = await EECredentials.isOwned(req.user, credentialId); const { ownsCredential, credential } = await EECredentials.isOwned(req.user, credentialId);
if (!ownsCredential || !credential) { if (!ownsCredential || !credential) {
throw new ResponseHelper.UnauthorizedError('Forbidden'); throw new UnauthorizedError('Forbidden');
} }
let amountRemoved: number | null = null; let amountRemoved: number | null = null;

View file

@ -14,6 +14,7 @@ import { Container } from 'typedi';
import { InternalHooks } from '@/InternalHooks'; import { InternalHooks } from '@/InternalHooks';
import { listQueryMiddleware } from '@/middlewares'; import { listQueryMiddleware } from '@/middlewares';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
export const credentialsController = express.Router(); export const credentialsController = express.Router();
credentialsController.use('/', EECredentialsController); credentialsController.use('/', EECredentialsController);
@ -60,9 +61,7 @@ credentialsController.get(
const sharing = await CredentialsService.getSharing(req.user, credentialId, ['credentials']); const sharing = await CredentialsService.getSharing(req.user, credentialId, ['credentials']);
if (!sharing) { if (!sharing) {
throw new ResponseHelper.NotFoundError( throw new NotFoundError(`Credential with ID "${credentialId}" could not be found.`);
`Credential with ID "${credentialId}" could not be found.`,
);
} }
const { credentials: credential } = sharing; const { credentials: credential } = sharing;
@ -145,7 +144,7 @@ credentialsController.patch(
userId: req.user.id, userId: req.user.id,
}, },
); );
throw new ResponseHelper.NotFoundError( throw new NotFoundError(
'Credential to be updated not found. You can only update credentials owned by you', 'Credential to be updated not found. You can only update credentials owned by you',
); );
} }
@ -165,9 +164,7 @@ credentialsController.patch(
const responseData = await CredentialsService.update(credentialId, newCredentialData); const responseData = await CredentialsService.update(credentialId, newCredentialData);
if (responseData === null) { if (responseData === null) {
throw new ResponseHelper.NotFoundError( throw new NotFoundError(`Credential ID "${credentialId}" could not be found to be updated.`);
`Credential ID "${credentialId}" could not be found to be updated.`,
);
} }
// Remove the encrypted data as it is not needed in the frontend // Remove the encrypted data as it is not needed in the frontend
@ -197,7 +194,7 @@ credentialsController.delete(
userId: req.user.id, userId: req.user.id,
}, },
); );
throw new ResponseHelper.NotFoundError( throw new NotFoundError(
'Credential to be deleted not found. You can only removed credentials owned by you', 'Credential to be deleted not found. You can only removed credentials owned by you',
); );
} }

View file

@ -12,11 +12,11 @@ import { SourceControlPreferencesService } from './sourceControlPreferences.serv
import type { SourceControlPreferences } from './types/sourceControlPreferences'; import type { SourceControlPreferences } from './types/sourceControlPreferences';
import type { SourceControlledFile } from './types/sourceControlledFile'; import type { SourceControlledFile } from './types/sourceControlledFile';
import { SOURCE_CONTROL_API_ROOT, SOURCE_CONTROL_DEFAULT_BRANCH } from './constants'; import { SOURCE_CONTROL_API_ROOT, SOURCE_CONTROL_DEFAULT_BRANCH } from './constants';
import { BadRequestError } from '@/ResponseHelper';
import type { ImportResult } from './types/importResult'; import type { ImportResult } from './types/importResult';
import { InternalHooks } from '../../InternalHooks'; import { InternalHooks } from '../../InternalHooks';
import { getRepoType } from './sourceControlHelper.ee'; import { getRepoType } from './sourceControlHelper.ee';
import { SourceControlGetStatus } from './types/sourceControlGetStatus'; import { SourceControlGetStatus } from './types/sourceControlGetStatus';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
@Service() @Service()
@RestController(`/${SOURCE_CONTROL_API_ROOT}`) @RestController(`/${SOURCE_CONTROL_API_ROOT}`)

View file

@ -17,7 +17,6 @@ import {
import { SourceControlGitService } from './sourceControlGit.service.ee'; import { SourceControlGitService } from './sourceControlGit.service.ee';
import type { PushResult } from 'simple-git'; import type { PushResult } from 'simple-git';
import { SourceControlExportService } from './sourceControlExport.service.ee'; import { SourceControlExportService } from './sourceControlExport.service.ee';
import { BadRequestError } from '@/ResponseHelper';
import type { ImportResult } from './types/importResult'; import type { ImportResult } from './types/importResult';
import type { SourceControlPushWorkFolder } from './types/sourceControlPushWorkFolder'; import type { SourceControlPushWorkFolder } from './types/sourceControlPushWorkFolder';
import type { SourceControllPullOptions } from './types/sourceControlPullWorkFolder'; import type { SourceControllPullOptions } from './types/sourceControlPullWorkFolder';
@ -35,6 +34,7 @@ import type { ExportableCredential } from './types/exportableCredential';
import { InternalHooks } from '@/InternalHooks'; import { InternalHooks } from '@/InternalHooks';
import { TagRepository } from '@db/repositories/tag.repository'; import { TagRepository } from '@db/repositories/tag.repository';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
@Service() @Service()
export class SourceControlService { export class SourceControlService {

View file

@ -1,14 +1,14 @@
import { Container, Service } from 'typedi'; import { Container, Service } from 'typedi';
import * as ResponseHelper from '@/ResponseHelper';
import { VariablesRequest } from '@/requests'; import { VariablesRequest } from '@/requests';
import { Authorized, Delete, Get, Licensed, Patch, Post, RestController } from '@/decorators'; import { Authorized, Delete, Get, Licensed, Patch, Post, RestController } from '@/decorators';
import { import { VariablesService } from './variables.service.ee';
VariablesService,
VariablesLicenseError,
VariablesValidationError,
} from './variables.service.ee';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { VariableValidationError } from '@/errors/variable-validation.error';
import { VariableCountLimitReachedError } from '@/errors/variable-count-limit-reached.error';
@Service() @Service()
@Authorized() @Authorized()
@ -31,17 +31,17 @@ export class VariablesController {
this.logger.info('Attempt to update a variable blocked due to lack of permissions', { this.logger.info('Attempt to update a variable blocked due to lack of permissions', {
userId: req.user.id, userId: req.user.id,
}); });
throw new ResponseHelper.UnauthorizedError('Unauthorized'); throw new UnauthorizedError('Unauthorized');
} }
const variable = req.body; const variable = req.body;
delete variable.id; delete variable.id;
try { try {
return await Container.get(VariablesService).create(variable); return await Container.get(VariablesService).create(variable);
} catch (error) { } catch (error) {
if (error instanceof VariablesLicenseError) { if (error instanceof VariableCountLimitReachedError) {
throw new ResponseHelper.BadRequestError(error.message); throw new BadRequestError(error.message);
} else if (error instanceof VariablesValidationError) { } else if (error instanceof VariableValidationError) {
throw new ResponseHelper.BadRequestError(error.message); throw new BadRequestError(error.message);
} }
throw error; throw error;
} }
@ -52,7 +52,7 @@ export class VariablesController {
const id = req.params.id; const id = req.params.id;
const variable = await Container.get(VariablesService).getCached(id); const variable = await Container.get(VariablesService).getCached(id);
if (variable === null) { if (variable === null) {
throw new ResponseHelper.NotFoundError(`Variable with id ${req.params.id} not found`); throw new NotFoundError(`Variable with id ${req.params.id} not found`);
} }
return variable; return variable;
} }
@ -66,17 +66,17 @@ export class VariablesController {
id, id,
userId: req.user.id, userId: req.user.id,
}); });
throw new ResponseHelper.UnauthorizedError('Unauthorized'); throw new UnauthorizedError('Unauthorized');
} }
const variable = req.body; const variable = req.body;
delete variable.id; delete variable.id;
try { try {
return await Container.get(VariablesService).update(id, variable); return await Container.get(VariablesService).update(id, variable);
} catch (error) { } catch (error) {
if (error instanceof VariablesLicenseError) { if (error instanceof VariableCountLimitReachedError) {
throw new ResponseHelper.BadRequestError(error.message); throw new BadRequestError(error.message);
} else if (error instanceof VariablesValidationError) { } else if (error instanceof VariableValidationError) {
throw new ResponseHelper.BadRequestError(error.message); throw new BadRequestError(error.message);
} }
throw error; throw error;
} }
@ -90,7 +90,7 @@ export class VariablesController {
id, id,
userId: req.user.id, userId: req.user.id,
}); });
throw new ResponseHelper.UnauthorizedError('Unauthorized'); throw new UnauthorizedError('Unauthorized');
} }
await this.variablesService.delete(id); await this.variablesService.delete(id);

View file

@ -6,9 +6,8 @@ import { canCreateNewVariable } from './enviromentHelpers';
import { CacheService } from '@/services/cache.service'; import { CacheService } from '@/services/cache.service';
import { VariablesRepository } from '@db/repositories/variables.repository'; import { VariablesRepository } from '@db/repositories/variables.repository';
import type { DeepPartial } from 'typeorm'; import type { DeepPartial } from 'typeorm';
import { VariableCountLimitReachedError } from '@/errors/variable-count-limit-reached.error';
export class VariablesLicenseError extends Error {} import { VariableValidationError } from '@/errors/variable-validation.error';
export class VariablesValidationError extends Error {}
@Service() @Service()
export class VariablesService { export class VariablesService {
@ -59,19 +58,19 @@ export class VariablesService {
validateVariable(variable: Omit<Variables, 'id'>): void { validateVariable(variable: Omit<Variables, 'id'>): void {
if (variable.key.length > 50) { if (variable.key.length > 50) {
throw new VariablesValidationError('key cannot be longer than 50 characters'); throw new VariableValidationError('key cannot be longer than 50 characters');
} }
if (variable.key.replace(/[A-Za-z0-9_]/g, '').length !== 0) { if (variable.key.replace(/[A-Za-z0-9_]/g, '').length !== 0) {
throw new VariablesValidationError('key can only contain characters A-Za-z0-9_'); throw new VariableValidationError('key can only contain characters A-Za-z0-9_');
} }
if (variable.value?.length > 255) { if (variable.value?.length > 255) {
throw new VariablesValidationError('value cannot be longer than 255 characters'); throw new VariableValidationError('value cannot be longer than 255 characters');
} }
} }
async create(variable: Omit<Variables, 'id'>): Promise<Variables> { async create(variable: Omit<Variables, 'id'>): Promise<Variables> {
if (!canCreateNewVariable(await this.getCount())) { if (!canCreateNewVariable(await this.getCount())) {
throw new VariablesLicenseError('Variables limit reached'); throw new VariableCountLimitReachedError('Variables limit reached');
} }
this.validateVariable(variable); this.validateVariable(variable);

View file

@ -0,0 +1,10 @@
import { ApplicationError, type Severity } from 'n8n-workflow';
export class CredentialNotFoundError extends ApplicationError {
severity: Severity;
constructor(credentialId: string, credentialType: string) {
super(`Credential with ID "${credentialId}" does not exist for type "${credentialType}".`);
this.severity = 'warning';
}
}

View file

@ -0,0 +1,7 @@
import { ApplicationError } from 'n8n-workflow';
export class ExternalSecretsProviderNotFoundError extends ApplicationError {
constructor(public providerName: string) {
super(`External secrets provider not found: ${providerName}`);
}
}

View file

@ -0,0 +1,10 @@
import type { LICENSE_FEATURES } from '@/constants';
import { ApplicationError } from 'n8n-workflow';
export class FeatureNotLicensedError extends ApplicationError {
constructor(feature: (typeof LICENSE_FEATURES)[keyof typeof LICENSE_FEATURES]) {
super(
`Your license does not allow for ${feature}. To enable ${feature}, please upgrade to a license that supports this feature.`,
);
}
}

View file

@ -0,0 +1,3 @@
import { ApplicationError } from 'n8n-workflow';
export class InvalidRoleError extends ApplicationError {}

View file

@ -0,0 +1,7 @@
import { ApplicationError } from 'n8n-workflow';
export class NotStringArrayError extends ApplicationError {
constructor(env: string) {
super(`${env} is not a string array.`);
}
}

View file

@ -0,0 +1,23 @@
import { ApplicationError } from 'n8n-workflow';
/**
* Special Error which allows to return also an error code and http status code
*/
export abstract class ResponseError extends ApplicationError {
/**
* Creates an instance of ResponseError.
* Must be used inside a block with `ResponseHelper.send()`.
*/
constructor(
message: string,
// The HTTP status code of response
readonly httpStatusCode: number,
// The error code in the response
readonly errorCode: number = httpStatusCode,
// The error hint the response
readonly hint: string | undefined = undefined,
) {
super(message);
this.name = 'ResponseError';
}
}

View file

@ -0,0 +1,7 @@
import { ResponseError } from './abstract/response.error';
export class AuthError extends ResponseError {
constructor(message: string, errorCode?: number) {
super(message, 401, errorCode);
}
}

View file

@ -0,0 +1,7 @@
import { ResponseError } from './abstract/response.error';
export class BadRequestError extends ResponseError {
constructor(message: string, errorCode?: number) {
super(message, 400, errorCode);
}
}

View file

@ -0,0 +1,7 @@
import { ResponseError } from './abstract/response.error';
export class ConflictError extends ResponseError {
constructor(message: string, hint: string | undefined = undefined) {
super(message, 409, 409, hint);
}
}

View file

@ -0,0 +1,7 @@
import { ResponseError } from './abstract/response.error';
export class InternalServerError extends ResponseError {
constructor(message: string, errorCode = 500) {
super(message, 500, errorCode);
}
}

View file

@ -0,0 +1,7 @@
import { ResponseError } from './abstract/response.error';
export class NotFoundError extends ResponseError {
constructor(message: string, hint: string | undefined = undefined) {
super(message, 404, 404, hint);
}
}

View file

@ -0,0 +1,7 @@
import { ResponseError } from './abstract/response.error';
export class ServiceUnavailableError extends ResponseError {
constructor(message: string, errorCode = 503) {
super(message, 503, errorCode);
}
}

View file

@ -0,0 +1,7 @@
import { ResponseError } from './abstract/response.error';
export class UnauthorizedError extends ResponseError {
constructor(message: string, hint: string | undefined = undefined) {
super(message, 403, 403, hint);
}
}

View file

@ -0,0 +1,7 @@
import { ResponseError } from './abstract/response.error';
export class UnprocessableRequestError extends ResponseError {
constructor(message: string, hint: string | undefined = undefined) {
super(message, 422, 422, hint);
}
}

View file

@ -0,0 +1,3 @@
import { ApplicationError } from 'n8n-workflow';
export class SharedWorkflowNotFoundError extends ApplicationError {}

View file

@ -0,0 +1,9 @@
import { ApplicationError } from 'n8n-workflow';
export class UnrecognizedNodeTypeError extends ApplicationError {
severity = 'warning';
constructor(nodeType: string) {
super(`Unrecognized node type: ${nodeType}".`);
}
}

View file

@ -0,0 +1,3 @@
import { ApplicationError } from 'n8n-workflow';
export class VariableCountLimitReachedError extends ApplicationError {}

View file

@ -0,0 +1,3 @@
import { ApplicationError } from 'n8n-workflow';
export class VariableValidationError extends ApplicationError {}

View file

@ -0,0 +1,3 @@
import { ApplicationError } from 'n8n-workflow';
export class WorkflowHistoryVersionNotFoundError extends ApplicationError {}

View file

@ -9,7 +9,6 @@ import {
MessageEventBusDestinationSyslog, MessageEventBusDestinationSyslog,
} from './MessageEventBusDestination/MessageEventBusDestinationSyslog.ee'; } from './MessageEventBusDestination/MessageEventBusDestinationSyslog.ee';
import { MessageEventBusDestinationWebhook } from './MessageEventBusDestination/MessageEventBusDestinationWebhook.ee'; import { MessageEventBusDestinationWebhook } from './MessageEventBusDestination/MessageEventBusDestinationWebhook.ee';
import { BadRequestError } from '@/ResponseHelper';
import type { import type {
MessageEventBusDestinationWebhookOptions, MessageEventBusDestinationWebhookOptions,
MessageEventBusDestinationOptions, MessageEventBusDestinationOptions,
@ -20,6 +19,7 @@ import type { MessageEventBusDestination } from './MessageEventBusDestination/Me
import type { DeleteResult } from 'typeorm'; import type { DeleteResult } from 'typeorm';
import { AuthenticatedRequest } from '@/requests'; import { AuthenticatedRequest } from '@/requests';
import { logStreamingLicensedMiddleware } from './middleware/logStreamingEnabled.middleware.ee'; import { logStreamingLicensedMiddleware } from './middleware/logStreamingEnabled.middleware.ee';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
// ---------------------------------------- // ----------------------------------------
// TypeGuards // TypeGuards

View file

@ -9,13 +9,13 @@ import type { EventMessageTypes, FailedEventSummary } from './EventMessageClasse
import { eventNamesAll } from './EventMessageClasses'; import { eventNamesAll } from './EventMessageClasses';
import type { EventMessageAuditOptions } from './EventMessageClasses/EventMessageAudit'; import type { EventMessageAuditOptions } from './EventMessageClasses/EventMessageAudit';
import { EventMessageAudit } from './EventMessageClasses/EventMessageAudit'; import { EventMessageAudit } from './EventMessageClasses/EventMessageAudit';
import { BadRequestError } from '@/ResponseHelper';
import type { IRunExecutionData } from 'n8n-workflow'; import type { IRunExecutionData } from 'n8n-workflow';
import { EventMessageTypeNames } from 'n8n-workflow'; import { EventMessageTypeNames } from 'n8n-workflow';
import type { EventMessageNodeOptions } from './EventMessageClasses/EventMessageNode'; import type { EventMessageNodeOptions } from './EventMessageClasses/EventMessageNode';
import { EventMessageNode } from './EventMessageClasses/EventMessageNode'; import { EventMessageNode } from './EventMessageClasses/EventMessageNode';
import { recoverExecutionDataFromEventLogMessages } from './MessageEventBus/recoverEvents'; import { recoverExecutionDataFromEventLogMessages } from './MessageEventBus/recoverEvents';
import { RestController, Get, Post, Authorized } from '@/decorators'; import { RestController, Get, Post, Authorized } from '@/decorators';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
// ---------------------------------------- // ----------------------------------------
// TypeGuards // TypeGuards

View file

@ -15,7 +15,6 @@ import type {
import { NodeTypes } from '@/NodeTypes'; import { NodeTypes } from '@/NodeTypes';
import { Queue } from '@/Queue'; import { Queue } from '@/Queue';
import type { ExecutionRequest } from '@/requests'; import type { ExecutionRequest } from '@/requests';
import * as ResponseHelper from '@/ResponseHelper';
import { getSharedWorkflowIds } from '@/WorkflowHelpers'; import { getSharedWorkflowIds } from '@/WorkflowHelpers';
import { WorkflowRunner } from '@/WorkflowRunner'; import { WorkflowRunner } from '@/WorkflowRunner';
import * as GenericHelpers from '@/GenericHelpers'; import * as GenericHelpers from '@/GenericHelpers';
@ -24,6 +23,8 @@ import { getStatusUsingPreviousExecutionStatusMethod } from './executionHelpers'
import { ExecutionRepository } from '@db/repositories/execution.repository'; import { ExecutionRepository } from '@db/repositories/execution.repository';
import { WorkflowRepository } from '@db/repositories/workflow.repository'; import { WorkflowRepository } from '@db/repositories/workflow.repository';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
export interface IGetExecutionsQueryFilter { export interface IGetExecutionsQueryFilter {
id?: FindOperator<string> | string; id?: FindOperator<string> | string;
@ -114,9 +115,7 @@ export class ExecutionsService {
userId: req.user.id, userId: req.user.id,
filter: req.query.filter, filter: req.query.filter,
}); });
throw new ResponseHelper.InternalServerError( throw new InternalServerError('Parameter "filter" contained invalid JSON string.');
'Parameter "filter" contained invalid JSON string.',
);
} }
} }
@ -231,9 +230,7 @@ export class ExecutionsService {
executionId, executionId,
}, },
); );
throw new ResponseHelper.NotFoundError( throw new NotFoundError(`The execution with the ID "${executionId}" does not exist.`);
`The execution with the ID "${executionId}" does not exist.`,
);
} }
if (execution.finished) { if (execution.finished) {
@ -351,9 +348,7 @@ export class ExecutionsService {
requestFilters = requestFiltersRaw as IGetExecutionsQueryFilter; requestFilters = requestFiltersRaw as IGetExecutionsQueryFilter;
} }
} catch (error) { } catch (error) {
throw new ResponseHelper.InternalServerError( throw new InternalServerError('Parameter "filter" contained invalid JSON string.');
'Parameter "filter" contained invalid JSON string.',
);
} }
} }

View file

@ -8,6 +8,8 @@ import { LicenseService } from './License.service';
import { License } from '@/License'; import { License } from '@/License';
import type { AuthenticatedRequest, LicenseRequest } from '@/requests'; import type { AuthenticatedRequest, LicenseRequest } from '@/requests';
import { InternalHooks } from '@/InternalHooks'; import { InternalHooks } from '@/InternalHooks';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
export const licenseController = express.Router(); export const licenseController = express.Router();
@ -24,9 +26,7 @@ licenseController.use((req: AuthenticatedRequest, res, next) => {
}); });
ResponseHelper.sendErrorResponse( ResponseHelper.sendErrorResponse(
res, res,
new ResponseHelper.UnauthorizedError( new UnauthorizedError('Only an instance owner may activate or renew a license'),
'Only an instance owner may activate or renew a license',
),
); );
return; return;
} }
@ -85,7 +85,7 @@ licenseController.post(
Container.get(Logger).error(message, { stack: error.stack ?? 'n/a' }); Container.get(Logger).error(message, { stack: error.stack ?? 'n/a' });
} }
throw new ResponseHelper.BadRequestError(message); throw new BadRequestError(message);
} }
// Return the read data, plus the management JWT // Return the read data, plus the management JWT
@ -113,7 +113,7 @@ licenseController.post(
// not awaiting so as not to make the endpoint hang // not awaiting so as not to make the endpoint hang
void Container.get(InternalHooks).onLicenseRenewAttempt({ success: false }); void Container.get(InternalHooks).onLicenseRenewAttempt({ success: false });
if (error instanceof Error) { if (error instanceof Error) {
throw new ResponseHelper.BadRequestError(error.message); throw new BadRequestError(error.message);
} }
} }

View file

@ -7,7 +7,7 @@ import { Parser as XmlParser } from 'xml2js';
import { parseIncomingMessage } from 'n8n-core'; import { parseIncomingMessage } from 'n8n-core';
import { jsonParse } from 'n8n-workflow'; import { jsonParse } from 'n8n-workflow';
import config from '@/config'; import config from '@/config';
import { UnprocessableRequestError } from '@/ResponseHelper'; import { UnprocessableRequestError } from '@/errors/response-errors/unprocessable.error';
const xmlParser = new XmlParser({ const xmlParser = new XmlParser({
async: true, async: true,

View file

@ -3,8 +3,7 @@ import { RoleRepository } from '@db/repositories/role.repository';
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
import { CacheService } from './cache.service'; import { CacheService } from './cache.service';
import type { RoleNames, RoleScopes } from '@db/entities/Role'; import type { RoleNames, RoleScopes } from '@db/entities/Role';
import { InvalidRoleError } from '@/errors/invalid-role.error';
class InvalidRoleError extends Error {}
@Service() @Service()
export class RoleService { export class RoleService {

View file

@ -15,8 +15,8 @@ import { UserManagementMailer } from '@/UserManagement/email';
import { InternalHooks } from '@/InternalHooks'; import { InternalHooks } from '@/InternalHooks';
import { RoleService } from '@/services/role.service'; import { RoleService } from '@/services/role.service';
import { ErrorReporterProxy as ErrorReporter } from 'n8n-workflow'; import { ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
import { InternalServerError } from '@/ResponseHelper';
import type { UserRequest } from '@/requests'; import type { UserRequest } from '@/requests';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
@Service() @Service()
export class UserService { export class UserService {

View file

@ -9,7 +9,6 @@ import {
} from '../middleware/samlEnabledMiddleware'; } from '../middleware/samlEnabledMiddleware';
import { SamlService } from '../saml.service.ee'; import { SamlService } from '../saml.service.ee';
import { SamlConfiguration } from '../types/requests'; import { SamlConfiguration } from '../types/requests';
import { AuthError, BadRequestError } from '@/ResponseHelper';
import { getInitSSOFormView } from '../views/initSsoPost'; import { getInitSSOFormView } from '../views/initSsoPost';
import { issueCookie } from '@/auth/jwt'; import { issueCookie } from '@/auth/jwt';
import { validate } from 'class-validator'; import { validate } from 'class-validator';
@ -27,6 +26,8 @@ import { getSamlConnectionTestFailedView } from '../views/samlConnectionTestFail
import { InternalHooks } from '@/InternalHooks'; import { InternalHooks } from '@/InternalHooks';
import url from 'url'; import url from 'url';
import querystring from 'querystring'; import querystring from 'querystring';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { AuthError } from '@/errors/response-errors/auth.error';
@Service() @Service()
@RestController('/sso/saml') @RestController('/sso/saml')

View file

@ -2,7 +2,6 @@ import type express from 'express';
import Container, { Service } from 'typedi'; import Container, { Service } from 'typedi';
import type { User } from '@db/entities/User'; import type { User } from '@db/entities/User';
import { jsonParse } from 'n8n-workflow'; import { jsonParse } from 'n8n-workflow';
import { AuthError, BadRequestError } from '@/ResponseHelper';
import { getServiceProviderInstance } from './serviceProvider.ee'; import { getServiceProviderInstance } from './serviceProvider.ee';
import type { SamlUserAttributes } from './types/samlUserAttributes'; import type { SamlUserAttributes } from './types/samlUserAttributes';
import { isSsoJustInTimeProvisioningEnabled } from '../ssoHelpers'; import { isSsoJustInTimeProvisioningEnabled } from '../ssoHelpers';
@ -29,6 +28,8 @@ import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { UserRepository } from '@db/repositories/user.repository'; import { UserRepository } from '@db/repositories/user.repository';
import { SettingsRepository } from '@db/repositories/settings.repository'; import { SettingsRepository } from '@db/repositories/settings.repository';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { AuthError } from '@/errors/response-errors/auth.error';
@Service() @Service()
export class SamlService { export class SamlService {

View file

@ -3,7 +3,6 @@ import config from '@/config';
import { AuthIdentity } from '@db/entities/AuthIdentity'; import { AuthIdentity } from '@db/entities/AuthIdentity';
import { User } from '@db/entities/User'; import { User } from '@db/entities/User';
import { License } from '@/License'; import { License } from '@/License';
import { AuthError, InternalServerError } from '@/ResponseHelper';
import { hashPassword } from '@/UserManagement/UserManagementHelper'; import { hashPassword } from '@/UserManagement/UserManagementHelper';
import type { SamlPreferences } from './types/samlPreferences'; import type { SamlPreferences } from './types/samlPreferences';
import type { SamlUserAttributes } from './types/samlUserAttributes'; import type { SamlUserAttributes } from './types/samlUserAttributes';
@ -21,6 +20,9 @@ import type { SamlConfiguration } from './types/requests';
import { RoleService } from '@/services/role.service'; import { RoleService } from '@/services/role.service';
import { UserRepository } from '@db/repositories/user.repository'; import { UserRepository } from '@db/repositories/user.repository';
import { AuthIdentityRepository } from '@db/repositories/authIdentity.repository'; import { AuthIdentityRepository } from '@db/repositories/authIdentity.repository';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { AuthError } from '@/errors/response-errors/auth.error';
/** /**
* Check whether the SAML feature is licensed and enabled in the instance * Check whether the SAML feature is licensed and enabled in the instance
*/ */

View file

@ -1,15 +1,14 @@
import { Authorized, RestController, Get, Middleware } from '@/decorators'; import { Authorized, RestController, Get, Middleware } from '@/decorators';
import { WorkflowHistoryRequest } from '@/requests'; import { WorkflowHistoryRequest } from '@/requests';
import { Service } from 'typedi'; import { Service } from 'typedi';
import { import { WorkflowHistoryService } from './workflowHistory.service.ee';
HistoryVersionNotFoundError,
SharedWorkflowNotFoundError,
WorkflowHistoryService,
} from './workflowHistory.service.ee';
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { isWorkflowHistoryEnabled, isWorkflowHistoryLicensed } from './workflowHistoryHelper.ee'; import { isWorkflowHistoryEnabled, isWorkflowHistoryLicensed } from './workflowHistoryHelper.ee';
import { NotFoundError } from '@/ResponseHelper';
import { paginationListQueryMiddleware } from '@/middlewares/listQuery/pagination'; import { paginationListQueryMiddleware } from '@/middlewares/listQuery/pagination';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { SharedWorkflowNotFoundError } from '@/errors/shared-workflow-not-found.error';
import { WorkflowHistoryVersionNotFoundError } from '@/errors/workflow-history-version-not-found.error';
const DEFAULT_TAKE = 20; const DEFAULT_TAKE = 20;
@ -67,7 +66,7 @@ export class WorkflowHistoryController {
} catch (e) { } catch (e) {
if (e instanceof SharedWorkflowNotFoundError) { if (e instanceof SharedWorkflowNotFoundError) {
throw new NotFoundError('Could not find workflow'); throw new NotFoundError('Could not find workflow');
} else if (e instanceof HistoryVersionNotFoundError) { } else if (e instanceof WorkflowHistoryVersionNotFoundError) {
throw new NotFoundError('Could not find version'); throw new NotFoundError('Could not find version');
} }
throw e; throw e;

View file

@ -7,9 +7,8 @@ import { WorkflowHistoryRepository } from '@db/repositories/workflowHistory.repo
import { Service } from 'typedi'; import { Service } from 'typedi';
import { isWorkflowHistoryEnabled } from './workflowHistoryHelper.ee'; import { isWorkflowHistoryEnabled } from './workflowHistoryHelper.ee';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { SharedWorkflowNotFoundError } from '@/errors/shared-workflow-not-found.error';
export class SharedWorkflowNotFoundError extends Error {} import { WorkflowHistoryVersionNotFoundError } from '@/errors/workflow-history-version-not-found.error';
export class HistoryVersionNotFoundError extends Error {}
@Service() @Service()
export class WorkflowHistoryService { export class WorkflowHistoryService {
@ -36,7 +35,7 @@ export class WorkflowHistoryService {
): Promise<Array<Omit<WorkflowHistory, 'nodes' | 'connections'>>> { ): Promise<Array<Omit<WorkflowHistory, 'nodes' | 'connections'>>> {
const sharedWorkflow = await this.getSharedWorkflow(user, workflowId); const sharedWorkflow = await this.getSharedWorkflow(user, workflowId);
if (!sharedWorkflow) { if (!sharedWorkflow) {
throw new SharedWorkflowNotFoundError(); throw new SharedWorkflowNotFoundError('');
} }
return this.workflowHistoryRepository.find({ return this.workflowHistoryRepository.find({
where: { where: {
@ -52,7 +51,7 @@ export class WorkflowHistoryService {
async getVersion(user: User, workflowId: string, versionId: string): Promise<WorkflowHistory> { async getVersion(user: User, workflowId: string, versionId: string): Promise<WorkflowHistory> {
const sharedWorkflow = await this.getSharedWorkflow(user, workflowId); const sharedWorkflow = await this.getSharedWorkflow(user, workflowId);
if (!sharedWorkflow) { if (!sharedWorkflow) {
throw new SharedWorkflowNotFoundError(); throw new SharedWorkflowNotFoundError('');
} }
const hist = await this.workflowHistoryRepository.findOne({ const hist = await this.workflowHistoryRepository.findOne({
where: { where: {
@ -61,7 +60,7 @@ export class WorkflowHistoryService {
}, },
}); });
if (!hist) { if (!hist) {
throw new HistoryVersionNotFoundError(); throw new WorkflowHistoryVersionNotFoundError('');
} }
return hist; return hist;
} }

View file

@ -23,6 +23,10 @@ import { listQueryMiddleware } from '@/middlewares';
import { TagService } from '@/services/tag.service'; import { TagService } from '@/services/tag.service';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { WorkflowHistoryService } from './workflowHistory/workflowHistory.service.ee'; import { WorkflowHistoryService } from './workflowHistory/workflowHistory.service.ee';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
export const EEWorkflowController = express.Router(); export const EEWorkflowController = express.Router();
@ -52,13 +56,13 @@ EEWorkflowController.put(
!Array.isArray(shareWithIds) || !Array.isArray(shareWithIds) ||
!shareWithIds.every((userId) => typeof userId === 'string') !shareWithIds.every((userId) => typeof userId === 'string')
) { ) {
throw new ResponseHelper.BadRequestError('Bad request'); throw new BadRequestError('Bad request');
} }
const { ownsWorkflow, workflow } = await EEWorkflows.isOwned(req.user, workflowId); const { ownsWorkflow, workflow } = await EEWorkflows.isOwned(req.user, workflowId);
if (!ownsWorkflow || !workflow) { if (!ownsWorkflow || !workflow) {
throw new ResponseHelper.UnauthorizedError('Forbidden'); throw new UnauthorizedError('Forbidden');
} }
let newShareeIds: string[] = []; let newShareeIds: string[] = [];
@ -101,13 +105,13 @@ EEWorkflowController.get(
const workflow = await EEWorkflows.get({ id: workflowId }, { relations }); const workflow = await EEWorkflows.get({ id: workflowId }, { relations });
if (!workflow) { if (!workflow) {
throw new ResponseHelper.NotFoundError(`Workflow with ID "${workflowId}" does not exist`); throw new NotFoundError(`Workflow with ID "${workflowId}" does not exist`);
} }
const userSharing = workflow.shared?.find((shared) => shared.user.id === req.user.id); const userSharing = workflow.shared?.find((shared) => shared.user.id === req.user.id);
if (!userSharing && req.user.globalRole.name !== 'owner') { if (!userSharing && req.user.globalRole.name !== 'owner') {
throw new ResponseHelper.UnauthorizedError( throw new UnauthorizedError(
'You do not have permission to access this workflow. Ask the owner to share it with you', 'You do not have permission to access this workflow. Ask the owner to share it with you',
); );
} }
@ -156,7 +160,7 @@ EEWorkflowController.post(
try { try {
EEWorkflows.validateCredentialPermissionsToUser(newWorkflow, allCredentials); EEWorkflows.validateCredentialPermissionsToUser(newWorkflow, allCredentials);
} catch (error) { } catch (error) {
throw new ResponseHelper.BadRequestError( throw new BadRequestError(
'The workflow you are trying to save contains credentials that are not shared with you', 'The workflow you are trying to save contains credentials that are not shared with you',
); );
} }
@ -181,7 +185,7 @@ EEWorkflowController.post(
if (!savedWorkflow) { if (!savedWorkflow) {
Container.get(Logger).error('Failed to create workflow', { userId: req.user.id }); Container.get(Logger).error('Failed to create workflow', { userId: req.user.id });
throw new ResponseHelper.InternalServerError( throw new InternalServerError(
'An error occurred while saving your workflow. Please try again.', 'An error occurred while saving your workflow. Please try again.',
); );
} }

View file

@ -27,6 +27,9 @@ import { TagService } from '@/services/tag.service';
import { WorkflowHistoryService } from './workflowHistory/workflowHistory.service.ee'; import { WorkflowHistoryService } from './workflowHistory/workflowHistory.service.ee';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
export const workflowsController = express.Router(); export const workflowsController = express.Router();
workflowsController.use('/', EEWorkflowController); workflowsController.use('/', EEWorkflowController);
@ -84,7 +87,7 @@ workflowsController.post(
if (!savedWorkflow) { if (!savedWorkflow) {
Container.get(Logger).error('Failed to create workflow', { userId: req.user.id }); Container.get(Logger).error('Failed to create workflow', { userId: req.user.id });
throw new ResponseHelper.InternalServerError('Failed to save workflow'); throw new InternalServerError('Failed to save workflow');
} }
await Container.get(WorkflowHistoryService).saveVersion( await Container.get(WorkflowHistoryService).saveVersion(
@ -160,10 +163,10 @@ workflowsController.get(
'/from-url', '/from-url',
ResponseHelper.send(async (req: express.Request): Promise<IWorkflowResponse> => { ResponseHelper.send(async (req: express.Request): Promise<IWorkflowResponse> => {
if (req.query.url === undefined) { if (req.query.url === undefined) {
throw new ResponseHelper.BadRequestError('The parameter "url" is missing!'); throw new BadRequestError('The parameter "url" is missing!');
} }
if (!/^http[s]?:\/\/.*\.json$/i.exec(req.query.url as string)) { if (!/^http[s]?:\/\/.*\.json$/i.exec(req.query.url as string)) {
throw new ResponseHelper.BadRequestError( throw new BadRequestError(
'The parameter "url" is not valid! It does not seem to be a URL pointing to a n8n workflow JSON file.', 'The parameter "url" is not valid! It does not seem to be a URL pointing to a n8n workflow JSON file.',
); );
} }
@ -172,7 +175,7 @@ workflowsController.get(
const { data } = await axios.get<IWorkflowResponse>(req.query.url as string); const { data } = await axios.get<IWorkflowResponse>(req.query.url as string);
workflowData = data; workflowData = data;
} catch (error) { } catch (error) {
throw new ResponseHelper.BadRequestError('The URL does not point to valid JSON file!'); throw new BadRequestError('The URL does not point to valid JSON file!');
} }
// Do a very basic check if it is really a n8n-workflow-json // Do a very basic check if it is really a n8n-workflow-json
@ -183,7 +186,7 @@ workflowsController.get(
typeof workflowData.connections !== 'object' || typeof workflowData.connections !== 'object' ||
Array.isArray(workflowData.connections) Array.isArray(workflowData.connections)
) { ) {
throw new ResponseHelper.BadRequestError( throw new BadRequestError(
'The data in the file does not seem to be a n8n workflow JSON file!', 'The data in the file does not seem to be a n8n workflow JSON file!',
); );
} }
@ -221,7 +224,7 @@ workflowsController.get(
workflowId, workflowId,
userId: req.user.id, userId: req.user.id,
}); });
throw new ResponseHelper.NotFoundError( throw new NotFoundError(
'Could not load the workflow - you can only access workflows owned by you', 'Could not load the workflow - you can only access workflows owned by you',
); );
} }
@ -271,7 +274,7 @@ workflowsController.delete(
workflowId, workflowId,
userId: req.user.id, userId: req.user.id,
}); });
throw new ResponseHelper.BadRequestError( throw new BadRequestError(
'Could not delete the workflow - you can only remove workflows owned by you', 'Could not delete the workflow - you can only remove workflows owned by you',
); );
} }

View file

@ -1,6 +1,5 @@
import type { DeleteResult, EntityManager } from 'typeorm'; import type { DeleteResult, EntityManager } from 'typeorm';
import { In, Not } from 'typeorm'; import { In, Not } from 'typeorm';
import * as ResponseHelper from '@/ResponseHelper';
import * as WorkflowHelpers from '@/WorkflowHelpers'; import * as WorkflowHelpers from '@/WorkflowHelpers';
import { SharedWorkflow } from '@db/entities/SharedWorkflow'; import { SharedWorkflow } from '@db/entities/SharedWorkflow';
import type { User } from '@db/entities/User'; import type { User } from '@db/entities/User';
@ -17,6 +16,8 @@ import { RoleService } from '@/services/role.service';
import Container from 'typedi'; import Container from 'typedi';
import type { CredentialsEntity } from '@db/entities/CredentialsEntity'; import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
export class EEWorkflowsService extends WorkflowsService { export class EEWorkflowsService extends WorkflowsService {
static async isOwned( static async isOwned(
@ -170,7 +171,7 @@ export class EEWorkflowsService extends WorkflowsService {
const previousVersion = await EEWorkflowsService.get({ id: workflowId }); const previousVersion = await EEWorkflowsService.get({ id: workflowId });
if (!previousVersion) { if (!previousVersion) {
throw new ResponseHelper.NotFoundError('Workflow not found'); throw new NotFoundError('Workflow not found');
} }
const allCredentials = await CredentialsService.getMany(user); const allCredentials = await CredentialsService.getMany(user);
@ -183,9 +184,9 @@ export class EEWorkflowsService extends WorkflowsService {
); );
} catch (error) { } catch (error) {
if (error instanceof NodeOperationError) { if (error instanceof NodeOperationError) {
throw new ResponseHelper.BadRequestError(error.message); throw new BadRequestError(error.message);
} }
throw new ResponseHelper.BadRequestError( throw new BadRequestError(
'Invalid workflow credentials - make sure you have access to all credentials and try again.', 'Invalid workflow credentials - make sure you have access to all credentials and try again.',
); );
} }

View file

@ -6,7 +6,6 @@ import { In, Like } from 'typeorm';
import pick from 'lodash/pick'; import pick from 'lodash/pick';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
import * as ResponseHelper from '@/ResponseHelper';
import * as WorkflowHelpers from '@/WorkflowHelpers'; import * as WorkflowHelpers from '@/WorkflowHelpers';
import config from '@/config'; import config from '@/config';
import type { SharedWorkflow } from '@db/entities/SharedWorkflow'; import type { SharedWorkflow } from '@db/entities/SharedWorkflow';
@ -34,6 +33,8 @@ import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee'
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
import { WorkflowTagMappingRepository } from '@db/repositories/workflowTagMapping.repository'; import { WorkflowTagMappingRepository } from '@db/repositories/workflowTagMapping.repository';
import { ExecutionRepository } from '@db/repositories/execution.repository'; import { ExecutionRepository } from '@db/repositories/execution.repository';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
export class WorkflowsService { export class WorkflowsService {
static async getSharing( static async getSharing(
@ -208,7 +209,7 @@ export class WorkflowsService {
workflowId, workflowId,
userId: user.id, userId: user.id,
}); });
throw new ResponseHelper.NotFoundError( throw new NotFoundError(
'You do not have permission to update this workflow. Ask the owner to share it with you.', 'You do not have permission to update this workflow. Ask the owner to share it with you.',
); );
} }
@ -220,7 +221,7 @@ export class WorkflowsService {
workflow.versionId !== '' && workflow.versionId !== '' &&
workflow.versionId !== shared.workflow.versionId workflow.versionId !== shared.workflow.versionId
) { ) {
throw new ResponseHelper.BadRequestError( throw new BadRequestError(
'Your most recent changes may be lost, because someone else just updated this workflow. Open this workflow in a new tab to see those new updates.', 'Your most recent changes may be lost, because someone else just updated this workflow. Open this workflow in a new tab to see those new updates.',
100, 100,
); );
@ -330,7 +331,7 @@ export class WorkflowsService {
}); });
if (updatedWorkflow === null) { if (updatedWorkflow === null) {
throw new ResponseHelper.BadRequestError( throw new BadRequestError(
`Workflow with ID "${workflowId}" could not be found to be updated.`, `Workflow with ID "${workflowId}" could not be found to be updated.`,
); );
} }
@ -368,7 +369,7 @@ export class WorkflowsService {
message = message ?? (error as Error).message; message = message ?? (error as Error).message;
// Now return the original error for UI to display // Now return the original error for UI to display
throw new ResponseHelper.BadRequestError(message); throw new BadRequestError(message);
} }
} }

View file

@ -6,7 +6,6 @@ import type { PublicUser } from '@/Interfaces';
import type { User } from '@db/entities/User'; import type { User } from '@db/entities/User';
import { MeController } from '@/controllers/me.controller'; import { MeController } from '@/controllers/me.controller';
import { AUTH_COOKIE_NAME } from '@/constants'; import { AUTH_COOKIE_NAME } from '@/constants';
import { BadRequestError } from '@/ResponseHelper';
import type { AuthenticatedRequest, MeRequest } from '@/requests'; import type { AuthenticatedRequest, MeRequest } from '@/requests';
import { UserService } from '@/services/user.service'; import { UserService } from '@/services/user.service';
import { ExternalHooks } from '@/ExternalHooks'; import { ExternalHooks } from '@/ExternalHooks';
@ -14,6 +13,7 @@ import { InternalHooks } from '@/InternalHooks';
import { License } from '@/License'; import { License } from '@/License';
import { badPasswords } from '../shared/testData'; import { badPasswords } from '../shared/testData';
import { mockInstance } from '../../shared/mocking'; import { mockInstance } from '../../shared/mocking';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
describe('MeController', () => { describe('MeController', () => {
const externalHooks = mockInstance(ExternalHooks); const externalHooks = mockInstance(ExternalHooks);

View file

@ -7,7 +7,6 @@ import { OAuth1CredentialController } from '@/controllers/oauth/oAuth1Credential
import type { CredentialsEntity } from '@db/entities/CredentialsEntity'; import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
import type { User } from '@db/entities/User'; import type { User } from '@db/entities/User';
import type { OAuthRequest } from '@/requests'; import type { OAuthRequest } from '@/requests';
import { BadRequestError, NotFoundError } from '@/ResponseHelper';
import { CredentialsRepository } from '@db/repositories/credentials.repository'; import { CredentialsRepository } from '@db/repositories/credentials.repository';
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
import { ExternalHooks } from '@/ExternalHooks'; import { ExternalHooks } from '@/ExternalHooks';
@ -17,6 +16,8 @@ import { SecretsHelper } from '@/SecretsHelpers';
import { CredentialsHelper } from '@/CredentialsHelper'; import { CredentialsHelper } from '@/CredentialsHelper';
import { mockInstance } from '../../shared/mocking'; import { mockInstance } from '../../shared/mocking';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
describe('OAuth1CredentialController', () => { describe('OAuth1CredentialController', () => {
mockInstance(Logger); mockInstance(Logger);

View file

@ -9,7 +9,6 @@ import { OAuth2CredentialController } from '@/controllers/oauth/oAuth2Credential
import type { CredentialsEntity } from '@db/entities/CredentialsEntity'; import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
import type { User } from '@db/entities/User'; import type { User } from '@db/entities/User';
import type { OAuthRequest } from '@/requests'; import type { OAuthRequest } from '@/requests';
import { BadRequestError, NotFoundError } from '@/ResponseHelper';
import { CredentialsRepository } from '@db/repositories/credentials.repository'; import { CredentialsRepository } from '@db/repositories/credentials.repository';
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
import { ExternalHooks } from '@/ExternalHooks'; import { ExternalHooks } from '@/ExternalHooks';
@ -19,6 +18,8 @@ import { SecretsHelper } from '@/SecretsHelpers';
import { CredentialsHelper } from '@/CredentialsHelper'; import { CredentialsHelper } from '@/CredentialsHelper';
import { mockInstance } from '../../shared/mocking'; import { mockInstance } from '../../shared/mocking';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error';
describe('OAuth2CredentialController', () => { describe('OAuth2CredentialController', () => {
mockInstance(Logger); mockInstance(Logger);

View file

@ -5,7 +5,6 @@ import type { IInternalHooksClass } from '@/Interfaces';
import type { User } from '@db/entities/User'; import type { User } from '@db/entities/User';
import type { SettingsRepository } from '@db/repositories/settings.repository'; import type { SettingsRepository } from '@db/repositories/settings.repository';
import type { Config } from '@/config'; import type { Config } from '@/config';
import { BadRequestError } from '@/ResponseHelper';
import type { OwnerRequest } from '@/requests'; import type { OwnerRequest } from '@/requests';
import { OwnerController } from '@/controllers/owner.controller'; import { OwnerController } from '@/controllers/owner.controller';
import { AUTH_COOKIE_NAME } from '@/constants'; import { AUTH_COOKIE_NAME } from '@/constants';
@ -14,6 +13,7 @@ import { License } from '@/License';
import { mockInstance } from '../../shared/mocking'; import { mockInstance } from '../../shared/mocking';
import { badPasswords } from '../shared/testData'; import { badPasswords } from '../shared/testData';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
describe('OwnerController', () => { describe('OwnerController', () => {
const config = mock<Config>(); const config = mock<Config>();

View file

@ -6,7 +6,7 @@ import {
TranslationController, TranslationController,
CREDENTIAL_TRANSLATIONS_DIR, CREDENTIAL_TRANSLATIONS_DIR,
} from '@/controllers/translation.controller'; } from '@/controllers/translation.controller';
import { BadRequestError } from '@/ResponseHelper'; import { BadRequestError } from '@/errors/response-errors/bad-request.error';
describe('TranslationController', () => { describe('TranslationController', () => {
const config = mock<Config>(); const config = mock<Config>();