Refactor authentication flow and ticket handling in add-in
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
- Removed client-side HMAC verification; server now handles all HMAC checks. - Updated ticket processing logic to improve UX and security. - Adjusted API request handling to return discriminated results based on HTTP status. - Enhanced error messages for better clarity on authentication failures. - Added unit tests for the new token endpoint to ensure compliance with updated contract. - Cleaned up unused code and comments related to previous HMAC handling.
This commit is contained in:
parent
2e951f99f4
commit
c0833c00f7
24 changed files with 858 additions and 460 deletions
|
|
@ -5,11 +5,15 @@
|
|||
// 2) add-in ใน Word: ไม่มี x-api-key + body {ticket_data, HmacSha256} — server
|
||||
// ตรวจ HMAC ด้วย env ฝั่ง server (ADDIN_TICKET_SECRET → SECRET →
|
||||
// "super_secret", lib/addin-ticket.ts) แล้วออก token จาก claims ของ ticket
|
||||
// (secret ตัวเซ็น JWT ไม่มีทางไปอยู่ใน bundle ของ add-in) — ticket หมดอายุ
|
||||
// ยังออกให้ต่อเมื่อ fid นี้เคยมี version โดย uid เดียวกัน (requirement ขั้น 7)
|
||||
// (secret ตัวเซ็น JWT ไม่มีทางไปอยู่ใน bundle ของ add-in — client ไม่ตรวจ HMAC
|
||||
// เองแล้วตาม PLAN-SSO.md D13) · contract 2026-11-04: 400 = ฟอร์แม็ตพัง
|
||||
// (รวม missing) / 401 = ลายเซ็นไม่ถูก / 403 = ลายเซ็นถูกแต่ ticket หมดอายุ
|
||||
// (client ที่มี JWT เดิมของ fid นี้ใน session ใช้ต่อเอง — ไม่มีการออกซ้ำ
|
||||
// จากหลักฐาน DB อีกต่อไป เลิก requirement ขั้น 7 ฉบับ 2026-10-28)
|
||||
// ทั้งสองทางออก JWT HS256 เซ็นด้วย env DOCVER_TOKEN_SECRET อายุตาม env
|
||||
// DOCVER_TOKEN_TTL_SECONDS (default 3600) แล้วนำไปแนบ Authorization: Bearer กับ
|
||||
// endpoint อื่นของกลุ่ม (lib/docver-token.ts) · segment คงที่ "token" ไม่ชน [docId]
|
||||
// DOCVER_TOKEN_TTL_SECONDS (default 604800 = 7 วัน — token อายุยาวแทนระบบ
|
||||
// refresh/renew) แล้วนำไปแนบ Authorization: Bearer กับ endpoint อื่นของกลุ่ม
|
||||
// (lib/docver-token.ts) · segment คงที่ "token" ไม่ชน [docId]
|
||||
// (Next ให้ static ชนะ) และ middleware ยกเว้น api/documents/* อยู่แล้ว
|
||||
import { docverError } from "@/lib/document-versioning";
|
||||
import {
|
||||
|
|
@ -18,7 +22,6 @@ import {
|
|||
ticketToDocverClaims,
|
||||
verifyAddinTicketHmac,
|
||||
} from "@/lib/addin-ticket";
|
||||
import { hasVersionByUser } from "@/lib/docver-store";
|
||||
import { isTicketExpired } from "@/utils/docver-ticket";
|
||||
import {
|
||||
issueDocverToken,
|
||||
|
|
@ -30,8 +33,10 @@ import {
|
|||
export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/** 401 ของทางเข้า ticket — ข้อความเดียวทุกสาเหตุ (HMAC ผิด/รูปผิด/หมดอายุ/ไม่ส่งมา) กัน oracle */
|
||||
const TICKET_DENIED = "ticket ไม่ถูกต้องหรือหมดอายุ";
|
||||
/** 401 ของทางเข้า ticket — ข้อความเดียวทุกสาเหตุ (HMAC ผิด/รูปผิด) กัน oracle · หมดอายุแยกเป็น 403 */
|
||||
const TICKET_DENIED = "ticket ไม่ถูกต้อง";
|
||||
/** 403 ของทางเข้า ticket — ลายเซ็นถูกแต่หมดอายุ (client มี JWT เดิมใน session ก็ใช้ต่อได้) */
|
||||
const TICKET_EXPIRED = "ticket ของ e-Court หมดอายุ";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
return request.headers.get("x-api-key") !== null
|
||||
|
|
@ -65,25 +70,20 @@ async function postWithTicket(request: Request): Promise<Response> {
|
|||
}
|
||||
const parsed = parseTicketTokenBody(raw);
|
||||
if (!parsed.ok) {
|
||||
// missing = ไม่มี ticket มาเล่น ๆ (401 เดียวกับตอน verify ไม่ผ่าน)
|
||||
// invalid = รูป body ผิดจริง ๆ ให้ 400 ตาม convention กัน typo เงียบ ๆ
|
||||
return docverError(parsed.error, parsed.kind === "invalid" ? 400 : 401);
|
||||
// contract 2026-11-04: ฟอร์แม็ตพัง = 400 ทั้ง missing/invalid (เดิม missing เป็น 401)
|
||||
return docverError(parsed.error, 400);
|
||||
}
|
||||
// ตรวจ 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 เหมือนไม่มีสิทธิ์
|
||||
// ลายเซ็นถูกแต่ ticket หมดอายุ → 403 เสมอ (contract 2026-11-04 — เลิก requirement
|
||||
// ขั้น 7 ฉบับ 2026-10-28: ไม่ตรวจ hasVersionByUser อีกต่อไป เพราะ ticket ฝังอยู่ใน
|
||||
// ไฟล์ ใครได้ไฟล์ก็ได้ ticket การปล่อยให้ ticket เก่าแลก token ใหม่ได้ตลอดไป
|
||||
// เท่ากันคือปล่อยสิทธิ์ตามการครอบครองไฟล์ — ฝั่ง client ที่มี JWT ของ fid นี้
|
||||
// ใน session ที่ยังไม่หมด (7 วัน) ใช้ต่อเอง ไม่มี = ขอไฟล์ใหม่จาก e-Court)
|
||||
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 docverError(TICKET_EXPIRED, 403);
|
||||
}
|
||||
return issueToken(ticketToDocverClaims(verified.ticket));
|
||||
}
|
||||
|
|
@ -96,8 +96,9 @@ async function issueToken(claims: DocverTokenClaims): Promise<Response> {
|
|||
return docverError("DOCVER_TOKEN_SECRET ยังไม่ถูกตั้งค่า", 500);
|
||||
}
|
||||
const ttlRaw = Number(process.env.DOCVER_TOKEN_TTL_SECONDS);
|
||||
// default 7 วัน (2026-11-04) — token อายุยาวแทนระบบ refresh/renew ของ add-in
|
||||
const ttlSeconds =
|
||||
Number.isFinite(ttlRaw) && ttlRaw > 0 ? Math.floor(ttlRaw) : 3600;
|
||||
Number.isFinite(ttlRaw) && ttlRaw > 0 ? Math.floor(ttlRaw) : 604_800;
|
||||
|
||||
const accessToken = issueDocverToken(claims, secret, ttlSeconds);
|
||||
return new Response(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue