feat(docver): derive docId from fid claim and drop [docId] URL segment
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 1m9s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 4s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 1m9s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 4s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s
- docId of every docver operation now comes from the Bearer token: the fid
claim (e-Court fileId) is used directly as docId; segment [docId] removed
from URLs — POST/GET /api/documents/versions and
GET /api/documents/versions/{versionId}/download-link
- fid is required and must be a string (trim 1-200): token issuance rejects
missing/blank/non-string fid with 400, verify rejects fid-less tokens with 401
- parseDocId removed from lib/document-versioning.ts (dead code, no callers)
- openapi spec: paths renamed, docId path params removed, fid required + string
- PLAN-DOCVER.md / CLAUDE.md / wordaddin versioningService comment updated
- tests: fid required/string cases added, parseDocId describe removed (256 pass)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
aad6f155fe
commit
0b2c9e6343
10 changed files with 122 additions and 158 deletions
|
|
@ -6,7 +6,6 @@ import {
|
|||
docverError,
|
||||
isDocxBytes,
|
||||
isPdfBytes,
|
||||
parseDocId,
|
||||
parseDownloadFormat,
|
||||
parseFieldsJson,
|
||||
parseVersionIdParam,
|
||||
|
|
@ -23,28 +22,6 @@ function ok<T>(result: { ok: boolean } & T): asserts result is { ok: true } & T
|
|||
expect(result.ok).toBe(true);
|
||||
}
|
||||
|
||||
describe("parseDocId", () => {
|
||||
it("รับ GUID / รหัส e-Court / charset ที่อนุญาตทั้งหมด และ trim รอบ ๆ", () => {
|
||||
for (const raw of [
|
||||
"123e4567-e89b-12d3-a456-426614174000",
|
||||
"ecourt:123",
|
||||
"DOC_2026.10",
|
||||
" doc1 ",
|
||||
"a".repeat(200),
|
||||
]) {
|
||||
const result = parseDocId(raw);
|
||||
ok(result);
|
||||
expect(result.docId).toBe(raw.trim());
|
||||
}
|
||||
});
|
||||
|
||||
it("ตกเมื่อว่าง / เกิน 200 / มีช่องว่างกลาง / ไทย / slash / '..'", () => {
|
||||
for (const raw of ["", " ", "a".repeat(201), "doc 1", "เอกสาร1", "a/b", "..", "a\\b"]) {
|
||||
expect(parseDocId(raw).ok).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseVersionIdParam", () => {
|
||||
it("รับ UUID ทั้งพิมพ์ใหญ่/เล็ก (normalize ลงตัวพิมพ์เล็ก) + trim", () => {
|
||||
const result = parseVersionIdParam(" 6F9619FF-8B86-D011-B42D-00C04FC964FF ");
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const SECRET = "test-secret-docver";
|
|||
const CLAIMS: DocverTokenClaims = {
|
||||
userid: "123",
|
||||
username: "Sorawit Bholsithi",
|
||||
fid: 3,
|
||||
fid: "3",
|
||||
template: "T12",
|
||||
};
|
||||
|
||||
|
|
@ -76,13 +76,21 @@ describe("issueDocverToken / verifyDocverToken", () => {
|
|||
expect(verified.claims).toEqual(CLAIMS);
|
||||
});
|
||||
|
||||
it("claims ทางเลือก null ทั้งคู่ (fid/template ไม่ส่ง) ก็ roundtrip ได้", () => {
|
||||
it("template เป็นทางเลือก (null ได้) แต่ fid บังคับ — token ไม่มี fid = ไม่ผ่าน", () => {
|
||||
const verified = verifyDocverToken(
|
||||
issueDocverToken({ userid: "u1", username: "ชื่อ", fid: null, template: null }, SECRET, 60),
|
||||
issueDocverToken({ userid: "u1", username: "ชื่อ", fid: "7", template: null }, SECRET, 60),
|
||||
SECRET,
|
||||
);
|
||||
ok(verified);
|
||||
expect(verified.claims).toEqual({ userid: "u1", username: "ชื่อ", fid: null, template: null });
|
||||
expect(verified.claims).toEqual({ userid: "u1", username: "ชื่อ", fid: "7", template: null });
|
||||
const noFid = signPayload({
|
||||
userid: "u1",
|
||||
username: "n",
|
||||
template: "T1",
|
||||
iat: 0,
|
||||
exp: Math.floor(Date.now() / 1000) + 60,
|
||||
});
|
||||
expect(verifyDocverToken(noFid, SECRET).ok).toBe(false);
|
||||
});
|
||||
|
||||
it("แก้ payload แล้วใช้ signature เดิม (tampered) หรือ secret ผิด = ไม่ผ่าน", () => {
|
||||
|
|
@ -119,10 +127,13 @@ describe("issueDocverToken / verifyDocverToken", () => {
|
|||
it("claims ไม่ครบรูปร่าง (แม้เซ็นถูก) = ไม่ผ่าน — กัน token เซ็นเองหลุดไปใช้", () => {
|
||||
const now = Math.floor(Date.now() / 1000) + 60;
|
||||
for (const bad of [
|
||||
{ username: "n", fid: null, template: null, iat: 0, exp: now }, // ไม่มี userid
|
||||
{ userid: "u", fid: null, template: null, iat: 0, exp: now }, // ไม่มี username
|
||||
{ userid: "", username: "n", fid: null, template: null, iat: 0, exp: now }, // userid ว่าง
|
||||
{ userid: "u", username: "n", fid: "3", template: null, iat: 0, exp: now }, // fid เป็น string
|
||||
{ username: "n", fid: "3", template: null, iat: 0, exp: now }, // ไม่มี userid
|
||||
{ userid: "u", fid: "3", template: null, iat: 0, exp: now }, // ไม่มี username
|
||||
{ userid: "", username: "n", fid: "3", template: null, iat: 0, exp: now }, // userid ว่าง
|
||||
{ userid: "u", username: "n", fid: 3, template: null, iat: 0, exp: now }, // fid เป็น number
|
||||
{ userid: "u", username: "n", fid: "", template: null, iat: 0, exp: now }, // fid ว่าง
|
||||
{ userid: "u", username: "n", template: null, iat: 0, exp: now }, // ไม่มี fid (บังคับแล้ว)
|
||||
{ userid: "u", username: "n", fid: null, template: null, iat: 0, exp: now }, // fid null
|
||||
]) {
|
||||
expect(verifyDocverToken(signPayload(bad), SECRET).ok).toBe(false);
|
||||
}
|
||||
|
|
@ -224,23 +235,23 @@ describe("requireDocverBearer", () => {
|
|||
});
|
||||
|
||||
describe("parseTokenRequestBody", () => {
|
||||
it("ครบทุก field = ได้ claims (trim ค่า) · fid/template ไม่ส่ง = null", () => {
|
||||
it("ครบทุก field = ได้ claims (trim ค่า) · template ไม่ส่ง = null (fid บังคับ)", () => {
|
||||
const full = parseTokenRequestBody({
|
||||
userid: " 123 ",
|
||||
username: " Sorawit Bholsithi ",
|
||||
fid: 3,
|
||||
fid: " 3 ",
|
||||
template: " T12 ",
|
||||
});
|
||||
ok(full);
|
||||
expect(full.claims).toEqual({ userid: "123", username: "Sorawit Bholsithi", fid: 3, template: "T12" });
|
||||
expect(full.claims).toEqual({ userid: "123", username: "Sorawit Bholsithi", fid: "3", template: "T12" });
|
||||
|
||||
const minimal = parseTokenRequestBody({ userid: "123", username: "Sorawit" });
|
||||
const minimal = parseTokenRequestBody({ userid: "123", username: "Sorawit", fid: "3" });
|
||||
ok(minimal);
|
||||
expect(minimal.claims).toEqual({ userid: "123", username: "Sorawit", fid: null, template: null });
|
||||
expect(minimal.claims).toEqual({ userid: "123", username: "Sorawit", fid: "3", template: null });
|
||||
});
|
||||
|
||||
it("template ส่งมาเป็นช่องว่าง = null (ถือว่าไม่ส่ง)", () => {
|
||||
const result = parseTokenRequestBody({ userid: "123", username: "n", template: " " });
|
||||
const result = parseTokenRequestBody({ userid: "123", username: "n", fid: "3", template: " " });
|
||||
ok(result);
|
||||
expect(result.claims.template).toBeNull();
|
||||
});
|
||||
|
|
@ -275,21 +286,29 @@ describe("parseTokenRequestBody", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("fid ไม่ใช่ integer (string / ทศนิยม) หรือ template ไม่ใช่ string = 400", () => {
|
||||
it("fid ขาด/null/ว่าง/ไม่ใช่ string (number) หรือ template ไม่ใช่ string = 400", () => {
|
||||
const missing = parseTokenRequestBody({ userid: "1", username: "n" });
|
||||
expect(missing.ok).toBe(false);
|
||||
if (!missing.ok) expect(missing.error).toContain("fid");
|
||||
for (const raw of [
|
||||
{ userid: "1", username: "n", fid: "3" },
|
||||
{ userid: "1", username: "n", fid: 3.5 },
|
||||
{ userid: "1", username: "n", fid: null },
|
||||
{ userid: "1", username: "n", fid: 3 },
|
||||
{ userid: "1", username: "n", fid: "" },
|
||||
{ userid: "1", username: "n", fid: " " },
|
||||
{ userid: "1", username: "n", template: 12 },
|
||||
]) {
|
||||
expect(parseTokenRequestBody(raw).ok).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it("ความยาวเกิน cap: userid > 100 / username > 200 / template > 100 = 400", () => {
|
||||
it("ความยาวเกิน cap: userid > 100 / username > 200 / fid > 200 / template > 100 = 400", () => {
|
||||
expect(parseTokenRequestBody({ userid: "a".repeat(101), username: "n" }).ok).toBe(false);
|
||||
expect(
|
||||
parseTokenRequestBody({ userid: "1", username: "a".repeat(201) }).ok,
|
||||
).toBe(false);
|
||||
expect(
|
||||
parseTokenRequestBody({ userid: "1", username: "n", fid: "a".repeat(201) }).ok,
|
||||
).toBe(false);
|
||||
expect(
|
||||
parseTokenRequestBody({ userid: "1", username: "n", template: "a".repeat(101) }).ok,
|
||||
).toBe(false);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue