feat: add GET /api/documents/latest returning latest version file with stamped ticket
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 4s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 1m23s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 5s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 0s
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 4s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 1m23s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 5s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 0s
Server-to-server endpoint for the e-Court backend (x-api-key against DOCVER_API_KEY or EXTERNAL_API_KEY): - type=pdf returns the latest version's PDF bytes (404 when absent) - type=docx stamps fresh ticket custom properties (exp + HmacSha256 via addinTicketSecret(), Ed25519 when PRIVATE_KEY_HEX is set) into an in-memory copy before returning — MinIO object is never modified - docId comes from fid inside the ticket_data query param, which must include fid/uid/uname so the stamped file stays redeemable by the add-in at /api/documents/token (buildStampedTicketData drops null values because parseDocverTicket rejects tid:null) - adds parseTicketDataParam, parseLatestTypeParam, requireDocumentApiKey, findLatestVersionForDoc, getVersionObjectBytes + unit tests (320 pass) - syncs PLAN-DOCVER.md, OpenAPI spec and .env.example Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
5bc8d9a817
commit
0b6ee8cc84
12 changed files with 806 additions and 9 deletions
|
|
@ -4,6 +4,7 @@ import { afterEach, describe, expect, it } from "vitest";
|
|||
import {
|
||||
issueDocverToken,
|
||||
parseTokenRequestBody,
|
||||
requireDocumentApiKey,
|
||||
requireDocverApiKey,
|
||||
requireDocverBearer,
|
||||
verifyDocverToken,
|
||||
|
|
@ -30,12 +31,16 @@ function ok<T>(result: { ok: boolean } & T): asserts result is { ok: true } & T
|
|||
// --- env save/restore — require* อ่าน env ตอนเรียก ไม่ใช่ตอน module load
|
||||
const HAD_API_KEY = Object.prototype.hasOwnProperty.call(process.env, "DOCVER_API_KEY");
|
||||
const ORIG_API_KEY = process.env.DOCVER_API_KEY;
|
||||
const HAD_EXTERNAL_KEY = Object.prototype.hasOwnProperty.call(process.env, "EXTERNAL_API_KEY");
|
||||
const ORIG_EXTERNAL_KEY = process.env.EXTERNAL_API_KEY;
|
||||
const HAD_SECRET = Object.prototype.hasOwnProperty.call(process.env, "DOCVER_TOKEN_SECRET");
|
||||
const ORIG_SECRET = process.env.DOCVER_TOKEN_SECRET;
|
||||
|
||||
afterEach(() => {
|
||||
if (HAD_API_KEY) process.env.DOCVER_API_KEY = ORIG_API_KEY;
|
||||
else delete process.env.DOCVER_API_KEY;
|
||||
if (HAD_EXTERNAL_KEY) process.env.EXTERNAL_API_KEY = ORIG_EXTERNAL_KEY;
|
||||
else delete process.env.EXTERNAL_API_KEY;
|
||||
if (HAD_SECRET) process.env.DOCVER_TOKEN_SECRET = ORIG_SECRET;
|
||||
else delete process.env.DOCVER_TOKEN_SECRET;
|
||||
});
|
||||
|
|
@ -175,6 +180,59 @@ describe("requireDocverApiKey", () => {
|
|||
});
|
||||
});
|
||||
|
||||
// requireDocumentApiKey — auth ของ GET /api/documents/latest: x-api-key เทียบ
|
||||
// DOCVER_API_KEY หรือ EXTERNAL_API_KEY ตัวใดตัวหนึ่ง (ต่างจาก /token)
|
||||
describe("requireDocumentApiKey", () => {
|
||||
function setKeys(docver: string | undefined, external: string | undefined): void {
|
||||
if (docver === undefined) delete process.env.DOCVER_API_KEY;
|
||||
else process.env.DOCVER_API_KEY = docver;
|
||||
if (external === undefined) delete process.env.EXTERNAL_API_KEY;
|
||||
else process.env.EXTERNAL_API_KEY = external;
|
||||
}
|
||||
|
||||
it("ทั้งคู่ไม่ตั้ง / ว่าง / ช่องว่างล้วน = 401 fail closed ทุกกรณี", async () => {
|
||||
for (const [docver, external] of [
|
||||
[undefined, undefined],
|
||||
["", ""],
|
||||
[" ", " "],
|
||||
] as const) {
|
||||
setKeys(docver, external);
|
||||
const res = requireDocumentApiKey(reqWithKey("test-key"));
|
||||
expect(res?.status, `${docver}/${external}`).toBe(401);
|
||||
expect(await bodyOf(res!)).toEqual({ error: "ระบบยังไม่ได้ตั้งค่า API key" });
|
||||
}
|
||||
});
|
||||
|
||||
it("ตั้งอันเดียวตัวไหนก็ผ่านเมื่อ key ตรงตัวนั้น · ตั้งทั้งคู่ตรงอันที่สองก็ผ่าน", () => {
|
||||
setKeys("docver-key", undefined);
|
||||
expect(requireDocumentApiKey(reqWithKey("docver-key"))).toBeNull();
|
||||
expect(requireDocumentApiKey(reqWithKey("external-key"))?.status).toBe(401);
|
||||
|
||||
setKeys(undefined, "external-key");
|
||||
expect(requireDocumentApiKey(reqWithKey("external-key"))).toBeNull();
|
||||
expect(requireDocumentApiKey(reqWithKey("docver-key"))?.status).toBe(401);
|
||||
|
||||
setKeys("docver-key", "external-key");
|
||||
expect(requireDocumentApiKey(reqWithKey("external-key"))).toBeNull();
|
||||
});
|
||||
|
||||
it("ไม่ใส่ header กับ key ผิด ต้องได้ 401 ข้อความเดียวกันเป๊ะ (กัน enumerate)", async () => {
|
||||
setKeys("docver-key", "external-key");
|
||||
const missing = requireDocumentApiKey(reqWithKey());
|
||||
const wrong = requireDocumentApiKey(reqWithKey("not-any-key"));
|
||||
expect(missing?.status).toBe(401);
|
||||
expect(wrong?.status).toBe(401);
|
||||
expect(await bodyOf(missing!)).toEqual(await bodyOf(wrong!));
|
||||
});
|
||||
|
||||
it("env มี whitespace รอบ ๆ ก็ trim ให้เทียบได้ทั้งสองตัว", () => {
|
||||
setKeys(" docver-key ", undefined);
|
||||
expect(requireDocumentApiKey(reqWithKey("docver-key"))).toBeNull();
|
||||
setKeys(undefined, " external-key ");
|
||||
expect(requireDocumentApiKey(reqWithKey("external-key"))).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("requireDocverBearer", () => {
|
||||
it("ทุกกรณีพ้นสภาพ (ไม่มี header / ไม่ใช่ Bearer / token เน่า / หมดอายุ / secret ไม่ตั้ง) = 401 ข้อความเดียวกัน", async () => {
|
||||
const deniedBodies = new Set<string>();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue