server: refactor API handler and returning errors

This refactors the API handler to not use default exports, be async
instead of constructing a promise and modify how errors are returned.
This commit is contained in:
Johann150 2022-10-23 13:34:37 +02:00
parent c3c7164dfb
commit 66d7b69377
Signed by untrusted user: Johann150
GPG key ID: 9EE6577A2A06F8F1
2 changed files with 30 additions and 33 deletions

View file

@ -5,59 +5,56 @@ import authenticate, { AuthenticationError } from './authenticate.js';
import call from './call.js'; import call from './call.js';
import { ApiError } from './error.js'; import { ApiError } from './error.js';
export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise<void>((res) => { export async function handler(endpoint: IEndpoint, ctx: Koa.Context): Promise<void> {
const body = ctx.is('multipart/form-data') const body = ctx.is('multipart/form-data')
? (ctx.request as any).body ? (ctx.request as any).body
: ctx.method === 'GET' : ctx.method === 'GET'
? ctx.query ? ctx.query
: ctx.request.body; : ctx.request.body;
const reply = (x?: any, y?: ApiError) => { const error = (e: ApiError): void => {
if (x == null) { ctx.status = e.httpStatusCode ?? 500;
ctx.status = 204; if (e.httpStatusCode === 401) {
} else if (typeof x === 'number' && y) { ctx.response.set('WWW-Authenticate', 'Bearer');
ctx.status = x;
ctx.body = {
error: {
message: y!.message,
code: y!.code,
id: y!.id,
kind: y!.kind,
...(y!.info ? { info: y!.info } : {}),
},
};
} else {
// 文字列を返す場合は、JSON.stringify通さないとJSONと認識されない
ctx.body = typeof x === 'string' ? JSON.stringify(x) : x;
} }
res(); ctx.body = {
}; error: {
message: e!.message,
code: e!.code,
...(e!.info ? { info: e!.info } : {}),
endpoint: endpoint.name,
},
};
}
// Authentication // Authentication
// for GET requests, do not even pass on the body parameter as it is considered unsafe // for GET requests, do not even pass on the body parameter as it is considered unsafe
authenticate(ctx.headers.authorization, ctx.method === 'GET' ? null : body['i']).then(([user, app]) => { await authenticate(ctx.headers.authorization, ctx.method === 'GET' ? null : body['i']).then(async ([user, app]) => {
// API invoking // API invoking
call(endpoint.name, user, app, body, ctx).then((res: any) => { await call(endpoint.name, user, app, body, ctx).then((res: any) => {
if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) { if (ctx.method === 'GET' && endpoint.meta.cacheSec && !body['i'] && !user) {
ctx.set('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`); ctx.set('Cache-Control', `public, max-age=${endpoint.meta.cacheSec}`);
} }
reply(res); if (res == null) {
ctx.status = 204;
} else {
ctx.status = 200;
// If a string is returned, it must be passed through JSON.stringify to be recognized as JSON.
ctx.body = typeof res === 'string' ? JSON.stringify(res) : res;
}
}).catch((e: ApiError) => { }).catch((e: ApiError) => {
reply(e.httpStatusCode ? e.httpStatusCode : e.kind === 'client' ? 400 : 500, e); error(e);
}); });
}).catch(e => { }).catch(e => {
if (e instanceof AuthenticationError) { if (e instanceof AuthenticationError) {
ctx.response.status = 403; error({
ctx.response.set('WWW-Authenticate', 'Bearer');
ctx.response.body = {
message: 'Authentication failed: ' + e.message, message: 'Authentication failed: ' + e.message,
code: 'AUTHENTICATION_FAILED', code: 'AUTHENTICATION_FAILED',
id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
kind: 'client', httpStatusCode: 401,
}; });
res();
} else { } else {
reply(500, new ApiError()); error(new ApiError());
} }
}); });
}); };

View file

@ -11,7 +11,7 @@ import cors from '@koa/cors';
import { Instances, AccessTokens, Users } from '@/models/index.js'; import { Instances, AccessTokens, Users } from '@/models/index.js';
import config from '@/config/index.js'; import config from '@/config/index.js';
import endpoints from './endpoints.js'; import endpoints from './endpoints.js';
import handler from './api-handler.js'; import { handler } from './api-handler.js';
import signup from './private/signup.js'; import signup from './private/signup.js';
import signin from './private/signin.js'; import signin from './private/signin.js';
import signupPending from './private/signup-pending.js'; import signupPending from './private/signup-pending.js';