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
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:
parent
3726cb742f
commit
b1449ab668
14 changed files with 727 additions and 75 deletions
|
|
@ -40,6 +40,7 @@ scripts/patch-pdf-worker.mjs ← postinstall: คัดลอก pdf.worker.min.
|
|||
scripts/copy-swagger-ui.mjs ← postinstall ตัวที่สอง: คัดลอก swagger-ui-dist (bundle+css) ไป public/note/swagger-ui/ สำหรับหน้า Swagger UI ที่ GET /api/swagger (intranet ไม่ใช้ CDN — หาไฟล์ไม่เจอหลังอัปเกรด = exit 1 ดัง ๆ เหมือน patch-pdf-worker; Docker build รัน script นี้ด้วย ดู docker/web.Dockerfile)
|
||||
scripts/fetch-office-js.mjs ← postinstall ตัวที่สาม: ดาวน์โหลด Office.js runtime **ฉบับ CDN ล่าสุด** (appsforoffice.microsoft.com — ชุดไฟล์เฉพาะ Word: loader + host files ราย platform + legacy 15.x + o15apptofilemappingtable/microsoftajax + office_strings.js ต่อ locale en-us/th-th บังคับ) ไป public/wordaddin/office-js/ ให้ taskpane โหลด same-origin — npm `@microsoft/office-js` เลิกอัปเดตที่ 1.1.110 (~2018) ไม่มี `Document.close`/enum `CloseBehavior` ที่ runtime ใหม่+`@types/office-js` มี; ไฟล์หลักหาย/ตรวจไม่เจอ `"Document.close"`+`CloseBehavior` ใน word-win32-16.01.js หลังโหลด = exit 1 ดัง ๆ (ไฟล์ปลายทาง gitignore — ดาวน์โหลดใหม่ทุกครั้ง; ติดตั้งต้องออกเน็ตถึง CDN ของ Microsoft ส่วน runtime ตัวระบบ intranet ล้วน)
|
||||
scripts/make-ticket-docx.mjs ← dev tool รันมือ (ไม่ใช่ postinstall): stamp ticket ขอ e-Court (`ticket_data` + `HmacSha256`) ลงไฟล์ .docx ใด ๆ ผ่าน pizzip เพื่อทดสอบ ticket auth ของ add-in ใน Word จริงโดยไม่ต้องผ่านกลุ่ม template (TMPL_PATH) — คำนวณ HMAC ตรงกับตัว stamp จริง (key เรียงตามตัวอักษรหลังเติม exp + เซ็นบน string ดิบ hex lowercase) · ตัวเลือก --fid/--uid/--uname/--tid/--exp <unix|+sec>/--secret/--tracked/--in-place/--list (PLAN-ADDIN-AUTH.md §9)
|
||||
scripts/install-wordaddin.{mjs,sh,ps1} ← dev tool ติดตั้ง/ถอด (sideload) Word add-in บนเครื่องทดสอบ Windows + macOS — 3 รูปแบบกลไกเดียวกัน (Windows = registry `Wef\Developer`, macOS = โฟลเดอร์ wef ของ Word — กลไกเดียวกับ office-addin-dev-settings): .mjs = ใน repo (`npm run addin:install`, zero-dependency), .sh (macOS) / .ps1 (Windows) = **ไฟล์ส่งต่อให้เครื่องที่ไม่มี Node/repo** (.ps1 ต้องมี BOM — PS 5.1 อ่าน UTF-8 ไร้ BOM เป็น ANSI) — โหลด manifest จาก server ปัจจุบัน (default `https://ecourt-note.chamomind.com/docedit/wordaddin/manifest.xml`, `--url`/`-Url` ชี้ dev ได้) + `--remove`/`-Remove` ถอด
|
||||
db/schema.ts ← ตาราง + JSONB types + DTO + mapper toDTO
|
||||
db/index.ts ← postgres.js + drizzle (DATABASE_URL → container judes-note-db, prepare:false)
|
||||
middleware.ts ← บังคับ login ทุกหน้า — ไม่มี cookie oacCase ที่ parse ได้ = redirect /note/login (path ที่ตัด basePath แล้ว: หน้าขึ้นต้น /note/… ส่วน API ขึ้นต้น /api/note/… — ยกเว้น note/login, api/note/*, api/template/* (กลุ่ม docx template เปิดกว้างให้ทางศาล/ผู้ทดสอบภายนอกยิงได้เลย — ไม่แตะ DB), api/documents/* (กลุ่ม docver จัด version เอกสาร — stand-alone ไม่มี session ใช้ auth ของกลุ่มเอง: ขอ access_token ผ่าน x-api-key แล้วแนบ Bearer ทุก endpoint, docs/PLAN-DOCVER.md), api/swagger/* (หน้า Swagger UI), wordaddin/* (taskpane ของ Word add-in — ไม่มี login: auth เป็น e-Court pass-through จาก token/owner ในไฟล์), note/uploads/*, note/samples, note/swagger-ui/*, note/pdf.worker.min.mjs, _next, fonts, favicon และ **root "/" เอง** (matcher ใช้ `.+` — root เป็น redirect สู่ /wordaddin ซึ่ง public ตามคำขอผู้ใช้ 2026-10-14, ลิงก์ e-Court N8 ที่ root ยังถูกคุมผ่าน /note ที่รับส่งต่อ))
|
||||
|
|
@ -53,12 +54,12 @@ lib/external-api.ts ← N10 ส่วนกลางของ route — parseQ
|
|||
lib/openapi.ts ← OpenAPI 3.0 spec ของ API ทดสอบ (กลุ่ม template + N10 + docver — 2026-10-06) — แหล่งเดียวของ Swagger UI: GET /api/template inline ลง HTML ตรง ๆ ไม่มี endpoint spec แยก; URL ใน paths เป็น path เต็ม /api/… (servers เติมแค่ basePath ตาม invariant #12) — แก้ contract ของ API ต้องมาแก้ไฟล์นี้ให้ตรงด้วย
|
||||
lib/document-versioning.ts ← docver pure validation (ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย — unit test tests/lib/document-versioning.test.ts ต้องรันได้เหมือน lib/external-api-auth.ts): parseVersionUploadForm (allowlist field — identity ไม่รับจาก form แล้ว แกะจาก token ผ่าน lib/docver-token.ts + fields JSON ≤1,000 keys **ความยาวค่าไม่จำกัด** (2026-10-27 เอกสารจริง 100–200 หน้า) + message ≤2048), isDocxBytes/isPdfBytes (magic bytes), parseDownloadFormat/parseVersionIdParam, contentDispositionValue (ASCII fallback + filename*=UTF-8'') + docverError สร้าง Response JSON {"error": ไทย} ใช้ร่วมทุก route ของกลุ่ม (parseDocId ถอดแล้ว 2026-10-26 — docId ไม่มาจาก path param)
|
||||
lib/docver-token.ts ← docver token + auth ของกลุ่ม /api/documents/* (2026-10-22 — pure module ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย เหมือน lib/document-versioning.ts; unit test tests/lib/docver-token.test.ts ต้องรันได้โดยไม่ต้องตั้ง env)
|
||||
lib/addin-ticket.ts ← add-in ticket auth ฝั่ง server ของทางเข้าที่สองของ POST /api/documents/token (2026-10-27, docs/PLAN-ADDIN-AUTH.md — pure เหมือน lib/docver-token.ts; unit test tests/lib/addin-ticket.test.ts): addinTicketSecret (env ADDIN_TICKET_SECRET → SECRET → "super_secret") + verifyAddinTicket (HMAC timing-safe บน ticket_data ดิบ + exp ผ่าน utils/docver-ticket.ts) + ticketToDocverClaims (uid→userid, uname→username, tid→template) + parseTicketTokenBody (allowlist ticket_data/HmacSha256 — missing → 401 / invalid → 400): issueDocverToken/verifyDocverToken (JWT HS256 มือเขียน node:crypto ล้วน — บังคับ alg=HS256 กัน alg-confusion, timingSafeEqual หลัง hash ทั้งสองฝั่ง, exp บังคับ) + requireDocverApiKey (header x-api-key เทียบ env DOCVER_API_KEY — fail closed 401 ไม่ตั้ง/ว่าง, 401 เดียวกันทั้งไม่ใส่/ผิด กัน enumerate, ห้าม log ค่า key/header ดิบ) + requireDocverBearer (Authorization: Bearer → claims {userid, username, fid, template} — secret จาก env DOCVER_TOKEN_SECRET ไม่ตั้ง = 401, ทุกกรณี 401 ข้อความเดียวกัน) + parseTokenRequestBody (allowlist userid/username/fid/template — **fid = fileId ของ e-Court (string, บังคับ 2026-10-26)** เป็น docId ของทุก operation ตรง ๆ · verify ไม่รับ token ที่ไม่มี fid)
|
||||
lib/addin-ticket.ts ← add-in ticket auth ฝั่ง server ของทางเข้าที่สองของ POST /api/documents/token (2026-10-27, docs/PLAN-ADDIN-AUTH.md — pure เหมือน lib/docver-token.ts; unit test tests/lib/addin-ticket.test.ts): addinTicketSecret (env ADDIN_TICKET_SECRET → SECRET → "super_secret") + verifyAddinTicketHmac (HMAC timing-safe บน ticket_data ดิบ + parse **ไม่ตรวจ exp** — route ทางเข้า ticket ใช้ตัวนี้แล้วตัดสิน exp เอง: ticket หมดอายุยังออก token ต่อเมื่อ fid นั้นเคยมี version โดย uid เดียวกัน ผ่าน `hasVersionByUser` ของ lib/docver-store.ts — requirement ขั้น 7 แก้บั๊ก 2026-10-28, DB พัง = fail closed 401) + verifyAddinTicket (HMAC+exp เต็มชุด — ผู้เรียกเดิม) + ticketToDocverClaims (uid→userid, uname→username, tid→template) + parseTicketTokenBody (allowlist ticket_data/HmacSha256 — missing → 401 / invalid → 400): issueDocverToken/verifyDocverToken (JWT HS256 มือเขียน node:crypto ล้วน — บังคับ alg=HS256 กัน alg-confusion, timingSafeEqual หลัง hash ทั้งสองฝั่ง, exp บังคับ) + requireDocverApiKey (header x-api-key เทียบ env DOCVER_API_KEY — fail closed 401 ไม่ตั้ง/ว่าง, 401 เดียวกันทั้งไม่ใส่/ผิด กัน enumerate, ห้าม log ค่า key/header ดิบ) + requireDocverBearer (Authorization: Bearer → claims {userid, username, fid, template} — secret จาก env DOCVER_TOKEN_SECRET ไม่ตั้ง = 401, ทุกกรณี 401 ข้อความเดียวกัน) + parseTokenRequestBody (allowlist userid/username/fid/template — **fid = fileId ของ e-Court (string, บังคับ 2026-10-26)** เป็น docId ของทุก operation ตรง ๆ · verify ไม่รับ token ที่ไม่มี fid)
|
||||
lib/docver-store.ts ← docver server-side MinIO + DB (env MINIO_* อ่านตอนเรียกไม่ใช่ module load; client + cached ensureBucket เก็บบน globalThis แบบ db/index.ts): object key `docver/<versionId>/document.docx|pdf` (uuid ล้วน ไม่มี input ผู้ใช้), putVersionObjects (docx ก่อน — pdf ล้ม = ลบ docx คืนแล้วโยนต่อ), removeVersionObjects (best-effort), presignVersionUrl (TTL env DOCVER_LINK_TTL_SECONDS), insertVersionRecord (**tx เดียว**: upsert documents ON CONFLICT DO NOTHING → SELECT id FOR UPDATE → max(versionNumber)+1 → insert — จ่ายเลข version ปลอดภัยเรียกคู่ขนาน, UNIQUE(document_id, version_number) เป็น safety net)
|
||||
wordaddin/ ← **client-only zone ของ Word add-in** (ห้าม import จาก lib/ หรือถูก import โดย server component — พอร์ตดิบจาก D:\word-addin-poc\addin\src, spec: docs/addin-ui-flow.md, คู่มือ dev: docs/WORDADDIN-DEV.md)
|
||||
wordaddin/config.ts ← env NEXT_PUBLIC_ADDIN_API_BASE_URL ("" = ยังไม่ต่อ backend จริง) + NEXT_PUBLIC_ADDIN_MOCK_USERNAME (ชื่อ fallback ของ ?addindemo/ไฟล์ที่มี token ไม่มี owner)
|
||||
wordaddin/models/versionRecord.ts ← types VersionRecord/FieldsSnapshot + readSnapshotField (พอร์ตดิบ)
|
||||
wordaddin/services/ ← documentService/fileService/metadataService/autoSaveService/versionFlow/apiClient/errors พอร์ตดิบทั้งไฟล์ + **templateService** (แท็บที่ 2 "เครื่องมือ Template" 2026-10-19 — list/select/applyTestValues + แทรกย่อหน้า-ตัวแบ่งบรรทัด-ตัวแบ่งหน้า ที่ cursor (page break มี fallback OOXML กัน E_FAIL), Office.js ล้วน — ⚠️ `contentControls` ธรรมดาเห็นเฉพาะ rich text CC: feature-detect WordApi 1.5 ใช้ `getContentControls()` เห็นทุกชนิด — helper `supportedContentControls`/`supportsAllContentControlTypes` อยู่ที่ **metadataService** แชร์กับ `readContentControlFields` (fields ที่แนบตอนสร้าง version เก็บ CC ทุกตัวแก้ 2026-10-28 — เดิมเหลือแค่ rich ตัวเดียว), ห้าม top-level Office access) · **authService + ticketService = ticket auth ของ e-Court ทางเดียว ไม่มี login แล้ว** (ยกเลิก Keycloak → pass-through token/owner → ticket 2026-10-27, `docs/PLAN-ADDIN-AUTH.md` ตาม `docs/requirement/addin-workflow.md` — surface เหลือ signInForDemo (dev-only) / signInWithTicket / getAccessToken / renewTokenFromTicket / getUsername; ไฟล์ที่ stamp custom properties `ticket_data`+`HmacSha256` มา = `detect()` ของ TaskpaneApp เรียก signInWithTicket: ตรวจ HMAC ฝั่ง client (Web Crypto + NEXT_PUBLIC_ADDIN_TICKET_SECRET — UX gate) → exp → session localStorage `wordaddin-auth` ตาม uid (user ใหม่ = เคลียร์ทั้งก้อน) → ขอ token ที่ `/api/documents/token` แบบ body ticket ผูก fid; ไม่มี ticket/HMAC ไม่ตรง/หมดอายุ = phase `no-access` แยกข้อความตามสาเหตุ; apiClient โดน 401 = renew จาก ticket 1 ครั้ง ยังไม่ผ่าน = AuthRequiredError → no-access) · **versioningService = ของจริง** (ปลด mock 2026-10-27 — uploadVersion/getVersions/getDownloadLink ยิงกลุ่ม `/api/documents/*` ผ่าน apiClient (Bearer same-origin ผ่าน withBasePath — `NEXT_PUBLIC_ADDIN_API_BASE_URL` ตั้งเฉพาะวัน deploy คนละ origin ต้องรวม basePath); เอกสารระบุด้วย fid ใน token — พารามิเตอร์ documentId/templateId เหลือตาม signature เดิมไม่ส่งถึง server และ **ห้ามส่ง userId/userName/templateId ใน form (contract 2026-10-22 ส่งมา = 400)**; mock เหลือเฉพาะโหมด `?addindemo` ผ่าน `authService.isDemoMode()` — memory + metadata persist ลง localStorage ต่อ documentId, download-link คืน about:blank; unit test tests/wordaddin/versioningService.test.ts คุมทั้งสองโหมด)
|
||||
wordaddin/services/ ← documentService/fileService/metadataService/autoSaveService/versionFlow/apiClient/errors พอร์ตดิบทั้งไฟล์ + **templateService** (แท็บที่ 2 "เครื่องมือ Template" 2026-10-19 — list/select/applyTestValues + แทรกย่อหน้า-ตัวแบ่งบรรทัด-ตัวแบ่งหน้า ที่ cursor (page break มี fallback OOXML กัน E_FAIL), Office.js ล้วน — ⚠️ `contentControls` ธรรมดาเห็นเฉพาะ rich text CC: feature-detect WordApi 1.5 ใช้ `getContentControls()` เห็นทุกชนิด — helper `supportedContentControls`/`supportsAllContentControlTypes` อยู่ที่ **metadataService** แชร์กับ `readContentControlFields` (fields ที่แนบตอนสร้าง version เก็บ CC ทุกตัวแก้ 2026-10-28 — เดิมเหลือแค่ rich ตัวเดียว), ห้าม top-level Office access) · **authService + ticketService = ticket auth ของ e-Court ทางเดียว ไม่มี login แล้ว** (ยกเลิก Keycloak → pass-through token/owner → ticket 2026-10-27, `docs/PLAN-ADDIN-AUTH.md` ตาม `docs/requirement/addin-workflow.md` — surface เหลือ signInForDemo (dev-only) / signInWithTicket / getAccessToken / renewTokenFromTicket / getUsername; ไฟล์ที่ stamp custom properties `ticket_data`+`HmacSha256` มา = `detect()` ของ TaskpaneApp เรียก signInWithTicket: ตรวจ HMAC ฝั่ง client (Web Crypto + NEXT_PUBLIC_ADDIN_TICKET_SECRET — UX gate) → session localStorage `wordaddin-auth` ตาม uid (user ใหม่ = เคลียร์ทั้งก้อน) → **ต้องการ token เมื่อไหร่ยิงขอ `/api/documents/token` เสมอ แล้วให้ server ตัดสิน exp เอง (แก้ 2026-10-28 ตาม requirement ขั้น 7: ticket หมดอายุแต่ fid เคยมี version โดย uid เดียวกัน = ออกให้ — กลับมาทำเอกสารเดิมใช้ต่อได้แม้ token เก่าหมด/ localStorage หาย)** ผูก fid; ไม่มี ticket/HMAC ไม่ตรง/หมดอายุ+ไม่เคยทำงาน = phase `no-access` แยกข้อความตามสาเหตุ; apiClient โดน 401 = renew จาก ticket 1 ครั้ง (ไม่ตัด exp ฝั่ง client) ยังไม่ผ่าน = AuthRequiredError → no-access) · **versioningService = ของจริง** (ปลด mock 2026-10-27 — uploadVersion/getVersions/getDownloadLink ยิงกลุ่ม `/api/documents/*` ผ่าน apiClient (Bearer same-origin ผ่าน withBasePath — `NEXT_PUBLIC_ADDIN_API_BASE_URL` ตั้งเฉพาะวัน deploy คนละ origin ต้องรวม basePath); เอกสารระบุด้วย fid ใน token — พารามิเตอร์ documentId/templateId เหลือตาม signature เดิมไม่ส่งถึง server และ **ห้ามส่ง userId/userName/templateId ใน form (contract 2026-10-22 ส่งมา = 400)**; mock เหลือเฉพาะโหมด `?addindemo` ผ่าน `authService.isDemoMode()` — memory + metadata persist ลง localStorage ต่อ documentId, download-link คืน about:blank; unit test tests/wordaddin/versioningService.test.ts คุมทั้งสองโหมด)
|
||||
utils/coordinates.ts ← pixel⇄% + getPointerPercent + clamp + mergeRectsByLine
|
||||
utils/geometry.ts ← bbox/translate/scale/hitTestPath ของ annotation (pure, หน่วย %)
|
||||
utils/annotation-display.ts ← label/ข้อความย่อต่อชนิด + TYPE_SHORT/TYPE_ORDER ชื่อย่อ/ลำดับชนิด (บรรทัดสรุปหน้าของหน้าสรุป + แผงตัวกรอง explorer — ย้ายจาก 2 ที่ซ้ำกันมารวมที่นี่ 2026-09-24) + ค่า default TEXTBOX/STAMP + describeAnnotationChange สรุปสิ่งที่เปลี่ยนของ version history F19 (sidebar/สรุป/groups/CSV ใช้ร่วม)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue