forked from FoundKeyGang/FoundKey
server: refactor API error
This commit is contained in:
parent
c411669133
commit
bd68096ea9
3 changed files with 33 additions and 40 deletions
|
@ -12,21 +12,6 @@ export async function handler(endpoint: IEndpoint, ctx: Koa.Context): Promise<vo
|
|||
? ctx.query
|
||||
: ctx.request.body;
|
||||
|
||||
const 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: 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<vo
|
|||
ctx.body = typeof res === 'string' ? JSON.stringify(res) : res;
|
||||
}
|
||||
}).catch((e: ApiError) => {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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<string, { message: string, httpStatusCode: number }> = {
|
||||
|
|
|
@ -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<string, any>): 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) {
|
||||
|
|
Loading…
Reference in a new issue