API Restriction · 06 — overview และ checklist ของงานที่เหลือ
ไฟล์เดียวที่บอกว่างานทั้งชุดถึงไหนแล้ว เหลืออะไร ใครต้องตัดสินอะไร และลำดับไหนสลับไม่ได้ — ยึดไฟล์นี้เป็นตัวตั้ง ไม่ใช่รายการงานที่กระจายอยู่ในไฟล์ 03/04/05
อัปเดต: 2026-09-10
API Restriction · 06 — overview และ checklist ของงานที่เหลือ
ไฟล์ 00–05 เป็นเอกสารออกแบบ เขียนไว้ก่อนลงมือ และไม่ได้ถูกแก้ตามสิ่งที่เกิดขึ้นจริงระหว่างทาง ไฟล์นี้คือรายการงานที่ยึดถือได้ ถ้ารายการในไฟล์ 03 หรือ 05 §7 ขัดกับไฟล์นี้ ให้ยึดไฟล์นี้ เพราะไฟล์นี้เขียนหลังจากโค้ดขึ้น PR แล้ว
สถานะ ณ 10/09/2026 · ตรวจกับ Backend_UserService@4ca6be8 และ SupApp_util_lib 10.33.0
1. ทำเสร็จแล้ว รอ review อย่างเดียว
งาน U1–U9 ทั้งชุดเขียนเสร็จและอยู่ใน PR แล้ว สามใบนี้เป็นของงานรอบนี้โดยตรง
| PR | repo | สถานะ | เนื้อหา |
|---|---|---|---|
| 3110 | Backend_UserService | Draft | U1–U9 · ตาราง RoleApiPermission · apiPermissions ใน blob · API ผูก/ถอนสิทธิ์ · outbox + drain worker · audit · seed สองชุด · endpoint fallback GET api/v1/users/cache/info/{oid} |
| 3111 | Backend_Package | Active | SupApp_util_lib 10.33.0 — เส้น fallback forward Authorization ของผู้เรียกต่อไปด้วย |
| 3112 | Backend_Iac | Active | ApiPermissions:OwnerServiceName ของ user-service ครบ dev/sit/uat · แก้ host ของ FallbackEndpoint ฝั่ง centralized-service |
SupApp_util_lib 10.33.0 publish ขึ้น feed แล้ว และ UserService04.Domain.csproj ชี้เวอร์ชันนี้แล้ว · service อื่นจะได้ 10.33.0 ก็ต่อเมื่อ PR ของ lane api-surface (3100–3109) merge เข้าไป เพราะ PR ชุดนั้นเป็นตัว bump <PackageReference> ให้แต่ละ repo
ลำดับที่สลับไม่ได้
- merge 3112 (IaC) ก่อน 3110 เสมอ — ถ้า 3110 ขึ้นก่อน pod ของ UserService จะ crash-loop เพราะอ่าน
ApiPermissions:OwnerServiceNameไม่เจอ ย้อนได้ด้วยการ merge 3112 ตามทันที แต่ระหว่างนั้น service ล่ม
2. งานที่เหลือ 5 ข้อ
ห้าข้อนี้คือสิ่งที่ทำให้ระบบใช้ได้จริง ซึ่งยังไม่มีในโค้ดวันนี้ เรียงตามลำดับที่ต้องทำ เพราะข้อหลังใช้ผลของข้อหน้า
ข้อ 1 — HostApp ส่ง header บอกว่ามาจาก app ไหน
ตรวจแล้วว่า Frontend_HostAppSuperApp ยังไม่ส่ง header นี้เลยสักที่ (grep X-App-Id ใต้ apps/ ไม่เจอ) ฝั่ง backend รออยู่แล้ว — lib อ่าน header ตามชื่อที่ตั้งใน AppContextHeaderName และ parse ที่ RedisUserInfoMiddleware.cs:532
มติที่เคาะแล้ว: ค่า AppId มาจาก env file ของ FE ไม่ใช่ค่าที่คำนวณตอน runtime ข้อนี้ตกลงกันแล้ว ไม่ต้องรื้อ
- หาว่า AppId ตัวไหน คือค่าที่ต้องใส่ ทำได้ด้วยการ query dev DB ว่า role 5 ตัวของ admin อยู่ใต้
AppIdใดบ้าง คำตอบนี้ใช้ได้ทั้งข้อนี้และข้อ 4 - ตัดสินว่า shell ส่งค่าเดียว หรือ remote app แต่ละตัวส่งค่าของตัวเอง role ผูกกับ
AppIdดังนั้น remote อย่าง FX ที่มี role คนละชุดอาจต้องส่งคนละค่า ข้อนี้ต้องให้ Owner เคาะ ไม่ใช่ dev เลือกเอง - ยืนยันว่า header ผ่าน APIM ได้จริง
Backend_Iac/src/apim/policies/sentinel-facade-inbound-v6.xmlยังไม่ verifyว่า forward custom header ให้หรือไม่ - เติมชื่อ header ลง
Access-Control-Allow-Headersของ CORS ทุก env ไม่งั้น browser จะ preflight ไม่ผ่าน และทุก request ตายก่อนถึง backend
ข้อ 2 — scan API ทุก service เพื่อตั้งชื่อ permission
ข้อมูลดิบมีอยู่แล้ว ไม่ต้อง scan ใหม่ตั้งแต่ศูนย์ — API Inventory รวม endpoint ของ 11 service ไว้แล้ว 446 แถว ปรับปรุง 10/09/2026 และ UserService ตั้งชื่อครบแล้ว 166 action จาก 28 controller อยู่ใน PERMISSION_NAMING.md ของ Atlas
- ขยายเกณฑ์ตั้งชื่อเดิมไปยังอีก 10 service โดยใช้ inventory เป็นตัวตั้ง เกณฑ์ที่ freeze ไว้แล้วคือ
{resource}:{action}ตัวพิมพ์เล็กล้วน ห้าม wildcard ห้ามใส่ชื่อ service ห้ามใส่ HTTP method ห้ามใส่ชื่อ env - ตัดแถวที่ปิดไปแล้วออกก่อน PR 3100–3109 ปิด endpoint ที่ไม่พบผู้เรียกด้วย
[NotExposed]ไปหลายร้อยเส้น ถ้าตั้งชื่อ permission ให้เส้นที่ตายแล้ว seed ในข้อ 3 จะ insert ขยะตามไปด้วย
ข้อ 3 — insert permission ที่ได้จากข้อ 2
- เขียน data migration ตามแบบเดียวกับ U9 คือมี
CreatedByเป็น marker ของตัวเอง และDownลบเฉพาะแถวที่ตัวเองใส่ · author บน branchMIGRATIONเท่านั้น ตามกฎ repo - แต่ละ service ต้องมี
ApiPermissions:OwnerServiceNameของตัวเองใน IaC ครบทั้ง 3 env ก่อน pod ของ service นั้นจะรันโค้ดที่ register catalogue ไม่งั้น crash-loop แบบเดียวกับที่ 3110 จะเป็นถ้าไม่มี 3112 และค่าต้องตัวพิมพ์เล็กล้วน เพราะ DB CHECK บังคับlower()และ lib เทียบด้วยStringComparison.Ordinalสะกดต่างกันคือสิทธิ์ไม่ match เลยโดยไม่มี error ให้เห็น - เช็คก่อนว่า service ไหน register catalogue จริงบ้าง อย่าเหมาว่าครบทั้ง 10
ข้อ 4 — ผูก role เดิมเข้ากับ permission
U9 ทำให้ UserService ไปแล้ว 126 แถว (54 permission × 2 role + 6 × 3 role) แต่มีข้อบกพร่องที่ต้องแก้ก่อนขยายไป service อื่น
- แก้ seed ให้ scope ด้วย
AppIdตอนนี้ joinRolesด้วยNameกับRoleTypeเท่านั้น เพราะตอนเขียนยังไม่รู้ค่าAppIdจริง ⇒ ถ้าชื่อ role ซ้ำข้าม app จะให้สิทธิ์ทุก app ที่ชื่อตรงกัน ใช้คำตอบจากข้อ 1 มาปิดช่องนี้ - ผูก role ของอีก 10 service เข้ากับ permission จากข้อ 3
ข้อ 5 — ทดสอบว่าเข้าถึง API ได้จริงไหม
PermissionAuthorizationHandler เรียก context.Fail() ในทุกเส้นทางที่ปฏิเสธ ซึ่งเป็น explicit veto ที่ observe/LogOnly ผ่อนให้ไม่ได้ (คอมเมนต์ที่ PermissionAuthorizationHandler.cs:71 ระบุไว้ตรง ๆ) แปลว่าเปิด enforcement คือ 403 ทันทีสำหรับทุกเส้นที่ยังไม่ได้ผูกสิทธิ์ ไม่มีโหมดกลางให้ถอยแบบนุ่มนวล
- re-warm blob ทุกตัวก่อน blob ที่ cache ไว้ก่อนงานนี้ไม่มี key
authrevและด่านจะปฏิเสธเมื่อหา key ไม่เจอ ⇒ ถ้าไม่ re-warm ก่อน คนที่ blob ยังเก่าจะโดน 503 ทั้งหมด - เปิด
EnforceAppContextบน dev ก่อนเท่านั้น พร้อมเขียนวิธีปิดกลับไว้ข้าง ๆ - merge U4/U4a (
feature/us-authz-u4-require-permission@2ab98aaซ่อม test project และเขียน test แล้ว) หลังเปิดสวิตช์เท่านั้น ถ้า merge ก่อน ทุก endpoint ที่แปะ attribute จะ 403 ทันทีที่โค้ดไปรัน - ทดสอบด้วยเบราว์เซอร์จริง กดผ่านหน้าจอทุก step ห้าม seed ข้อมูล ห้ามยิง API แทน ตามกฎ E2E ที่ Owner ตั้งไว้
-
AdminAmloControllerถูกเว้นไว้โดยตั้งใจ ไม่แปะ[RequirePermission]เพราะทุกเส้นผูกกับบริษัทแต่โมเดล grant ไม่มีมิติบริษัท ถ้าแปะจะกลายเป็น admin ของบริษัทหนึ่งเห็นข้อมูลของอีกบริษัท คนทดสอบต้องรู้ว่านี่ไม่ใช่ของตกหล่น
3. เรื่องที่รอ Owner ตัดสิน
- SV-01
RedisUserInfo:EnableFallbackถูกเปลี่ยนจากfalseเป็นtrueทั้ง appsettings และ IaC ทุก env ระหว่างทำงานรอบนี้ โดยยังไม่ได้ขออนุมัติ เป็นการแก้ที่อยู่นอก scope ที่ freeze ไว้ ย้อนได้ ต้องการคำตอบว่ารับหรือถอย - commit
988cb9aยังไม่ผ่าน review หลักฐาน review ผูกกับ73626d5ส่วน988cb9aคือตัวที่แก้ regression เรื่อง hard-delete role ที่ reviewer เจอเอง จึงเป็นการแก้ที่ยังไม่มีใครตรวจ - shell ส่ง AppId ค่าเดียว หรือ remote แต่ละตัวส่งเอง ซ้ำกับข้อ 1 โดยตั้งใจ เพราะเป็นสิ่งที่บล็อกงาน
4. หนี้ทางเทคนิคที่รู้ตัวแล้ว
ไม่บล็อก rollout แต่ถ้าไม่เขียนไว้จะหายไปกับ session
-
KeyPrefixของ Redis ต่อ env ยังไม่ verifyว่าตรงกันทุก service ที่เป็นผู้บริโภค ถ้ามี service ไหน prefix ไม่ตรง การล้าง cache จะไปไม่ถึง service นั้น ตอนนี้ครอบคลุมถึง keyauthrevด้วย - แถว outbox ที่เป็น role-scoped เก็บ
RoleIdไว้ในคอลัมน์UserIdเพราะตอนนั้น entity ถูก freeze ไปแล้ว ควรย้ายไปคอลัมน์จริงใน migration รอบถัดไป - AC-04 เส้นอ่าน DB ในโปรเซสถูก register แล้วแต่ยังไม่มี test ยืนยันว่า register จริง
- N+1 ที่
CallerPermissionGuard.cs:26,:30บน branch u4 คือ query หนึ่งครั้งต่อหนึ่ง role -
AssignEndUserRoleHandlerและCreateCompanyApplicationHandlerยังไม่มี test ที่ระบุชื่อมันตรง ๆ แม้รูปแบบการเขียนของมันจะถูกครอบด้วย test รวมแล้วก็ตาม - user ที่ถูก deactivate ยังคง authenticated อยู่ถ้าโทเคน Entra ยังไม่หมดอายุ เพียงแต่ไม่มี role เหลือ ⇒
[Authorize]เปล่า ๆ ยังผ่าน มีแต่[Authorize(Roles = …)]กับด่านสิทธิ์ที่ปฏิเสธ เป็นพฤติกรรมเดิมบนdevelopmentไม่ใช่ของใหม่ และอยู่นอก scope รอบนี้
5. อ่านต่อ
| ไฟล์ | ใช้ตอนไหน |
|---|---|
| 00 | ที่มา สภาพเดิม และเหตุผลที่เลือกทางนี้ |
| 01 | spec ของ header AppId ใช้กับข้อ 1 |
| 02 | data model และเกณฑ์ตั้งชื่อ ใช้กับข้อ 2 ถึง 4 |
| 03 | ลำดับ deploy และ observe mode ใช้กับข้อ 5 |
| 04 | รายละเอียดระดับโค้ดต่อ repo |
| 05 | ภาพรวมด้วย sequence diagram |