refactor: Improve error logging/reporting for cli (#4691)

* use response error classes instead of `ResponseError` everywhere

* improve error logging in dev mode or when telemetry is disabled
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2022-11-22 14:00:36 +01:00 committed by GitHub
parent 5364e7fc92
commit 0b754a4f85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 253 additions and 393 deletions

View file

@ -161,6 +161,7 @@
"passport": "^0.6.0",
"passport-cookie": "^1.0.9",
"passport-jwt": "^4.0.0",
"picocolors": "^1.0.0",
"pg": "^8.3.0",
"posthog-node": "^1.3.0",
"prom-client": "^13.1.0",

View file

@ -191,10 +191,8 @@ export class ActiveWorkflowRunner {
): Promise<IResponseCallbackData> {
Logger.debug(`Received webhook "${httpMethod}" for path "${path}"`);
if (this.activeWorkflows === null) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
'The "activeWorkflows" instance did not get initialized yet.',
404,
404,
);
}
@ -224,10 +222,8 @@ export class ActiveWorkflowRunner {
});
if (dynamicWebhooks === undefined || dynamicWebhooks.length === 0) {
// The requested webhook is not registered
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
`The requested webhook "${httpMethod} ${path}" is not registered.`,
404,
404,
WEBHOOK_PROD_UNREGISTERED_HINT,
);
}
@ -252,10 +248,8 @@ export class ActiveWorkflowRunner {
}
});
if (webhook === undefined) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
`The requested webhook "${httpMethod} ${path}" is not registered.`,
404,
404,
WEBHOOK_PROD_UNREGISTERED_HINT,
);
}
@ -277,10 +271,8 @@ export class ActiveWorkflowRunner {
relations: ['shared', 'shared.user', 'shared.user.globalRole'],
});
if (workflowData === undefined) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
`Could not find workflow with id "${webhook.workflowId}"`,
404,
404,
);
}
@ -313,7 +305,7 @@ export class ActiveWorkflowRunner {
const workflowStartNode = workflow.getNode(webhookData.node);
if (workflowStartNode === null) {
throw new ResponseHelper.ResponseError('Could not find node to process webhook.', 404, 404);
throw new ResponseHelper.NotFoundError('Could not find node to process webhook.');
}
return new Promise((resolve, reject) => {

View file

@ -212,7 +212,7 @@ export async function validateEntity(
.join(' | ');
if (errorMessages) {
throw new ResponseHelper.ResponseError(errorMessages, undefined, 400);
throw new ResponseHelper.BadRequestError(errorMessages);
}
}

View file

@ -5,7 +5,8 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Request, Response } from 'express';
import { parse, stringify } from 'flatted';
import { ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
import picocolors from 'picocolors';
import { ErrorReporterProxy as ErrorReporter, NodeApiError } from 'n8n-workflow';
import type {
IExecutionDb,
@ -15,44 +16,69 @@ import type {
IWorkflowDb,
} from './Interfaces';
const inDevelopment = !process.env.NODE_ENV || process.env.NODE_ENV === 'development';
/**
* Special Error which allows to return also an error code and http status code
*
* @class ResponseError
* @extends {Error}
*/
export class ResponseError extends Error {
// The HTTP status code of response
httpStatusCode?: number;
// The error code in the response
errorCode?: number;
// The error hint the response
hint?: string;
abstract class ResponseError extends Error {
/**
* Creates an instance of ResponseError.
* Must be used inside a block with `ResponseHelper.send()`.
*
* @param {string} message The error message
* @param {number} [errorCode] The error code which can be used by frontend to identify the actual error
* @param {number} [httpStatusCode] The HTTP status code the response should have
* @param {string} [hint] The error hint to provide a context (webhook related)
*/
constructor(message: string, errorCode?: number, httpStatusCode?: number, hint?: string) {
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';
}
}
if (errorCode) {
this.errorCode = errorCode;
}
if (httpStatusCode) {
this.httpStatusCode = httpStatusCode;
}
if (hint) {
this.hint = hint;
}
export class BadRequestError extends ResponseError {
constructor(message: string) {
super(message, 400);
}
}
export class AuthError extends ResponseError {
constructor(message: string) {
super(message, 401);
}
}
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 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);
}
}
@ -95,40 +121,49 @@ export function sendSuccessResponse(
}
}
export function sendErrorResponse(res: Response, error: ResponseError) {
interface ErrorResponse {
code: number;
message: string;
hint?: string;
stacktrace?: string;
}
export function sendErrorResponse(res: Response, error: Error) {
let httpStatusCode = 500;
if (error.httpStatusCode) {
httpStatusCode = error.httpStatusCode;
}
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
console.error('ERROR RESPONSE');
console.error(error);
}
const response = {
const response: ErrorResponse = {
code: 0,
message: 'Unknown error',
hint: '',
};
if (error.name === 'NodeApiError') {
if (error instanceof ResponseError) {
if (inDevelopment) {
console.error(picocolors.red(error.httpStatusCode), error.message);
}
response.message = error.message;
httpStatusCode = error.httpStatusCode;
if (error.errorCode) {
response.code = error.errorCode;
}
if (error.hint) {
response.hint = error.hint;
}
}
if (error instanceof NodeApiError) {
if (inDevelopment) {
console.error(picocolors.red(error.name), error.message);
}
Object.assign(response, error);
}
if (error.errorCode) {
response.code = error.errorCode;
}
if (error.message) {
response.message = error.message;
}
if (error.hint) {
response.hint = error.hint;
}
if (error.stack && process.env.NODE_ENV !== 'production') {
// @ts-ignore
response.stack = error.stack;
if (error.stack && inDevelopment) {
response.stacktrace = error.stack;
}
res.status(httpStatusCode).json(response);
}
@ -154,7 +189,9 @@ export function send<T, R extends Request, S extends Response>(
sendSuccessResponse(res, data, raw);
} catch (error) {
if (error instanceof Error) {
ErrorReporter.error(error);
if (!(error instanceof ResponseError) || error.httpStatusCode > 404) {
ErrorReporter.error(error);
}
if (isUniqueConstraintError(error)) {
error.message = 'There is already an entry with this name';

View file

@ -157,7 +157,6 @@ import { WaitTracker, WaitTrackerClass } from '@/WaitTracker';
import * as WebhookHelpers from '@/WebhookHelpers';
import * as WebhookServer from '@/WebhookServer';
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
import { ResponseError } from '@/ResponseHelper';
import { toHttpNodeParameters } from '@/CurlConverterHelper';
import { setupErrorMiddleware } from '@/ErrorReporting';
import { getLicense } from '@/License';
@ -725,7 +724,7 @@ class App {
// eslint-disable-next-line consistent-return
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
if (!Db.isInitialized) {
const error = new ResponseHelper.ResponseError('Database is not ready!', undefined, 503);
const error = new ResponseHelper.ServiceUnavailableError('Database is not ready!');
return ResponseHelper.sendErrorResponse(res, error);
}
@ -766,7 +765,7 @@ class App {
} catch (err) {
ErrorReporter.error(err);
LoggerProxy.error('No Database connection!', err);
const error = new ResponseHelper.ResponseError('No Database connection!', undefined, 503);
const error = new ResponseHelper.ServiceUnavailableError('No Database connection!');
return ResponseHelper.sendErrorResponse(res, error);
}
@ -869,7 +868,9 @@ class App {
const { path, methodName } = req.query;
if (!req.query.currentNodeParameters) {
throw new ResponseError('Parameter currentNodeParameters is required.', undefined, 400);
throw new ResponseHelper.BadRequestError(
'Parameter currentNodeParameters is required.',
);
}
const currentNodeParameters = jsonParse(
@ -904,7 +905,7 @@ class App {
);
}
throw new ResponseError('Parameter methodName is required.', undefined, 400);
throw new ResponseHelper.BadRequestError('Parameter methodName is required.');
},
),
);
@ -1076,10 +1077,8 @@ class App {
userId: req.user.id,
});
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
`Workflow with ID "${workflowId}" could not be found.`,
undefined,
400,
);
}
@ -1103,7 +1102,7 @@ class App {
const parameters = toHttpNodeParameters(curlCommand);
return ResponseHelper.flattenObject(parameters, 'parameters');
} catch (e) {
throw new ResponseHelper.ResponseError(`Invalid cURL command`, undefined, 400);
throw new ResponseHelper.BadRequestError(`Invalid cURL command`);
}
},
),
@ -1178,11 +1177,7 @@ class App {
if (!credentialId) {
LoggerProxy.error('OAuth1 credential authorization failed due to missing credential ID');
throw new ResponseHelper.ResponseError(
'Required credential ID is missing',
undefined,
400,
);
throw new ResponseHelper.BadRequestError('Required credential ID is missing');
}
const credential = await getCredentialForUser(credentialId, req.user);
@ -1192,18 +1187,14 @@ class App {
'OAuth1 credential authorization failed because the current user does not have the correct permissions',
{ userId: req.user.id },
);
throw new ResponseHelper.ResponseError(
RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL,
undefined,
404,
);
throw new ResponseHelper.NotFoundError(RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL);
}
let encryptionKey: string;
try {
encryptionKey = await UserSettings.getEncryptionKey();
} catch (error) {
throw new ResponseHelper.ResponseError(error.message, undefined, 500);
throw new ResponseHelper.InternalServerError(error.message);
}
const mode: WorkflowExecuteMode = 'internal';
@ -1304,12 +1295,10 @@ class App {
const { oauth_verifier, oauth_token, cid: credentialId } = req.query;
if (!oauth_verifier || !oauth_token) {
const errorResponse = new ResponseHelper.ResponseError(
const errorResponse = new ResponseHelper.ServiceUnavailableError(
`Insufficient parameters for OAuth1 callback. Received following query parameters: ${JSON.stringify(
req.query,
)}`,
undefined,
503,
);
LoggerProxy.error(
'OAuth1 callback failed because of insufficient parameters received',
@ -1328,10 +1317,8 @@ class App {
userId: req.user?.id,
credentialId,
});
const errorResponse = new ResponseHelper.ResponseError(
const errorResponse = new ResponseHelper.NotFoundError(
RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL,
undefined,
404,
);
return ResponseHelper.sendErrorResponse(res, errorResponse);
}
@ -1340,7 +1327,7 @@ class App {
try {
encryptionKey = await UserSettings.getEncryptionKey();
} catch (error) {
throw new ResponseHelper.ResponseError(error.message, undefined, 500);
throw new ResponseHelper.InternalServerError(error.message);
}
const mode: WorkflowExecuteMode = 'internal';
@ -1378,11 +1365,7 @@ class App {
userId: req.user?.id,
credentialId,
});
const errorResponse = new ResponseHelper.ResponseError(
'Unable to get access tokens!',
undefined,
404,
);
const errorResponse = new ResponseHelper.NotFoundError('Unable to get access tokens!');
return ResponseHelper.sendErrorResponse(res, errorResponse);
}
@ -1539,7 +1522,7 @@ class App {
const sharedWorkflowIds = await getSharedWorkflowIds(req.user);
if (!sharedWorkflowIds.length) {
throw new ResponseHelper.ResponseError('Execution not found', undefined, 404);
throw new ResponseHelper.NotFoundError('Execution not found');
}
const execution = await Db.collections.Execution.findOne({
@ -1550,7 +1533,7 @@ class App {
});
if (!execution) {
throw new ResponseHelper.ResponseError('Execution not found', undefined, 404);
throw new ResponseHelper.NotFoundError('Execution not found');
}
if (config.getEnv('executions.mode') === 'queue') {

View file

@ -67,10 +67,8 @@ export class TestWebhooks {
webhookData = this.activeWebhooks!.get(httpMethod, pathElements.join('/'), webhookId);
if (webhookData === undefined) {
// The requested webhook is not registered
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
`The requested webhook "${httpMethod} ${path}" is not registered.`,
404,
404,
WEBHOOK_TEST_UNREGISTERED_HINT,
);
}
@ -94,10 +92,8 @@ export class TestWebhooks {
// TODO: Clean that duplication up one day and improve code generally
if (this.testWebhookData[webhookKey] === undefined) {
// The requested webhook is not registered
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
`The requested webhook "${httpMethod} ${path}" is not registered.`,
404,
404,
WEBHOOK_TEST_UNREGISTERED_HINT,
);
}
@ -108,7 +104,7 @@ export class TestWebhooks {
// get additional data
const workflowStartNode = workflow.getNode(webhookData.node);
if (workflowStartNode === null) {
throw new ResponseHelper.ResponseError('Could not find node to process webhook.', 404, 404);
throw new ResponseHelper.NotFoundError('Could not find node to process webhook.');
}
// eslint-disable-next-line no-async-promise-executor
@ -173,10 +169,8 @@ export class TestWebhooks {
if (webhookMethods === undefined) {
// The requested webhook is not registered
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
`The requested webhook "${path}" is not registered.`,
404,
404,
WEBHOOK_TEST_UNREGISTERED_HINT,
);
}

View file

@ -90,7 +90,7 @@ export function getInstanceBaseUrl(): string {
// TODO: Enforce at model level
export function validatePassword(password?: string): string {
if (!password) {
throw new ResponseHelper.ResponseError('Password is mandatory', undefined, 400);
throw new ResponseHelper.BadRequestError('Password is mandatory');
}
const hasInvalidLength =
@ -117,7 +117,7 @@ export function validatePassword(password?: string): string {
message.push('Password must contain at least 1 uppercase letter.');
}
throw new ResponseHelper.ResponseError(message.join(' '), undefined, 400);
throw new ResponseHelper.BadRequestError(message.join(' '));
}
return password;

View file

@ -77,24 +77,20 @@ export function authenticationMethods(this: N8nApp): void {
}
if (config.get('userManagement.isInstanceOwnerSetUp')) {
throw new ResponseHelper.ResponseError('Not logged in', undefined, 401);
throw new ResponseHelper.AuthError('Not logged in');
}
try {
user = await Db.collections.User.findOneOrFail({ relations: ['globalRole'] });
} catch (error) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.InternalServerError(
'No users found in database - did you wipe the users table? Create at least one user.',
undefined,
500,
);
}
if (user.email || user.password) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.InternalServerError(
'Invalid database state - user has password set.',
undefined,
500,
);
}

View file

@ -39,7 +39,7 @@ export function meNamespace(this: N8nApp): void {
userId: req.user.id,
payload: req.body,
});
throw new ResponseHelper.ResponseError('Email is mandatory', undefined, 400);
throw new ResponseHelper.BadRequestError('Email is mandatory');
}
if (!validator.isEmail(email)) {
@ -47,7 +47,7 @@ export function meNamespace(this: N8nApp): void {
userId: req.user.id,
invalidEmail: email,
});
throw new ResponseHelper.ResponseError('Invalid email address', undefined, 400);
throw new ResponseHelper.BadRequestError('Invalid email address');
}
const { email: currentEmail } = req.user;
@ -84,20 +84,16 @@ export function meNamespace(this: N8nApp): void {
const { currentPassword, newPassword } = req.body;
if (typeof currentPassword !== 'string' || typeof newPassword !== 'string') {
throw new ResponseHelper.ResponseError('Invalid payload.', undefined, 400);
throw new ResponseHelper.BadRequestError('Invalid payload.');
}
if (!req.user.password) {
throw new ResponseHelper.ResponseError('Requesting user not set up.');
throw new ResponseHelper.BadRequestError('Requesting user not set up.');
}
const isCurrentPwCorrect = await compareHash(currentPassword, req.user.password);
if (!isCurrentPwCorrect) {
throw new ResponseHelper.ResponseError(
'Provided current password is incorrect.',
undefined,
400,
);
throw new ResponseHelper.BadRequestError('Provided current password is incorrect.');
}
const validPassword = validatePassword(newPassword);
@ -135,11 +131,7 @@ export function meNamespace(this: N8nApp): void {
userId: req.user.id,
},
);
throw new ResponseHelper.ResponseError(
'Personalization answers are mandatory',
undefined,
400,
);
throw new ResponseHelper.BadRequestError('Personalization answers are mandatory');
}
await Db.collections.User.save({

View file

@ -31,7 +31,7 @@ export function ownerNamespace(this: N8nApp): void {
userId,
},
);
throw new ResponseHelper.ResponseError('Invalid request', undefined, 400);
throw new ResponseHelper.BadRequestError('Invalid request');
}
if (!email || !validator.isEmail(email)) {
@ -39,7 +39,7 @@ export function ownerNamespace(this: N8nApp): void {
userId,
invalidEmail: email,
});
throw new ResponseHelper.ResponseError('Invalid email address', undefined, 400);
throw new ResponseHelper.BadRequestError('Invalid email address');
}
const validPassword = validatePassword(password);
@ -49,11 +49,7 @@ export function ownerNamespace(this: N8nApp): void {
'Request to claim instance ownership failed because of missing first name or last name in payload',
{ userId, payload: req.body },
);
throw new ResponseHelper.ResponseError(
'First and last names are mandatory',
undefined,
400,
);
throw new ResponseHelper.BadRequestError('First and last names are mandatory');
}
let owner = await Db.collections.User.findOne(userId, {
@ -67,7 +63,7 @@ export function ownerNamespace(this: N8nApp): void {
userId,
},
);
throw new ResponseHelper.ResponseError('Invalid request', undefined, 400);
throw new ResponseHelper.BadRequestError('Invalid request');
}
owner = Object.assign(owner, {

View file

@ -28,10 +28,8 @@ export function passwordResetNamespace(this: N8nApp): void {
ResponseHelper.send(async (req: PasswordResetRequest.Email) => {
if (config.getEnv('userManagement.emails.mode') === '') {
Logger.debug('Request to send password reset email failed because emailing was not set up');
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.InternalServerError(
'Email sending must be set up in order to request a password reset email',
undefined,
500,
);
}
@ -42,7 +40,7 @@ export function passwordResetNamespace(this: N8nApp): void {
'Request to send password reset email failed because of missing email in payload',
{ payload: req.body },
);
throw new ResponseHelper.ResponseError('Email is mandatory', undefined, 400);
throw new ResponseHelper.BadRequestError('Email is mandatory');
}
if (!validator.isEmail(email)) {
@ -50,7 +48,7 @@ export function passwordResetNamespace(this: N8nApp): void {
'Request to send password reset email failed because of invalid email in payload',
{ invalidEmail: email },
);
throw new ResponseHelper.ResponseError('Invalid email address', undefined, 400);
throw new ResponseHelper.BadRequestError('Invalid email address');
}
// User should just be able to reset password if one is already present
@ -93,10 +91,8 @@ export function passwordResetNamespace(this: N8nApp): void {
public_api: false,
});
if (error instanceof Error) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.InternalServerError(
`Please contact your administrator: ${error.message}`,
undefined,
500,
);
}
}
@ -131,7 +127,7 @@ export function passwordResetNamespace(this: N8nApp): void {
queryString: req.query,
},
);
throw new ResponseHelper.ResponseError('', undefined, 400);
throw new ResponseHelper.BadRequestError('');
}
// Timestamp is saved in seconds
@ -151,7 +147,7 @@ export function passwordResetNamespace(this: N8nApp): void {
resetPasswordToken,
},
);
throw new ResponseHelper.ResponseError('', undefined, 404);
throw new ResponseHelper.NotFoundError('');
}
Logger.info('Reset-password token resolved successfully', { userId: id });
@ -178,10 +174,8 @@ export function passwordResetNamespace(this: N8nApp): void {
payload: req.body,
},
);
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
'Missing user ID or password or reset password token',
undefined,
400,
);
}
@ -204,7 +198,7 @@ export function passwordResetNamespace(this: N8nApp): void {
resetPasswordToken,
},
);
throw new ResponseHelper.ResponseError('', undefined, 404);
throw new ResponseHelper.NotFoundError('');
}
await Db.collections.User.update(userId, {

View file

@ -37,10 +37,8 @@ export function usersNamespace(this: N8nApp): void {
Logger.debug(
'Request to send email invite(s) to user(s) failed because emailing was not set up',
);
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.InternalServerError(
'Email sending must be set up in order to request a password reset email',
undefined,
500,
);
}
@ -49,10 +47,8 @@ export function usersNamespace(this: N8nApp): void {
mailer = await UserManagementMailer.getInstance();
} catch (error) {
if (error instanceof Error) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.InternalServerError(
`There is a problem with your SMTP setup! ${error.message}`,
undefined,
500,
);
}
}
@ -62,17 +58,15 @@ export function usersNamespace(this: N8nApp): void {
Logger.debug(
'Request to send email invite(s) to user(s) failed because user management is disabled',
);
throw new ResponseHelper.ResponseError('User management is disabled');
throw new ResponseHelper.BadRequestError('User management is disabled');
}
if (!config.getEnv('userManagement.isInstanceOwnerSetUp')) {
Logger.debug(
'Request to send email invite(s) to user(s) failed because the owner account is not set up',
);
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
'You must set up your own account before inviting others',
undefined,
400,
);
}
@ -83,7 +77,7 @@ export function usersNamespace(this: N8nApp): void {
payload: req.body,
},
);
throw new ResponseHelper.ResponseError('Invalid payload', undefined, 400);
throw new ResponseHelper.BadRequestError('Invalid payload');
}
if (!req.body.length) return [];
@ -92,19 +86,15 @@ export function usersNamespace(this: N8nApp): void {
// Validate payload
req.body.forEach((invite) => {
if (typeof invite !== 'object' || !invite.email) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
'Request to send email invite(s) to user(s) failed because the payload is not an array shaped Array<{ email: string }>',
undefined,
400,
);
}
if (!validator.isEmail(invite.email)) {
Logger.debug('Invalid email in payload', { invalidEmail: invite.email });
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
`Request to send email invite(s) to user(s) failed because of an invalid email address: ${invite.email}`,
undefined,
400,
);
}
createUsers[invite.email.toLowerCase()] = null;
@ -116,10 +106,8 @@ export function usersNamespace(this: N8nApp): void {
Logger.error(
'Request to send email invite(s) to user(s) failed because no global member role was found in database',
);
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.InternalServerError(
'Members role not found in database - inconsistent state',
undefined,
500,
);
}
@ -163,7 +151,7 @@ export function usersNamespace(this: N8nApp): void {
} catch (error) {
ErrorReporter.error(error);
Logger.error('Failed to create user shells', { userShells: createUsers });
throw new ResponseHelper.ResponseError('An error occurred during user creation');
throw new ResponseHelper.InternalServerError('An error occurred during user creation');
}
Logger.info('Created user shell(s) successfully', { userId: req.user.id });
@ -245,7 +233,7 @@ export function usersNamespace(this: N8nApp): void {
'Request to resolve signup token failed because of missing user IDs in query string',
{ inviterId, inviteeId },
);
throw new ResponseHelper.ResponseError('Invalid payload', undefined, 400);
throw new ResponseHelper.BadRequestError('Invalid payload');
}
// Postgres validates UUID format
@ -254,7 +242,7 @@ export function usersNamespace(this: N8nApp): void {
Logger.debug('Request to resolve signup token failed because of invalid user ID', {
userId,
});
throw new ResponseHelper.ResponseError('Invalid userId', undefined, 400);
throw new ResponseHelper.BadRequestError('Invalid userId');
}
}
@ -265,7 +253,7 @@ export function usersNamespace(this: N8nApp): void {
'Request to resolve signup token failed because the ID of the inviter and/or the ID of the invitee were not found in database',
{ inviterId, inviteeId },
);
throw new ResponseHelper.ResponseError('Invalid invite URL', undefined, 400);
throw new ResponseHelper.BadRequestError('Invalid invite URL');
}
const invitee = users.find((user) => user.id === inviteeId);
@ -275,10 +263,8 @@ export function usersNamespace(this: N8nApp): void {
inviterId,
inviteeId,
});
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
'The invitation was likely either deleted or already claimed',
undefined,
400,
);
}
@ -291,7 +277,7 @@ export function usersNamespace(this: N8nApp): void {
inviterId: inviter?.id,
},
);
throw new ResponseHelper.ResponseError('Invalid request', undefined, 400);
throw new ResponseHelper.BadRequestError('Invalid request');
}
void InternalHooksManager.getInstance().onUserInviteEmailClick({
@ -321,7 +307,7 @@ export function usersNamespace(this: N8nApp): void {
'Request to fill out a user shell failed because of missing properties in payload',
{ payload: req.body },
);
throw new ResponseHelper.ResponseError('Invalid payload', undefined, 400);
throw new ResponseHelper.BadRequestError('Invalid payload');
}
const validPassword = validatePassword(password);
@ -339,7 +325,7 @@ export function usersNamespace(this: N8nApp): void {
inviteeId,
},
);
throw new ResponseHelper.ResponseError('Invalid payload or URL', undefined, 400);
throw new ResponseHelper.BadRequestError('Invalid payload or URL');
}
const invitee = users.find((user) => user.id === inviteeId) as User;
@ -349,11 +335,7 @@ export function usersNamespace(this: N8nApp): void {
'Request to fill out a user shell failed because the invite had already been accepted',
{ inviteeId },
);
throw new ResponseHelper.ResponseError(
'This invite has been accepted already',
undefined,
400,
);
throw new ResponseHelper.BadRequestError('This invite has been accepted already');
}
invitee.firstName = firstName;
@ -398,16 +380,14 @@ export function usersNamespace(this: N8nApp): void {
'Request to delete a user failed because it attempted to delete the requesting user',
{ userId: req.user.id },
);
throw new ResponseHelper.ResponseError('Cannot delete your own user', undefined, 400);
throw new ResponseHelper.BadRequestError('Cannot delete your own user');
}
const { transferId } = req.query;
if (transferId === idToDelete) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
'Request to delete a user failed because the user to delete and the transferee are the same user',
undefined,
400,
);
}
@ -416,10 +396,8 @@ export function usersNamespace(this: N8nApp): void {
});
if (!users.length || (transferId && users.length !== 2)) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
'Request to delete a user failed because the ID of the user to delete and/or the ID of the transferee were not found in DB',
undefined,
404,
);
}
@ -502,10 +480,8 @@ export function usersNamespace(this: N8nApp): void {
if (!isEmailSetUp()) {
Logger.error('Request to reinvite a user failed because email sending was not set up');
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.InternalServerError(
'Email sending must be set up in order to invite other users',
undefined,
500,
);
}
@ -515,7 +491,7 @@ export function usersNamespace(this: N8nApp): void {
Logger.debug(
'Request to reinvite a user failed because the ID of the reinvitee was not found in database',
);
throw new ResponseHelper.ResponseError('Could not find user', undefined, 404);
throw new ResponseHelper.NotFoundError('Could not find user');
}
if (reinvitee.password) {
@ -523,11 +499,7 @@ export function usersNamespace(this: N8nApp): void {
'Request to reinvite a user failed because the invite had already been accepted',
{ userId: reinvitee.id },
);
throw new ResponseHelper.ResponseError(
'User has already accepted the invite',
undefined,
400,
);
throw new ResponseHelper.BadRequestError('User has already accepted the invite');
}
const baseUrl = getInstanceBaseUrl();
@ -538,7 +510,7 @@ export function usersNamespace(this: N8nApp): void {
mailer = await UserManagementMailer.getInstance();
} catch (error) {
if (error instanceof Error) {
throw new ResponseHelper.ResponseError(error.message, undefined, 500);
throw new ResponseHelper.InternalServerError(error.message);
}
}
@ -559,11 +531,7 @@ export function usersNamespace(this: N8nApp): void {
inviteAcceptUrl,
domain: baseUrl,
});
throw new ResponseHelper.ResponseError(
`Failed to send email to ${reinvitee.email}`,
undefined,
500,
);
throw new ResponseHelper.InternalServerError(`Failed to send email to ${reinvitee.email}`);
}
void InternalHooksManager.getInstance().onUserReinvite({

View file

@ -44,21 +44,13 @@ export class WaitingWebhooks {
const execution = await Db.collections.Execution.findOne(executionId);
if (execution === undefined) {
throw new ResponseHelper.ResponseError(
`The execution "${executionId} does not exist.`,
404,
404,
);
throw new ResponseHelper.NotFoundError(`The execution "${executionId} does not exist.`);
}
const fullExecutionData = ResponseHelper.unflattenExecutionData(execution);
if (fullExecutionData.finished || fullExecutionData.data.resultData.error) {
throw new ResponseHelper.ResponseError(
`The execution "${executionId} has finished already.`,
409,
409,
);
throw new ResponseHelper.ConflictError(`The execution "${executionId} has finished already.`);
}
return this.startExecution(httpMethod, path, fullExecutionData, req, res);
@ -107,7 +99,7 @@ export class WaitingWebhooks {
try {
workflowOwner = await getWorkflowOwner(workflowData.id!.toString());
} catch (error) {
throw new ResponseHelper.ResponseError('Could not find workflow', undefined, 404);
throw new ResponseHelper.NotFoundError('Could not find workflow');
}
const additionalData = await WorkflowExecuteAdditionalData.getBase(workflowOwner.id);
@ -128,13 +120,13 @@ export class WaitingWebhooks {
// 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.
const errorMessage = `The execution "${executionId}" with webhook suffix path "${path}" is not known.`;
throw new ResponseHelper.ResponseError(errorMessage, 404, 404);
throw new ResponseHelper.NotFoundError(errorMessage);
}
const workflowStartNode = workflow.getNode(lastNodeExecuted);
if (workflowStartNode === null) {
throw new ResponseHelper.ResponseError('Could not find node to process webhook.', 404, 404);
throw new ResponseHelper.NotFoundError('Could not find node to process webhook.');
}
const runExecutionData = fullExecutionData.data;

View file

@ -152,7 +152,7 @@ export async function executeWebhook(
if (nodeType === undefined) {
const errorMessage = `The type of the webhook node "${workflowStartNode.name}" is not known`;
responseCallback(new Error(errorMessage), {});
throw new ResponseHelper.ResponseError(errorMessage, 500, 500);
throw new ResponseHelper.InternalServerError(errorMessage);
}
const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
@ -169,7 +169,7 @@ export async function executeWebhook(
try {
user = await getWorkflowOwner(workflowData.id.toString());
} catch (error) {
throw new ResponseHelper.ResponseError('Cannot find workflow', undefined, 404);
throw new ResponseHelper.NotFoundError('Cannot find workflow');
}
}
@ -212,7 +212,7 @@ export async function executeWebhook(
// that something does not resolve properly.
const errorMessage = `The response mode '${responseMode}' is not valid!`;
responseCallback(new Error(errorMessage), {});
throw new ResponseHelper.ResponseError(errorMessage, 500, 500);
throw new ResponseHelper.InternalServerError(errorMessage);
}
// Add the Response and Request so that this data can be accessed in the node
@ -661,7 +661,7 @@ export async function executeWebhook(
responseCallback(new Error('There was a problem executing the workflow'), {});
}
throw new ResponseHelper.ResponseError(e.message, 500, 500);
throw new ResponseHelper.InternalServerError(e.message);
});
// eslint-disable-next-line consistent-return
@ -671,7 +671,7 @@ export async function executeWebhook(
responseCallback(new Error('There was a problem executing the workflow'), {});
}
throw new ResponseHelper.ResponseError(e.message, 500, 500);
throw new ResponseHelper.InternalServerError(e.message);
}
}

View file

@ -294,7 +294,7 @@ class App {
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
if (!Db.isInitialized) {
const error = new ResponseHelper.ResponseError('Database is not ready!', undefined, 503);
const error = new ResponseHelper.ServiceUnavailableError('Database is not ready!');
return ResponseHelper.sendErrorResponse(res, error);
}
@ -318,7 +318,7 @@ class App {
await connection.query('SELECT 1');
// eslint-disable-next-line id-denylist
} catch (err) {
const error = new ResponseHelper.ResponseError('No Database connection!', undefined, 503);
const error = new ResponseHelper.ServiceUnavailableError('No Database connection!');
return ResponseHelper.sendErrorResponse(res, error);
}

View file

@ -172,10 +172,8 @@ executionsController.get(
userId: req.user.id,
filter: req.query.filter,
});
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.InternalServerError(
`Parameter "filter" contained invalid JSON string.`,
500,
500,
);
}
}
@ -363,10 +361,8 @@ executionsController.post(
executionId,
},
);
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
`The execution with the ID "${executionId}" does not exist.`,
404,
404,
);
}
@ -485,10 +481,8 @@ executionsController.post(
requestFilters = requestFiltersRaw as IGetExecutionsQueryFilter;
}
} catch (error) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.InternalServerError(
`Parameter "filter" contained invalid JSON string.`,
500,
500,
);
}
}

View file

@ -71,7 +71,7 @@ nodesController.post(
const { name } = req.body;
if (!name) {
throw new ResponseHelper.ResponseError(PACKAGE_NAME_NOT_PROVIDED, undefined, 400);
throw new ResponseHelper.BadRequestError(PACKAGE_NAME_NOT_PROVIDED);
}
let parsed: CommunityPackages.ParsedPackageName;
@ -79,21 +79,17 @@ nodesController.post(
try {
parsed = parseNpmPackageName(name);
} catch (error) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
error instanceof Error ? error.message : 'Failed to parse package name',
undefined,
400,
);
}
if (parsed.packageName === STARTER_TEMPLATE_NAME) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
[
`Package "${parsed.packageName}" is only a template`,
'Please enter an actual package to install',
].join('.'),
undefined,
400,
);
}
@ -101,23 +97,19 @@ nodesController.post(
const hasLoaded = hasPackageLoaded(name);
if (isInstalled && hasLoaded) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
[
`Package "${parsed.packageName}" is already installed`,
'To update it, click the corresponding button in the UI',
].join('.'),
undefined,
400,
);
}
const packageStatus = await checkNpmPackageStatus(name);
if (packageStatus.status !== 'OK') {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
`Package "${name}" is banned so it cannot be installed`,
undefined,
400,
);
}
@ -144,7 +136,7 @@ nodesController.post(
const clientError = error instanceof Error ? isClientError(error) : false;
throw new ResponseHelper.ResponseError(message, undefined, clientError ? 400 : 500);
throw new ResponseHelper[clientError ? 'BadRequestError' : 'InternalServerError'](message);
}
if (!hasLoaded) removePackageFromMissingList(name);
@ -228,7 +220,7 @@ nodesController.delete(
const { name } = req.query;
if (!name) {
throw new ResponseHelper.ResponseError(PACKAGE_NAME_NOT_PROVIDED, undefined, 400);
throw new ResponseHelper.BadRequestError(PACKAGE_NAME_NOT_PROVIDED);
}
try {
@ -236,13 +228,13 @@ nodesController.delete(
} catch (error) {
const message = error instanceof Error ? error.message : UNKNOWN_FAILURE_REASON;
throw new ResponseHelper.ResponseError(message, undefined, 400);
throw new ResponseHelper.BadRequestError(message);
}
const installedPackage = await findInstalledPackage(name);
if (!installedPackage) {
throw new ResponseHelper.ResponseError(PACKAGE_NOT_INSTALLED, undefined, 400);
throw new ResponseHelper.BadRequestError(PACKAGE_NOT_INSTALLED);
}
try {
@ -253,7 +245,7 @@ nodesController.delete(
error instanceof Error ? error.message : UNKNOWN_FAILURE_REASON,
].join(':');
throw new ResponseHelper.ResponseError(message, undefined, 500);
throw new ResponseHelper.InternalServerError(message);
}
const pushInstance = Push.getInstance();
@ -288,13 +280,13 @@ nodesController.patch(
const { name } = req.body;
if (!name) {
throw new ResponseHelper.ResponseError(PACKAGE_NAME_NOT_PROVIDED, undefined, 400);
throw new ResponseHelper.BadRequestError(PACKAGE_NAME_NOT_PROVIDED);
}
const previouslyInstalledPackage = await findInstalledPackage(name);
if (!previouslyInstalledPackage) {
throw new ResponseHelper.ResponseError(PACKAGE_NOT_INSTALLED, undefined, 400);
throw new ResponseHelper.BadRequestError(PACKAGE_NOT_INSTALLED);
}
try {
@ -345,7 +337,7 @@ nodesController.patch(
error instanceof Error ? error.message : UNKNOWN_FAILURE_REASON,
].join(':');
throw new ResponseHelper.ResponseError(message, undefined, 500);
throw new ResponseHelper.InternalServerError(message);
}
}),
);

View file

@ -21,13 +21,18 @@ export const externalHooks: IExternalHooksClass = ExternalHooks();
export const tagsController = express.Router();
const workflowsEnabledMiddleware: express.RequestHandler = (req, res, next) => {
if (config.getEnv('workflowTagsDisabled')) {
throw new ResponseHelper.BadRequestError('Workflow tags are disabled');
}
next();
};
// Retrieves all tags, with or without usage count
tagsController.get(
'/',
workflowsEnabledMiddleware,
ResponseHelper.send(async (req: express.Request): Promise<TagEntity[] | ITagWithCountDb[]> => {
if (config.getEnv('workflowTagsDisabled')) {
throw new ResponseHelper.ResponseError('Workflow tags are disabled');
}
if (req.query.withUsageCount === 'true') {
const tablePrefix = config.getEnv('database.tablePrefix');
return TagHelpers.getTagsWithCountDb(tablePrefix);
@ -40,10 +45,8 @@ tagsController.get(
// Creates a tag
tagsController.post(
'/',
workflowsEnabledMiddleware,
ResponseHelper.send(async (req: express.Request): Promise<TagEntity | void> => {
if (config.getEnv('workflowTagsDisabled')) {
throw new ResponseHelper.ResponseError('Workflow tags are disabled');
}
const newTag = new TagEntity();
newTag.name = req.body.name.trim();
@ -61,11 +64,8 @@ tagsController.post(
// Updates a tag
tagsController.patch(
'/:id',
workflowsEnabledMiddleware,
ResponseHelper.send(async (req: express.Request): Promise<TagEntity | void> => {
if (config.getEnv('workflowTagsDisabled')) {
throw new ResponseHelper.ResponseError('Workflow tags are disabled');
}
const { name } = req.body;
const { id } = req.params;
@ -87,18 +87,14 @@ tagsController.patch(
tagsController.delete(
'/:id',
workflowsEnabledMiddleware,
ResponseHelper.send(async (req: TagsRequest.Delete): Promise<boolean> => {
if (config.getEnv('workflowTagsDisabled')) {
throw new ResponseHelper.ResponseError('Workflow tags are disabled');
}
if (
config.getEnv('userManagement.isInstanceOwnerSetUp') === true &&
req.user.globalRole.name !== 'owner'
) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.UnauthorizedError(
'You are not allowed to perform this action',
undefined,
403,
'Only owners can remove tags',
);
}

View file

@ -391,11 +391,7 @@ export class Worker extends Command {
await connection.query('SELECT 1');
} catch (e) {
LoggerProxy.error('No Database connection!', e);
const error = new ResponseHelper.ResponseError(
'No Database connection!',
undefined,
503,
);
const error = new ResponseHelper.ServiceUnavailableError('No Database connection!');
return ResponseHelper.sendErrorResponse(res, error);
}
@ -406,11 +402,7 @@ export class Worker extends Command {
await Worker.jobQueue.client.ping();
} catch (e) {
LoggerProxy.error('No Redis connection!', e);
const error = new ResponseHelper.ResponseError(
'No Redis connection!',
undefined,
503,
);
const error = new ResponseHelper.ServiceUnavailableError('No Redis connection!');
return ResponseHelper.sendErrorResponse(res, error);
}

View file

@ -54,7 +54,7 @@ EECredentialsController.get(
const includeDecryptedData = req.query.includeData === 'true';
if (Number.isNaN(Number(credentialId))) {
throw new ResponseHelper.ResponseError(`Credential ID must be a number.`, undefined, 400);
throw new ResponseHelper.BadRequestError(`Credential ID must be a number.`);
}
let credential = (await EECredentials.get(
@ -63,17 +63,15 @@ EECredentialsController.get(
)) as CredentialsEntity & CredentialWithSharings;
if (!credential) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
'Could not load the credential. If you think this is an error, ask the owner to share it with you again',
undefined,
404,
);
}
const userSharing = credential.shared?.find((shared) => shared.user.id === req.user.id);
if (!userSharing && req.user.globalRole.name !== 'owner') {
throw new ResponseHelper.ResponseError(`Forbidden.`, undefined, 403);
throw new ResponseHelper.UnauthorizedError(`Forbidden.`);
}
credential = EECredentials.addOwnerAndSharings(credential);
@ -117,7 +115,7 @@ EECredentialsController.post(
if (!ownsCredential) {
const sharing = await EECredentials.getSharing(req.user, credentials.id);
if (!sharing) {
throw new ResponseHelper.ResponseError(`Forbidden`, undefined, 403);
throw new ResponseHelper.UnauthorizedError(`Forbidden`);
}
const decryptedData = await EECredentials.decrypt(encryptionKey, sharing.credentials);
@ -144,13 +142,13 @@ EECredentialsController.put(
!Array.isArray(shareWithIds) ||
!shareWithIds.every((userId) => typeof userId === 'string')
) {
throw new ResponseHelper.ResponseError('Bad request', undefined, 400);
throw new ResponseHelper.BadRequestError('Bad request');
}
const { ownsCredential, credential } = await EECredentials.isOwned(req.user, credentialId);
if (!ownsCredential || !credential) {
throw new ResponseHelper.ResponseError('Forbidden', undefined, 403);
throw new ResponseHelper.UnauthorizedError('Forbidden');
}
let amountRemoved: number | null = null;

View file

@ -75,16 +75,14 @@ credentialsController.get(
const includeDecryptedData = req.query.includeData === 'true';
if (Number.isNaN(Number(credentialId))) {
throw new ResponseHelper.ResponseError(`Credential ID must be a number.`, undefined, 400);
throw new ResponseHelper.BadRequestError(`Credential ID must be a number.`);
}
const sharing = await CredentialsService.getSharing(req.user, credentialId, ['credentials']);
if (!sharing) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
`Credential with ID "${credentialId}" could not be found.`,
undefined,
404,
);
}
@ -159,10 +157,8 @@ credentialsController.patch(
credentialId,
userId: req.user.id,
});
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
'Credential to be updated not found. You can only update credentials owned by you',
undefined,
404,
);
}
@ -183,10 +179,8 @@ credentialsController.patch(
const responseData = await CredentialsService.update(credentialId, newCredentialData);
if (responseData === undefined) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
`Credential ID "${credentialId}" could not be found to be updated.`,
undefined,
404,
);
}
@ -217,10 +211,8 @@ credentialsController.delete(
credentialId,
userId: req.user.id,
});
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
'Credential to be deleted not found. You can only removed credentials owned by you',
undefined,
404,
);
}

View file

@ -206,11 +206,7 @@ export class CredentialsService {
try {
return await UserSettings.getEncryptionKey();
} catch (error) {
throw new ResponseHelper.ResponseError(
RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY,
undefined,
500,
);
throw new ResponseHelper.InternalServerError(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY);
}
}

View file

@ -58,7 +58,7 @@ oauth2CredentialController.get(
const { id: credentialId } = req.query;
if (!credentialId) {
throw new ResponseHelper.ResponseError('Required credential ID is missing', undefined, 400);
throw new ResponseHelper.BadRequestError('Required credential ID is missing');
}
const credential = await getCredentialForUser(credentialId, req.user);
@ -68,14 +68,14 @@ oauth2CredentialController.get(
userId: req.user.id,
credentialId,
});
throw new ResponseHelper.ResponseError(RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL, undefined, 404);
throw new ResponseHelper.NotFoundError(RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL);
}
let encryptionKey: string;
try {
encryptionKey = await UserSettings.getEncryptionKey();
} catch (error) {
throw new ResponseHelper.ResponseError((error as Error).message, undefined, 500);
throw new ResponseHelper.InternalServerError((error as Error).message);
}
const mode: WorkflowExecuteMode = 'internal';
@ -173,12 +173,10 @@ oauth2CredentialController.get(
const { code, state: stateEncoded } = req.query;
if (!code || !stateEncoded) {
const errorResponse = new ResponseHelper.ResponseError(
const errorResponse = new ResponseHelper.ServiceUnavailableError(
`Insufficient parameters for OAuth2 callback. Received following query parameters: ${JSON.stringify(
req.query,
)}`,
undefined,
503,
);
return ResponseHelper.sendErrorResponse(res, errorResponse);
}
@ -190,10 +188,8 @@ oauth2CredentialController.get(
token: string;
};
} catch (error) {
const errorResponse = new ResponseHelper.ResponseError(
const errorResponse = new ResponseHelper.ServiceUnavailableError(
'Invalid state format returned',
undefined,
503,
);
return ResponseHelper.sendErrorResponse(res, errorResponse);
}
@ -205,10 +201,8 @@ oauth2CredentialController.get(
userId: req.user?.id,
credentialId: state.cid,
});
const errorResponse = new ResponseHelper.ResponseError(
const errorResponse = new ResponseHelper.NotFoundError(
RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL,
undefined,
404,
);
return ResponseHelper.sendErrorResponse(res, errorResponse);
}
@ -217,11 +211,7 @@ oauth2CredentialController.get(
try {
encryptionKey = await UserSettings.getEncryptionKey();
} catch (error) {
throw new ResponseHelper.ResponseError(
(error as IDataObject).message as string,
undefined,
500,
);
throw new ResponseHelper.InternalServerError((error as IDataObject).message as string);
}
const mode: WorkflowExecuteMode = 'internal';
@ -250,10 +240,8 @@ oauth2CredentialController.get(
userId: req.user?.id,
credentialId: state.cid,
});
const errorResponse = new ResponseHelper.ResponseError(
const errorResponse = new ResponseHelper.NotFoundError(
'The OAuth2 callback state is invalid!',
undefined,
404,
);
return ResponseHelper.sendErrorResponse(res, errorResponse);
}
@ -299,11 +287,7 @@ oauth2CredentialController.get(
userId: req.user?.id,
credentialId: state.cid,
});
const errorResponse = new ResponseHelper.ResponseError(
'Unable to get access tokens!',
undefined,
404,
);
const errorResponse = new ResponseHelper.NotFoundError('Unable to get access tokens!');
return ResponseHelper.sendErrorResponse(res, errorResponse);
}

View file

@ -46,13 +46,13 @@ EEWorkflowController.put(
!Array.isArray(shareWithIds) ||
!shareWithIds.every((userId) => typeof userId === 'string')
) {
throw new ResponseHelper.ResponseError('Bad request', undefined, 400);
throw new ResponseHelper.BadRequestError('Bad request');
}
const { ownsWorkflow, workflow } = await EEWorkflows.isOwned(req.user, workflowId);
if (!ownsWorkflow || !workflow) {
throw new ResponseHelper.ResponseError('Forbidden', undefined, 403);
throw new ResponseHelper.UnauthorizedError('Forbidden');
}
let newShareeIds: string[] = [];
@ -86,20 +86,14 @@ EEWorkflowController.get(
);
if (!workflow) {
throw new ResponseHelper.ResponseError(
`Workflow with ID "${workflowId}" does not exist`,
undefined,
404,
);
throw new ResponseHelper.NotFoundError(`Workflow with ID "${workflowId}" does not exist`);
}
const userSharing = workflow.shared?.find((shared) => shared.user.id === req.user.id);
if (!userSharing && req.user.globalRole.name !== 'owner') {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.UnauthorizedError(
'It looks like you cannot access this workflow. Ask the owner to share it with you.',
undefined,
403,
);
}
@ -143,10 +137,8 @@ EEWorkflowController.post(
try {
EEWorkflows.validateCredentialPermissionsToUser(newWorkflow, allCredentials);
} catch (error) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
'The workflow you are trying to save contains credentials that are not shared with you',
undefined,
400,
);
}
@ -173,7 +165,7 @@ EEWorkflowController.post(
if (!savedWorkflow) {
LoggerProxy.error('Failed to create workflow', { userId: req.user.id });
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.InternalServerError(
'An error occurred while saving your workflow. Please try again.',
);
}

View file

@ -91,7 +91,7 @@ workflowsController.post(
if (!savedWorkflow) {
LoggerProxy.error('Failed to create workflow', { userId: req.user.id });
throw new ResponseHelper.ResponseError('Failed to save workflow');
throw new ResponseHelper.InternalServerError('Failed to save workflow');
}
if (tagIds && !config.getEnv('workflowTagsDisabled') && savedWorkflow.tags) {
@ -152,13 +152,11 @@ workflowsController.get(
`/from-url`,
ResponseHelper.send(async (req: express.Request): Promise<IWorkflowResponse> => {
if (req.query.url === undefined) {
throw new ResponseHelper.ResponseError(`The parameter "url" is missing!`, undefined, 400);
throw new ResponseHelper.BadRequestError(`The parameter "url" is missing!`);
}
if (!/^http[s]?:\/\/.*\.json$/i.exec(req.query.url as string)) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
`The parameter "url" is not valid! It does not seem to be a URL pointing to a n8n workflow JSON file.`,
undefined,
400,
);
}
let workflowData: IWorkflowResponse | undefined;
@ -166,11 +164,7 @@ workflowsController.get(
const { data } = await axios.get<IWorkflowResponse>(req.query.url as string);
workflowData = data;
} catch (error) {
throw new ResponseHelper.ResponseError(
`The URL does not point to valid JSON file!`,
undefined,
400,
);
throw new ResponseHelper.BadRequestError(`The URL does not point to valid JSON file!`);
}
// Do a very basic check if it is really a n8n-workflow-json
@ -182,10 +176,8 @@ workflowsController.get(
typeof workflowData.connections !== 'object' ||
Array.isArray(workflowData.connections)
) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
`The data in the file does not seem to be a n8n workflow JSON file!`,
undefined,
400,
);
}
@ -222,10 +214,8 @@ workflowsController.get(
workflowId,
userId: req.user.id,
});
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
'Could not load the workflow - you can only access workflows owned by you',
undefined,
404,
);
}
@ -297,10 +287,8 @@ workflowsController.delete(
workflowId,
userId: req.user.id,
});
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
'Could not delete the workflow - you can only remove workflows owned by you',
undefined,
400,
);
}

View file

@ -168,7 +168,7 @@ export class EEWorkflowsService extends WorkflowsService {
const previousVersion = await EEWorkflowsService.get({ id: parseInt(workflowId, 10) });
if (!previousVersion) {
throw new ResponseHelper.ResponseError('Workflow not found', undefined, 404);
throw new ResponseHelper.NotFoundError('Workflow not found');
}
const allCredentials = await EECredentials.getAll(user);
@ -180,10 +180,8 @@ export class EEWorkflowsService extends WorkflowsService {
allCredentials,
);
} catch (error) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
'Invalid workflow credentials - make sure you have access to all credentials and try again.',
undefined,
400,
);
}
}

View file

@ -124,10 +124,8 @@ export class WorkflowsService {
userId: user.id,
filter,
});
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.InternalServerError(
`Parameter "filter" contained invalid JSON string.`,
500,
500,
);
}
}
@ -196,18 +194,14 @@ export class WorkflowsService {
workflowId,
userId: user.id,
});
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.NotFoundError(
'You do not have permission to update this workflow. Ask the owner to share it with you.',
undefined,
404,
);
}
if (!forceSave && workflow.hash !== '' && workflow.hash !== shared.workflow.hash) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
'We are sorry, but the workflow has been changed in the meantime. Please reload the workflow and try again.',
undefined,
400,
);
}
@ -290,10 +284,8 @@ export class WorkflowsService {
const updatedWorkflow = await Db.collections.Workflow.findOne(workflowId, options);
if (updatedWorkflow === undefined) {
throw new ResponseHelper.ResponseError(
throw new ResponseHelper.BadRequestError(
`Workflow with ID "${workflowId}" could not be found to be updated.`,
undefined,
400,
);
}

View file

@ -11,10 +11,9 @@ interface ErrorReporter {
report: (error: Error | string, options?: ReportingOptions) => void;
}
const isProduction = process.env.NODE_ENV === 'production';
const instance: ErrorReporter = {
report: (error, options) => isProduction && Logger.error('ERROR', { error, options }),
report: (error) =>
error instanceof Error && Logger.error(`${error.constructor.name}: ${error.message}`),
};
export function init(errorReporter: ErrorReporter) {

View file

@ -183,6 +183,7 @@ importers:
passport-cookie: ^1.0.9
passport-jwt: ^4.0.0
pg: ^8.3.0
picocolors: ^1.0.0
posthog-node: ^1.3.0
prom-client: ^13.1.0
psl: ^1.8.0
@ -263,6 +264,7 @@ importers:
passport-cookie: 1.0.9
passport-jwt: 4.0.0
pg: 8.8.0
picocolors: 1.0.0
posthog-node: 1.3.0
prom-client: 13.2.0
psl: 1.9.0