สิทธิ์ผู้ใช้ Permission และ Module Gate
ภาพรวม
ระบบสิทธิ์ของ cms-api-go ประกอบด้วย สี่ชั้นที่ทำงานต่างกัน และเป็นจุดที่เข้าใจสับสนได้ง่ายมาก จึงต้องแยกให้ชัดเจน
- CASL policy (
@CheckPolicies) — มี metadata กำกับครบทุก route แต่ ปิดการบังคับใช้อยู่ เพราะCheckPoliciesเป็น no-op ที่ปล่อยผ่านทุก request ตรงกับ NestJS เดิมที่PoliciesGuard.canActivateคืนค่าtrueเสมอ ผลคือ permission ในตารางsystem_moduleและsystem_role_moduleถูกใช้เพียงเพื่อ ซ่อนหรือแสดงเมนูใน cms-web ผ่านGET /api/user/:id/permissionเท่านั้น - SuperAdminGuard — บังคับใช้จริง โดยกำหนดว่า
roleIdต้องเท่ากับ 1 - ModuleGate — บังคับใช้จริง เมื่อ platform admin ปิดโมดูลของ organization ใด ลูกค้าที่เรียก API ตรงก็จะได้ 403
- AppEnabledGuard — บังคับใช้จริง โดย app อย่าง appointment, loyalty หรือ bulletin ต้องถูกเปิดให้ organization นั้นก่อนจึงจะใช้งานได้
Business Flow
การอ่าน permission ของตัวเอง (สำหรับแสดงเมนู)
- cms-web เรียก
GET /api/user/:id/permissionโดยพารามิเตอร์:idต้องเป็น id ของผู้เรียกเอง หากไม่ใช่จะได้ 400 พร้อมข้อความCannot access other userId - service โหลดข้อมูล user พร้อม
SystemRoleModuleซึ่งเป็น baseline ตาม role และดึงรายชื่อโมดูลทั้งหมดจากsystem_module - หาก user มี
type = customerและ organization นั้นมีแถวอยู่ในorganization_moduleระบบจะใช้ override ของ organization นั้นแทน baseline โดย platform admin เป็นผู้กำหนด - หาก user มี
type = onemobyซึ่งรวมถึง role 1 ที่เป็น platform operator ระบบจะใช้ baseline ตาม role เสมอ ไม่ถูก override - ตอบกลับเป็นรายการรูปแบบ
{subject: ชื่อ module, action: [...]}ให้ cms-web นำไปตัดสินใจว่าจะแสดงเมนูใดบ้าง
ModuleGate (บังคับใช้ฝั่ง server)
- middleware อ่านค่า
user.typeของผู้เรียกจาก CLS ผ่านselfId - หากเป็น
onemobyหรือระบุตัวผู้เรียกไม่ได้ จะปล่อยผ่านทันที (governors bypass) - หากเป็น
customerจะสอบถามorgmodulesetting.IsModuleEnabledForOrg(orgId, moduleName)ด้วย logic baseline-vs-override ชุดเดียวกับข้อ 3 ด้านบน - หากโมดูลถูกปิดอยู่จะได้ 403 พร้อมข้อความ
ModuleDisabledแต่หากเกิด error ระหว่าง lookup ระบบจะ fail OPEN คือปล่อยผ่านพร้อม log เพื่อไม่ให้ query ที่สะดุดทำให้ระบบล่มหรือ lock องค์กรออกจากโมดูล PlatformOnlyทำตรงกันข้าม คือ fail CLOSED เพราะความเสี่ยงที่นี่คือ privilege escalation ใช้กับPUT /api/apps/:appIdซึ่งเป็นการเปิดหรือปิด app ให้ organization และต้องทำจาก platform admin console เท่านั้น
ไฟล์และฟังก์ชันหลัก
| ไฟล์ | หน้าที่ |
|---|---|
internal/modules/auth/policy.go | enum PolicyAction และ PolicyModule พร้อม Can() และ CheckPolicies() ซึ่งเป็น no-op |
internal/modules/auth/guards.go | SuperAdmin() และ InternalApiKey() |
internal/modules/modulegate/modulegate.go | ModuleGate(d, moduleName), PlatformOnly(d), gate() และ dbCallerType() |
internal/modules/apps/guard.go | AppEnabledGuard(d) ใช้ regex /apps/(\w+) แล้วตรวจด้วย IsAppEnabled |
internal/modules/user/service.go | FindPermissionByUserID (ราวบรรทัด 405) |
internal/modules/orgmodulesetting/service.go | IsModuleEnabledForOrg — resolver ที่ทั้ง gate และระบบ permission ใช้ร่วมกัน |
Endpoint ที่เกี่ยวข้อง
| Method | Route | Handler | หมายเหตุ |
|---|---|---|---|
| GET | /api/user/:id/permission | ct.findPermissionByUserId | ไม่มี @CheckPolicies — service จำกัดให้อ่านของตัวเองเท่านั้น |
โมดูลที่ถูกครอบด้วย ModuleGate (ใช้ชื่อโมดูลตามตาราง system_module) ได้แก่
api-key, attribute-master, audiences, auto-response, campaign, customer-database,
form-builder, friend-track, import-mapping, report-all-friends, rich-menu, rich-message,
template-message, trigger-rule และ workflow
ค่า enum ของ PolicyModule ที่มีอยู่ในโค้ด ได้แก่
line-oa, campaign, rich-message, rich-menu, auto-response, organization, dashboard,
user, system_role, system_module, line-user, audiences, report-all-friends,
template-message, api-key, form-builder, customer-database, import-mapping,
tracking-log, attribute-master, trigger-rule, workflow, friend-track,
system-attribute, quick-reply และ otp-config
จุดเชื่อมต่อกับ Service อื่น
- ตาราง —
system_module,system_role,system_role_module,organization_module,userและline_oa_app - CLS — ใช้ค่า
selfId,organizationId,roleIdและisSuperAdminซึ่งเติมโดย JWT guard - ผู้กำหนด override — platform admin ผ่านโมดูล Organization Module Setting
- app-level gate — เกี่ยวข้องโดยตรงกับโมดูล Apps Platform
- ฝั่ง frontend — cms-web เป็นผู้บริโภคผลลัพธ์ของ permission นี้ในการแสดงเมนู