import { getConfig } from "../common/config.js"; import { fail, sessionRoute } from "../common/http.js"; import { listKeys as defaultList, generateKey as defaultGenerate, revokeKey as defaultRevoke, } from "../modules/core/apiKey.js"; const requireUser = (fn) => sessionRoute("apikeys", fn); // Internal modules have no `app` row and issue no billable calls, so no key. const mintableModules = () => Object.entries(getConfig().appModules) .filter(([, m]) => !m?.internal) .map(([type]) => type); export function apiKeyHandlers({ listKeys = defaultList, generateKey = defaultGenerate, revokeKey = defaultRevoke, } = {}) { return { whoami: requireUser(async (req, res) => { res.json({ username: req.user.username, canViewJson: req.user.canViewJson, modules: mintableModules() }); }), list: requireUser(async (req, res) => { res.json(await listKeys(req.user.id)); }), create: requireUser(async (req, res) => { const { type, label, expiresTs } = req.body || {}; if (!mintableModules().includes(type)) { return fail(res, 400, "bad_request", "unknown module"); } if (expiresTs != null && (typeof expiresTs !== "string" || Number.isNaN(Date.parse(expiresTs)))) { return fail(res, 400, "bad_request", "invalid expiresTs (expected an ISO date string)"); } // Returns the raw key ONCE — the client must surface it immediately. res.json(await generateKey(req.user.id, type, { label: label || null, expiresTs: expiresTs || null })); }), revoke: requireUser(async (req, res) => { const id = parseInt(req.params.id, 10); if (!Number.isInteger(id)) return fail(res, 400, "bad_request", "invalid id"); const ok = await revokeKey(req.user.id, id); res.status(ok ? 200 : 404).json({ ok }); }), }; }