// PLAN-DOCVER.md §auth — POST /api/documents/token ออก access_token ของกลุ่ม docver // ยืนยันด้วย header "x-api-key" เทียบ env DOCVER_API_KEY (fail closed เมื่อ env ไม่ // ตั้ง/ว่าง) — token เป็น JWT HS256 เซ็นด้วย env DOCVER_TOKEN_SECRET อายุตาม env // DOCVER_TOKEN_TTL_SECONDS (default 3600) แล้วนำไปแนบ Authorization: Bearer กับ // endpoint อื่นของกลุ่ม (lib/docver-token.ts) · segment คงที่ "token" ไม่ชน [docId] // (Next ให้ static ชนะ) และ middleware ยกเว้น api/documents/* อยู่แล้ว import { docverError } from "@/lib/document-versioning"; import { issueDocverToken, parseTokenRequestBody, requireDocverApiKey, } from "@/lib/docver-token"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; export async function POST(request: Request) { const apiKeyFail = requireDocverApiKey(request); if (apiKeyFail !== null) return apiKeyFail; let raw: unknown; try { raw = await request.json(); } catch { return docverError("body ต้องเป็น JSON", 400); } const parsed = parseTokenRequestBody(raw); if (!parsed.ok) return docverError(parsed.error, 400); // ความผิด config ฝั่ง server ไม่ใช่ความผิดของผู้เรียก — ไม่ตั้ง = 500 (ห้าม log ค่า secret) const secret = process.env.DOCVER_TOKEN_SECRET?.trim(); // const secret = // "487023dd1c1c4d6a07475e96b1cc300fec1e68ac9c2dd7d5ac9b1bf75da1053e"; if (secret === undefined || secret === "") { return docverError("DOCVER_TOKEN_SECRET ยังไม่ถูกตั้งค่า", 500); } const ttlRaw = Number(process.env.DOCVER_TOKEN_TTL_SECONDS); const ttlSeconds = Number.isFinite(ttlRaw) && ttlRaw > 0 ? Math.floor(ttlRaw) : 3600; const accessToken = issueDocverToken(parsed.claims, secret, ttlSeconds); return new Response( JSON.stringify({ access_token: accessToken, token_type: "Bearer", expires_in: ttlSeconds, }), { headers: { "content-type": "application/json; charset=utf-8" } }, ); }