import type Koa from "koa"; import type { User } from "@/models/entities/user.js"; import { UserIps } from "@/models/index.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; import type { IEndpoint } from "./endpoints.js"; import authenticate, { AuthenticationError } from "./authenticate.js"; import call from "./call.js"; import { ApiError } from "./error.js"; const userIpHistories = new Map>(); setInterval(() => { userIpHistories.clear(); }, 1000 * 60 * 60); export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise((res) => { const body = ctx.is("multipart/form-data") ? (ctx.request as any).body : ctx.method === "GET" ? ctx.query : ctx.request.body; const reply = (x?: any, y?: ApiError) => { if (x == null) { ctx.status = 204; } else if (typeof x === "number" && y) { 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(); }; // Authentication // 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]) => { // API invoking call(endpoint.name, user, app, body, ctx) .then((res: any) => { if ( ctx.method === "GET" && endpoint.meta.cacheSec && !body["i"] && !user ) { ctx.set( "Cache-Control", `public, max-age=${endpoint.meta.cacheSec}`, ); } reply(res); }) .catch((e: ApiError) => { reply( e.httpStatusCode ? e.httpStatusCode : e.kind === "client" ? 400 : 500, e, ); }); // Log IP if (user) { fetchMeta().then((meta) => { if (!meta.enableIpLogging) return; const ip = ctx.ip; const ips = userIpHistories.get(user.id); if (ips == null || !ips.has(ip)) { if (ips == null) { userIpHistories.set(user.id, new Set([ip])); } else { ips.add(ip); } try { UserIps.createQueryBuilder() .insert() .values({ createdAt: new Date(), userId: user.id, ip: ip, }) .orIgnore(true) .execute(); } catch {} } }); } }) .catch((e) => { if (e instanceof AuthenticationError) { ctx.response.status = 403; ctx.response.set("WWW-Authenticate", "Bearer"); ctx.response.body = { message: `Authentication failed: ${e.message}`, code: "AUTHENTICATION_FAILED", id: "b0a7f5f8-dc2f-4171-b91f-de88ad238e14", kind: "client", }; res(); } else { reply(500, new ApiError()); } }); });