Refactor ticket verification and installation scripts
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 1m17s
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

- Split `verifyAddinTicket` into two functions: `verifyAddinTicketHmac` (ignores expiration) and `verifyAddinTicket` (checks expiration).
- Add `hasVersionByUser` function to check if a document has been previously worked on by a user, allowing expired tickets to be accepted if the user has a history with the document.
- Update OpenAPI specification to reflect changes in ticket expiration handling.
- Introduce new scripts for installing and uninstalling the Word add-in on Windows and macOS, providing a streamlined process for sideloading the add-in.
- Enhance tests for ticket verification to cover new functionality regarding expired tickets and user history.
This commit is contained in:
Suphonchai Phoonsawat 2026-10-28 14:42:03 +07:00
parent 3726cb742f
commit b1449ab668
14 changed files with 727 additions and 75 deletions

View file

@ -3,9 +3,10 @@
// 1) e-Court server-to-server: header "x-api-key" เทียบ env DOCVER_API_KEY
// (fail closed เมื่อ env ไม่ตั้ง/ว่าง) + body claims ตรง ๆ (เดิมทุกอย่าง)
// 2) add-in ใน Word: ไม่มี x-api-key + body {ticket_data, HmacSha256} — server
// ตรวจ HMAC+exp ด้วย env ฝั่ง server (ADDIN_TICKET_SECRET → SECRET →
// ตรวจ HMAC ด้วย env ฝั่ง server (ADDIN_TICKET_SECRET → SECRET →
// "super_secret", lib/addin-ticket.ts) แล้วออก token จาก claims ของ ticket
// (secret ตัวเซ็น JWT ไม่มีทางไปอยู่ใน bundle ของ add-in)
// (secret ตัวเซ็น JWT ไม่มีทางไปอยู่ใน bundle ของ add-in) — ticket หมดอายุ
// ยังออกให้ต่อเมื่อ fid นี้เคยมี version โดย uid เดียวกัน (requirement ขั้น 7)
// ทั้งสองทางออก JWT HS256 เซ็นด้วย env DOCVER_TOKEN_SECRET อายุตาม env
// DOCVER_TOKEN_TTL_SECONDS (default 3600) แล้วนำไปแนบ Authorization: Bearer กับ
// endpoint อื่นของกลุ่ม (lib/docver-token.ts) · segment คงที่ "token" ไม่ชน [docId]
@ -15,8 +16,10 @@ import {
addinTicketSecret,
parseTicketTokenBody,
ticketToDocverClaims,
verifyAddinTicket,
verifyAddinTicketHmac,
} from "@/lib/addin-ticket";
import { hasVersionByUser } from "@/lib/docver-store";
import { isTicketExpired } from "@/utils/docver-ticket";
import {
issueDocverToken,
parseTokenRequestBody,
@ -66,8 +69,22 @@ async function postWithTicket(request: Request): Promise<Response> {
// invalid = รูป body ผิดจริง ๆ ให้ 400 ตาม convention กัน typo เงียบ ๆ
return docverError(parsed.error, parsed.kind === "invalid" ? 400 : 401);
}
const verified = verifyAddinTicket(parsed.ticketData, parsed.hmacHex, addinTicketSecret());
// ตรวจ HMAC ก่อนเสมอ (ตัวตนของผู้ถือไฟล์) — exp ไม่ใช่เกณฑ์ตัดสิทธิ์ ณ จุดนี้
const verified = verifyAddinTicketHmac(parsed.ticketData, parsed.hmacHex, addinTicketSecret());
if (!verified.ok) return docverError(TICKET_DENIED, 401);
// ticket หมดอายุ → ยังออก token ให้ต่อเมื่อ "เอกสารนี้เคยทำงานโดย uid เดียวกัน"
// (requirement ขั้น 7: กลับมาทำเอกสารเดิมผ่านได้แม้ ticket หมดอายุ — พิสูจน์จาก
// document_versions ของกลุ่มนี้เอง) · DB พัง = fail closed เป็น 401 เหมือนไม่มีสิทธิ์
if (isTicketExpired(verified.ticket.exp)) {
let workedBefore = false;
try {
workedBefore = await hasVersionByUser(verified.ticket.fid, verified.ticket.uid);
} catch {
workedBefore = false;
}
if (!workedBefore) return docverError(TICKET_DENIED, 401);
}
return issueToken(ticketToDocverClaims(verified.ticket));
}