add /api/auth short-path endpoints (token/session) + swagger group "auth"
- POST /api/auth/token = alias of /api/documents/token (docver access_token, both x-api-key and ticket entrances) - POST /api/auth/session = alias of /api/note/auth/session (exchange e-Court link credential for note login token, 8h TTL) - lib/openapi.ts: new "auth" tag + both paths + SessionExchangeRequest / SessionResponse schemas (session endpoint had never been documented) - tests/lib/openapi.test.ts: guard all $refs resolve + auth paths exist Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
ccd257c825
commit
545c9d5703
4 changed files with 525 additions and 2 deletions
257
lib/openapi.ts
257
lib/openapi.ts
|
|
@ -1,6 +1,7 @@
|
|||
// OpenAPI 3.0 spec ของ API ทดสอบทั้งหมดของ judes-note — เส้นเดียวกับที่ Swagger UI
|
||||
// แสดงผล: กลุ่ม template (DOCX Content Control) + N10 external API + docver
|
||||
// (จัด version เอกสาร)
|
||||
// (จัด version เอกสาร) + auth (ทางเข้า path สั้น /api/auth/* — alias ของ
|
||||
// /api/documents/token กับ /api/note/auth/session เพิ่ม 2026-11-05)
|
||||
// route handler GET /api/swagger (app/api/swagger/route.ts) เป็นคนเดียวที่ใช้
|
||||
// — inline เป็น JSON ลงใน HTML ตอน respond (ไม่มี endpoint spec แยก)
|
||||
//
|
||||
|
|
@ -25,7 +26,8 @@ export const openApiSpec = {
|
|||
version: "1.0.0",
|
||||
description:
|
||||
"API ทดสอบของระบบ Judge's Note — กลุ่ม Template (DOCX Content Control), " +
|
||||
"API ภายนอกสำหรับ backend e-Court (N10) และ API จัด version เอกสาร (docver)",
|
||||
"API ภายนอกสำหรับ backend e-Court (N10), API จัด version เอกสาร (docver) " +
|
||||
"และทางเข้า auth แบบ path สั้น (/api/auth/*)",
|
||||
},
|
||||
servers: [{ url: BASE_PATH, description: "origin เดียวกับหน้านี้ (basePath /docedit)" }],
|
||||
tags: [
|
||||
|
|
@ -59,6 +61,15 @@ export const openApiSpec = {
|
|||
"server-to-server ดึง annotations/bookmarks ของเอกสารที่เคยเปิดในระบบนี้ · " +
|
||||
"SSO: เส้น mint login-token สำหรับประกอบลิงก์เข้าสู่ระบบ (PLAN-SSO.md)",
|
||||
},
|
||||
{
|
||||
name: "auth",
|
||||
description:
|
||||
"ทางเข้า auth แบบ **path สั้น** `/api/auth/*` (เพิ่ม 2026-11-05) — endpoint " +
|
||||
"ก้อนเดียวกับของเดิมคนละ URL เลือกใช้ทางใดทางหนึ่งได้:\n" +
|
||||
"- `POST /api/auth/token` = `POST /api/documents/token` — ออก access_token ของกลุ่ม documents (docver)\n" +
|
||||
"- `POST /api/auth/session` = `POST /api/note/auth/session` — แลก credential ของลิงก์ e-Court " +
|
||||
"เป็น session token ของแอป note",
|
||||
},
|
||||
],
|
||||
paths: {
|
||||
"/api/template/test": {
|
||||
|
|
@ -687,6 +698,181 @@ export const openApiSpec = {
|
|||
},
|
||||
},
|
||||
},
|
||||
"/api/auth/token": {
|
||||
post: {
|
||||
tags: ["auth"],
|
||||
summary: "ขอ access_token ของกลุ่ม documents (docver) — path สั้นของ /api/documents/token",
|
||||
description:
|
||||
"ทางเข้า **path สั้น** ของ `POST /api/documents/token` — endpoint ก้อนเดียวกัน " +
|
||||
"พฤติกรรมเหมือนกันทุกประการ (เพิ่ม 2026-11-05)\n\n" +
|
||||
"ออก `access_token` (JWT HS256) ไว้เรียก endpoint อื่นของกลุ่ม documents — " +
|
||||
"token ฝัง claims `{userid, username, fid, template}` อายุตาม env " +
|
||||
"`DOCVER_TOKEN_TTL_SECONDS` (default 604800 วินาที = 7 วัน — token อายุยาวแทน" +
|
||||
"ระบบ refresh/renew ของ add-in)\n\n" +
|
||||
"**ทางเข้า 2 แบบ** — เลือกตามว่าใครเป็นคนเรียก (ดูตัวอย่างใน Request body ได้ทั้งคู่):\n\n" +
|
||||
"1. **ระบบภายนอก (server-to-server)** — แนบ header `x-api-key` (ค่าจาก env " +
|
||||
"`DOCVER_API_KEY`) แล้วส่ง claims ตรง ๆ ใน body ตาม schema `TokenRequest`\n" +
|
||||
"2. **add-in ใน Word** — ไม่ต้องมี `x-api-key` ส่ง body `{ticket_data, HmacSha256}` " +
|
||||
"ตาม schema `TicketTokenRequest` (คู่ค่าที่ e-Court stamp ไว้ใน custom properties " +
|
||||
"ของไฟล์ .docx) — server ตรวจลายเซ็น HMAC-SHA256 ด้วย env `ADDIN_TICKET_SECRET` " +
|
||||
'(ไม่ตั้ง → env `SECRET` → `"super_secret"`) แล้วออก token จาก claims ที่อยู่ในตัว ticket เอง ' +
|
||||
"(contract 2026-11-04: ฟอร์แม็ตพัง/ไม่ครบ = 400 · ลายเซ็นไม่ถูก = 401 · " +
|
||||
"**ลายเซ็นถูกแต่ ticket หมดอายุ = 403** — add-in ที่มี access_token เดิมของ `fid` " +
|
||||
"นี้ใน session ยังใช้ต่อได้ ไม่มี = ขอไฟล์ใหม่จาก e-Court)\n\n" +
|
||||
"ได้ token แล้วแนบเป็น `Authorization: Bearer <token>` กับทุก request ถัดไป",
|
||||
security: [{ ApiKeyAuth: [] }],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
oneOf: [
|
||||
{ $ref: "#/components/schemas/TokenRequest" },
|
||||
{ $ref: "#/components/schemas/TicketTokenRequest" },
|
||||
],
|
||||
},
|
||||
examples: {
|
||||
"server-to-server ด้วย x-api-key (ครบทุก claim)": {
|
||||
value: {
|
||||
userid: "123",
|
||||
username: "Sorawit Bholsithi",
|
||||
fid: "3",
|
||||
template: "T12",
|
||||
},
|
||||
},
|
||||
"server-to-server ขั้นต่ำ (template เป็นทางเลือก)": {
|
||||
value: { userid: "123", username: "Sorawit Bholsithi", fid: "3" },
|
||||
},
|
||||
"add-in ด้วย ticket จากไฟล์ (ไม่ต้องมี x-api-key)": {
|
||||
value: {
|
||||
ticket_data:
|
||||
'{"exp":1793067433,"fid":"T12_FD155_3s-long_ZZZ","tid":"T12","uid":"oom","uname":"สรวิชญ์"}',
|
||||
HmacSha256:
|
||||
"1dc53fbd48410845c895ddb841a8fe4ad93da413126fae9c688202e57f2baaa9",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "access_token พร้อมใช้ (แนบเป็น Authorization: Bearer ทุก request ถัดไป)",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: { $ref: "#/components/schemas/TokenResponse" },
|
||||
},
|
||||
},
|
||||
},
|
||||
400: { $ref: "#/components/responses/BadRequest" },
|
||||
401: {
|
||||
description:
|
||||
"ทางเข้า x-api-key: ไม่ได้ส่ง header หรือค่าไม่ถูกต้อง (env DOCVER_API_KEY) · " +
|
||||
"ทางเข้า ticket: ลายเซ็น HMAC ไม่ถูก (ข้อความเดียวกันทุกสาเหตุ)",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: { $ref: "#/components/schemas/Error" },
|
||||
},
|
||||
},
|
||||
},
|
||||
403: {
|
||||
description:
|
||||
"เฉพาะทางเข้า ticket: ลายเซ็นถูกแต่ ticket หมดอายุ — ใช้ access_token เดิมของ " +
|
||||
"fid นี้ใน session ต่อ หรือขอไฟล์ใหม่จาก e-Court (contract 2026-11-04)",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: { $ref: "#/components/schemas/Error" },
|
||||
},
|
||||
},
|
||||
},
|
||||
500: { $ref: "#/components/responses/ServerError" },
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/auth/session": {
|
||||
post: {
|
||||
tags: ["auth"],
|
||||
summary: "แลก credential ของลิงก์ e-Court เป็น session token ของแอป note (SSO)",
|
||||
description:
|
||||
"ทางเข้า **path สั้น** ของ `POST /api/note/auth/session` — endpoint ก้อนเดียวกัน " +
|
||||
"พฤติกรรมเหมือนกันทุกประการ (เพิ่ม 2026-11-05) · flow SSO ของแอป note " +
|
||||
"(PLAN-SSO.md §3.2 ฉบับ token): ลิงก์ e-Court พา browser มาหน้า `/note` พร้อม " +
|
||||
"credential → client เรียกเส้นนี้แลกเป็น access_token เก็บ localStorage " +
|
||||
'("note-auth") แล้วแนบ `Authorization: Bearer` กับทุก API/Server Action ของแอป\n\n' +
|
||||
"- ส่ง credential **ทางเดียว**: `t` (login-token จาก `POST /api/note/auth/token`) " +
|
||||
"หรือ `ticket_data`+`HmacSha256` (คู่ค่าชุดเดียวกับที่ e-Court stamp ใน custom " +
|
||||
"properties ของไฟล์ .docx ของ add-in) — สองชุดพร้อมกัน = 400 (D12)\n" +
|
||||
"- token ที่ได้เป็นชนิด `purpose=login` อายุ 8 ชม. (28800 วินาที) — ใช้ได้กับแอป " +
|
||||
"note เท่านั้น เอาไปยิง `/api/documents/*` ของกลุ่ม docver ไม่ได้ (401)\n" +
|
||||
"- ส่ง `fileUrl` มาด้วย = ลงทะเบียนเอกสาร N8 แบบ trusted ฝั่ง server ทันที " +
|
||||
"(fid จาก credential ที่ verify แล้ว) — ล้มก็ยังได้ token พร้อม `fileError` " +
|
||||
"ให้หน้าแรกแสดงสาเหตุ · `fileUrl` ต้องผ่าน allowlist ของ N8 " +
|
||||
"(admincourt.go.th + env `EXTERNAL_FILE_HOSTS`) หรือเป็น path `/samples/…`\n" +
|
||||
"- **ทาง ticket ใน production**: secret ต้องตั้งจริง (env `ADDIN_TICKET_SECRET` " +
|
||||
'→ `SECRET`) — ค่า default `"super_secret"` ที่รู้กันทั้งระบบถูกปฏิเสธ (500)',
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: { $ref: "#/components/schemas/SessionExchangeRequest" },
|
||||
examples: {
|
||||
"ทาง JWT (param t ของลิงก์ e-Court)": {
|
||||
value: {
|
||||
t: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwdXJwb3NlIjoibG9naW4iLCJ1c2VyaWQiOiJvb20iLCJ1c2VybmFtZSI6IuC4geC4m+C4tOC5iOC4quC4lOC5iCIsImZpZCI6IkYxMjMifQ.….….…",
|
||||
fileUrl: "https://service.admincourt.go.th/uploads/F123.pdf",
|
||||
typeId: "T12",
|
||||
},
|
||||
},
|
||||
"ทาง ticket (คู่ค่าเดียวกับไฟล์ .docx ของ add-in)": {
|
||||
value: {
|
||||
ticket_data:
|
||||
'{"exp":1793067433,"fid":"T12_FD155_3s-long_ZZZ","tid":"T12","uid":"oom","uname":"สรวิชญ์"}',
|
||||
HmacSha256:
|
||||
"1dc53fbd48410845c895ddb841a8fe4ad93da413126fae9c688202e57f2baaa9",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description:
|
||||
"session token ของแอป note (พร้อม fileId ถ้าลงทะเบียน fileUrl สำเร็จ)",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: { $ref: "#/components/schemas/SessionResponse" },
|
||||
},
|
||||
},
|
||||
},
|
||||
400: { $ref: "#/components/responses/BadRequest" },
|
||||
401: {
|
||||
description:
|
||||
"credential หมดอายุหรือไม่ถูกต้อง — ทาง JWT แยก `reason: expired` (หมดอายุ) กับ " +
|
||||
"`invalid` · ทาง ticket รวมเป็น `invalid` ทั้งลายเซ็นผิดและหมดอายุ",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
allOf: [
|
||||
{ $ref: "#/components/schemas/Error" },
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
reason: {
|
||||
type: "string",
|
||||
enum: ["expired", "invalid"],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
500: { $ref: "#/components/responses/ServerError" },
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/documents/token": {
|
||||
post: {
|
||||
tags: ["documents"],
|
||||
|
|
@ -1149,6 +1335,73 @@ export const openApiSpec = {
|
|||
},
|
||||
},
|
||||
},
|
||||
SessionExchangeRequest: {
|
||||
type: "object",
|
||||
description:
|
||||
"credential ของลิงก์ e-Court ที่จะแลกเป็น session token — ส่งทางเดียว: `t` " +
|
||||
"**หรือ** `ticket_data`+`HmacSha256` (สองชุดพร้อมกัน = 400 ตาม D12) · " +
|
||||
"allowlist 5 field เป๊ะ — ส่ง field อื่น = 400",
|
||||
properties: {
|
||||
t: {
|
||||
type: "string",
|
||||
description:
|
||||
"login-token (JWT purpose=login) จาก POST /api/note/auth/token — ≤ 8192 ตัวอักษร",
|
||||
},
|
||||
ticket_data: {
|
||||
type: "string",
|
||||
description:
|
||||
"JSON string ของ ticket ชุดเดียวกับที่ e-Court stamp ในไฟล์ .docx — " +
|
||||
"≤ 2048 ตัวอักษร (ส่งตรง ๆ ไม่แก้ string — HMAC ผูกกับค่าดิบ)",
|
||||
},
|
||||
HmacSha256: {
|
||||
type: "string",
|
||||
description: "hex 64 ตัว ของ HMAC-SHA256(secret, ticket_data ดิบ) — ≤ 128 ตัวอักษร",
|
||||
},
|
||||
fileUrl: {
|
||||
type: "string",
|
||||
description:
|
||||
"URL เอกสารบน e-Court — ต้องผ่าน allowlist ของ N8 (admincourt.go.th + env " +
|
||||
"EXTERNAL_FILE_HOSTS) · ส่งมา = ลงทะเบียนเอกสารตอนแลก token (≤ 2048 ตัวอักษร)",
|
||||
},
|
||||
typeId: {
|
||||
type: "string",
|
||||
description: "รหัสกลุ่มเอกสารของ e-Court (≤ 200 ตัวอักษร)",
|
||||
},
|
||||
},
|
||||
},
|
||||
SessionResponse: {
|
||||
type: "object",
|
||||
description:
|
||||
"session ของแอป note — client เก็บใน localStorage (\"note-auth\") แล้วแนบ " +
|
||||
"Authorization: Bearer กับทุก API/Server Action ของแอป",
|
||||
properties: {
|
||||
access_token: {
|
||||
type: "string",
|
||||
description: "JWT purpose=login อายุ 8 ชม. — ใช้ได้กับแอป note เท่านั้น",
|
||||
},
|
||||
token_type: { type: "string", example: "Bearer" },
|
||||
expires_in: {
|
||||
type: "integer",
|
||||
description: "อายุวินาที (NOTE_SESSION_TTL = 28800 = 8 ชม.)",
|
||||
example: 28800,
|
||||
},
|
||||
userid: { type: "string", description: "รหัสผู้ใช้จาก credential (u_userid ของ e-Court)" },
|
||||
username: { type: "string", description: "ชื่อแสดงผลจาก credential" },
|
||||
fileId: {
|
||||
type: "string",
|
||||
format: "uuid",
|
||||
description:
|
||||
"มีเมื่อส่ง fileUrl และลงทะเบียนสำเร็จ — id ของเอกสารในระบบนี้ " +
|
||||
"(ใช้เข้าหน้าอ่าน /note/annotate/<fileId>)",
|
||||
},
|
||||
fileError: {
|
||||
type: "string",
|
||||
description:
|
||||
"มีเมื่อลงทะเบียน fileUrl ไม่สำเร็จ (ไม่ผ่าน allowlist ฯลฯ) — token ยังออกให้ " +
|
||||
"หน้าแรกแสดงสาเหตุ",
|
||||
},
|
||||
},
|
||||
},
|
||||
ExtractResponse: {
|
||||
type: "object",
|
||||
properties: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue