Private Docs

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 แล้ว สามใบนี้เป็นของงานรอบนี้โดยตรง

PRrepoสถานะเนื้อหา
3110Backend_UserServiceDraftU1–U9 · ตาราง RoleApiPermission · apiPermissions ใน blob · API ผูก/ถอนสิทธิ์ · outbox + drain worker · audit · seed สองชุด · endpoint fallback GET api/v1/users/cache/info/{oid}
3111Backend_PackageActiveSupApp_util_lib 10.33.0 — เส้น fallback forward Authorization ของผู้เรียกต่อไปด้วย
3112Backend_IacActiveApiPermissions: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 บน branch MIGRATION เท่านั้น ตามกฎ 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 ตอนนี้ join Roles ด้วย 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 นั้น ตอนนี้ครอบคลุมถึง key authrev ด้วย
  • แถว 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ที่มา สภาพเดิม และเหตุผลที่เลือกทางนี้
01spec ของ header AppId ใช้กับข้อ 1
02data model และเกณฑ์ตั้งชื่อ ใช้กับข้อ 2 ถึง 4
03ลำดับ deploy และ observe mode ใช้กับข้อ 5
04รายละเอียดระดับโค้ดต่อ repo
05ภาพรวมด้วย sequence diagram