diff --git a/packages/backend/src/server/api/api-handler.ts b/packages/backend/src/server/api/api-handler.ts index 30a8dbcf5..bc68483c9 100644 --- a/packages/backend/src/server/api/api-handler.ts +++ b/packages/backend/src/server/api/api-handler.ts @@ -12,21 +12,6 @@ export async function handler(endpoint: IEndpoint, ctx: Koa.Context): Promise { - ctx.status = e.httpStatusCode; - if (e.httpStatusCode === 401) { - ctx.response.set('WWW-Authenticate', 'Bearer'); - } - ctx.body = { - error: { - message: e!.message, - code: e!.code, - ...(e!.info ? { info: e!.info } : {}), - endpoint: endpoint.name, - }, - }; - }; - // Authentication // for GET requests, do not even pass on the body parameter as it is considered unsafe await authenticate(ctx.headers.authorization, ctx.method === 'GET' ? null : body['i']).then(async ([user, app]) => { @@ -43,13 +28,13 @@ export async function handler(endpoint: IEndpoint, ctx: Koa.Context): Promise { - error(e); + e.apply(ctx, endpoint.name); }); }).catch(e => { if (e instanceof AuthenticationError) { - error(new ApiError('AUTHENTICATION_FAILED', e.message)); + new ApiError('AUTHENTICATION_FAILED', e.message).apply(ctx, endpoint.name); } else { - error(new ApiError()); + new ApiError().apply(ctx, endpoint.name); } }); } diff --git a/packages/backend/src/server/api/error.ts b/packages/backend/src/server/api/error.ts index 9323f8919..ddd7a3d0d 100644 --- a/packages/backend/src/server/api/error.ts +++ b/packages/backend/src/server/api/error.ts @@ -1,3 +1,5 @@ +import Koa from 'koa'; + export class ApiError extends Error { public message: string; public code: string; @@ -20,6 +22,24 @@ export class ApiError extends Error { this.message = message; this.httpStatusCode = httpStatusCode; } + + /** + * Makes the response of ctx the current error, given the respective endpoint name. + */ + public apply(ctx: Koa.Context, endpoint: string): void { + ctx.status = this.httpStatusCode; + if (ctx.status === 401) { + ctx.response.set('WWW-Authenticate', 'Bearer'); + } + ctx.body = { + error: { + message: this.message, + code: this.code, + info: this.info ?? undefined, + endpoint, + }, + }; + } } export const errors: Record = { diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts index 9888a0c10..f5cc7af08 100644 --- a/packages/backend/src/server/api/private/signin.ts +++ b/packages/backend/src/server/api/private/signin.ts @@ -12,7 +12,7 @@ import { getIpHash } from '@/misc/get-ip-hash.js'; import signin from '../common/signin.js'; import { verifyLogin, hash } from '../2fa.js'; import { limiter } from '../limiter.js'; -import { ApiError } from '../error.js'; +import { ApiError, errors } from '../error.js'; export default async (ctx: Koa.Context) => { ctx.set('Access-Control-Allow-Origin', config.url); @@ -21,42 +21,30 @@ export default async (ctx: Koa.Context) => { const body = ctx.request.body as any; const { username, password, token } = body; - // taken from @server/api/api-handler.ts - function error (e: ApiError): void { - ctx.status = e.httpStatusCode; - if (e.httpStatusCode === 401) { - ctx.response.set('WWW-Authenticate', 'Bearer'); - } - ctx.body = { - error: { - message: e!.message, - code: e!.code, - ...(e!.info ? { info: e!.info } : {}), - endpoint: 'signin', - }, - }; + function error(e: keyof errors, info?: Record): void { + new ApiError(e, info).apply(ctx, 'signin'); } try { // not more than 1 attempt per second and not more than 10 attempts per hour await limiter({ key: 'signin', duration: HOUR, max: 10, minInterval: SECOND }, getIpHash(ctx.ip)); } catch (err) { - error(new ApiError('RATE_LIMIT_EXCEEDED')); + error('RATE_LIMIT_EXCEEDED'); return; } if (typeof username !== 'string') { - error(new ApiError('INVALID_PARAM', { param: 'username', reason: 'not a string' })); + error('INVALID_PARAM', { param: 'username', reason: 'not a string' }); return; } if (typeof password !== 'string') { - error(new ApiError('INVALID_PARAM', { param: 'password', reason: 'not a string' })); + error('INVALID_PARAM', { param: 'password', reason: 'not a string' }); return; } if (token != null && typeof token !== 'string') { - error(new ApiError('INVALID_PARAM', { param: 'token', reason: 'provided but not a string' })); + error('INVALID_PARAM', { param: 'token', reason: 'provided but not a string' }); return; } @@ -67,12 +55,12 @@ export default async (ctx: Koa.Context) => { }) as ILocalUser; if (user == null) { - error(new ApiError('NO_SUCH_USER')); + error('NO_SUCH_USER'); return; } if (user.isSuspended) { - error(new ApiError('SUSPENDED')); + error('SUSPENDED'); return; } @@ -92,7 +80,7 @@ export default async (ctx: Koa.Context) => { success: false, }); - error(new ApiError('ACCESS_DENIED')); + error('ACCESS_DENIED'); } if (!profile.twoFactorEnabled) {