- POST /api/documents/token issues JWT HS256 (claims userid/username/fid/template) after x-api-key check against new env DOCVER_API_KEY (fail-closed, timing-safe, uniform 401) - all docver endpoints now require Authorization: Bearer, verified with env DOCVER_TOKEN_SECRET (TTL via DOCVER_TOKEN_TTL_SECONDS, default 3600); secret unset = 401 on verify / 500 on issue - identity (userId/userName) + templateId extracted from token claims instead of request payload; removed from multipart form (legacy fields now 400) - lib/docver-token.ts: hand-rolled JWT via node:crypto only — alg pinned to HS256 (anti alg-confusion), timing-safe compare with both sides hashed, exp enforced, identical 401 for all bearer failure modes - openapi spec: /api/documents/token path + DocverBearer scheme; PLAN-DOCVER.md / CLAUDE.md / .env.example updated - tests: new tests/lib/docver-token.test.ts + adjusted parseVersionUploadForm tests (258 pass, no DB env needed) - package-lock.json: sync minio (^8.0.7) that was missing from the earlier docver commit Co-Authored-By: Claude Code <noreply@anthropic.com>
47 lines
2.2 KiB
TypeScript
47 lines
2.2 KiB
TypeScript
// 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();
|
|
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" } },
|
|
);
|
|
}
|