UNPKG

hfs

Version:
79 lines (78 loc) 3.73 kB
"use strict"; // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ApiError = void 0; exports.apiMiddleware = apiMiddleware; const sse_1 = __importDefault(require("./sse")); const stream_1 = require("stream"); const misc_1 = require("./misc"); const const_1 = require("./const"); const config_1 = require("./config"); const plugins_1 = require("./plugins"); class ApiError extends Error { constructor(status, message) { super(typeof message === 'string' ? message : message && message instanceof Error ? message.message : JSON.stringify(message)); this.status = status; } } exports.ApiError = ApiError; const logApi = (0, config_1.defineConfig)(misc_1.CFG.log_api, true); function apiMiddleware(apis) { return async (ctx) => { var _a; if (!logApi.get()) ctx.state.dontLog = true; const isPost = ctx.state.params; const params = isPost ? ctx.state.params || {} : ctx.query; const apiName = ctx.path; console.debug('API', ctx.method, apiName, { ...params }); const noBrowser = (_a = ctx.get('user-agent')) === null || _a === void 0 ? void 0 : _a.startsWith('curl'); const safe = noBrowser || isPost && ctx.get('x-hfs-anti-csrf') // POST is safe because browser will enforce SameSite cookie || apiName.startsWith('get_'); // "get_" apis are safe because they make no change if (!safe) return send(const_1.HTTP_FOOL, "missing header x-hfs-anti-csrf=1"); const customApiRest = apiName.startsWith(const_1.PLUGIN_CUSTOM_REST_PREFIX) && apiName.slice(const_1.PLUGIN_CUSTOM_REST_PREFIX.length); const apiFun = customApiRest && (0, plugins_1.firstPlugin)(pl => { var _a; return (_a = pl.getData().customRest) === null || _a === void 0 ? void 0 : _a[customApiRest]; }) || apis.hasOwnProperty(apiName) && apis[apiName]; if (!apiFun) return send(const_1.HTTP_BAD_REQUEST, 'invalid api'); // we don't rely on SameSite cookie option because it's https-only let res; try { res = await apiFun(params, ctx); if (res === null) return; } catch (e) { if (typeof e === 'string') // message meant to be transmitted return send(const_1.HTTP_BAD_REQUEST, e); if (typeof e === 'number') e = new ApiError(e); res = e; } if (isAsyncGenerator(res)) res = (0, misc_1.asyncGeneratorToReadable)(res); if (res instanceof stream_1.Readable) { // Readable, we'll go SSE-mode res.pipe((0, sse_1.default)(ctx)); const resAsReadable = res; // satisfy ts ctx.req.on('close', () => // by closing the generated stream, creator of the stream will know the request is over without having to access anything else resAsReadable.destroy()); return; } if (res instanceof ApiError) return send(res.status, res.message); if (res instanceof Error) // generic error/exception return send(const_1.HTTP_BAD_REQUEST, res.stack || res.message || String(res)); ctx.body = res; function send(status, body) { ctx.body = body; ctx.status = status; } }; } function isAsyncGenerator(x) { return typeof (x === null || x === void 0 ? void 0 : x.next) === 'function'; }