ข้ามไปที่เนื้อหา

S9 — Web Portal — Backend Implementation Blueprint

เอกสารนี้คืออะไร

แผน implement บน portal เดิม (Node.js + MSSQL + Keycloak) — ขยายต่อ ไม่ใช่เขียนใหม่ · ผูก REQ → endpoint → service → ตาราง (schema) · อ้างอิง Traceability · Data Dictionary · ไม่ใช่โค้ดจริง (repo เป็น KB)

1. Stack เดิม & จุดที่ต่อเติม

Layer ของเดิม สิ่งที่ทำใน Phase 2
Backend Node.js / RESTful (OpenAPI 3.0) เพิ่ม service: membership, SSO, security-policy, service-center, survey, dashboard
DB PORTAL @ PSDBPRDDB (48 ตาราง) +23 ตารางใหม่ / ALTER 1 (ดู schema)
Auth/IdP Keycloak (UserProfile.Id = Keycloak UUID) ใช้ Keycloak เป็น identity broker ของ ThaiD/Digital ID/AD + SSO
API Gateway Reverse proxy SSL termination รวมศูนย์ (REQ-WPT-004, infra)
Query Builder Phase 1 (inherit)ApiTokens.AllowedQuery, OpenApiExecuteLog ต่อยอดเป็น PublishedQuery (CR-3)

หลักสำคัญ: Keycloak ทำ auth/SSO, ตาราง PORTAL ทำ mapping + audit

UserProfile.Id เป็น UUID จาก Keycloak อยู่แล้ว → อย่า reinvent SSO · ให้ Keycloak federate ThaiD/Digital ID (OIDC) + AD (LDAP/SAML) และทำ SSO ข้ามระบบ ส่วนตาราง AuthProvider/UserIdentity/SsoSession เก็บ mapping และ audit ฝั่ง portal

2. Layering (ตามของเดิม)

Route (Express/Nest) → Controller → Service (rule) → Repository (mssql) → PORTAL.*
Cross-cutting: Keycloak middleware (verify JWT) · AuthZ (UserRole/ModuleRoles) ·
               AuditLog middleware · SecurityPolicy guard · Notification dispatcher

3. REQ-by-REQ Implementation

REQ-WPT-001 — Integration + OpenAPI + Service Center + Query Builder

  • OpenAPI ทุก endpoint: ลงทะเบียน endpoint ของระบบย่อยใน PORTAL.EndpointRegistry (path/method/specVersion/openApiUrl) → หน้า API catalog
  • Service Center (CR-1): POST /api/sc/intake รับจาก สป.ยธ./MSC → insert PORTAL.ServiceCenterRequest (Origin='EXTERNAL')RoutingService แตก PORTAL.ServiceCenterRouting ราย subsystem
  • Multi-case (CR-6): portal submit หลายเรื่อง → ServiceCenterRequest (Origin='PORTAL') + หลาย routing rows; เจ้าหน้าที่ปลายทาง PATCH /api/sc/routing/{id} กดรับ/ไม่รับ (Accepted)
  • Query Builder (CR-3): ผู้ใช้ภายนอกสร้าง query → PORTAL.PublishedQuery (QueryDefinition) → publish เป็น endpoint ผูก ApiTokens (OAuth/Token) ; รัน → log OpenApiExecuteLog (เดิม)

REQ-WPT-002 — Central User–Subsystem Membership

  • MembershipService:
  • GET /api/me/memberships — หลัง login ด้วย ThaiD เอา CitizenId (=UserProfile.Pid) → query PORTAL.UserSubsystemMembership WHERE CitizenId=@pid AND MembershipStatus='MEMBER'
  • POST /api/memberships (จากระบบย่อยตอนอนุมัติสมาชิก) → upsert membership อัตโนมัติ
  • เป็น source of truth ของ "ผู้ใช้เป็นสมาชิกระบบไหนบ้าง" สำหรับ Card (WPT-003)

REQ-WPT-003 — Card UI ตามสมาชิก

  • GET /api/portal/cards → join PORTAL.SubsystemCard × UserSubsystemMembership (ตาม CitizenId)
  • Visibility='MEMBER_ONLY' → แสดงเฉพาะระบบที่เป็นสมาชิก ; PUBLIC → ทุกคน
  • Breadcrumb: SubsystemCard.BreadcrumbRoot → frontend ต่อเป็น "OCIPA › คำขอเยียวยา"

REQ-WPT-004 — SSL/TLS รวมศูนย์

  • ไม่มีงาน backend code — ตั้งที่ API Gateway / reverse proxy (wildcard cert, SSL termination)
  • เอกสาร config + ตรวจ TLS 1.2+ (NFR-S01) เท่านั้น

REQ-WPT-005 — Authorization รวมศูนย์

  • ฟอร์มขอสิทธิ์: POST /api/permission-requestsPORTAL.PermissionRequest (reuse) + RequestedPermissions (ราย system+roles)
  • ช่อง "ขอสิทธิ์ AD เท่านั้น" (O-8) → set PermissionRequest.AdOnlyAccess=1 (ไม่บังคับเลือกระบบย่อย)
  • Routing + approval: ApprovalService แตก PORTAL.PermissionApproval ราย SystemCode → หา owner จาก PORTAL.SubsystemOwner → ส่ง PORTAL.Notification
  • เจ้าของระบบ PATCH /api/approvals/{id} (APPROVED/REJECTED) — อนุมัติได้ใน Portal จุดเดียว
  • เมื่อทุก approval เสร็จ → อัปเดต PermissionRequest.Status + เขียน role ลง UserRole (reuse) และ sync เป็น Keycloak client roles (ผ่าน Keycloak Admin API)
  • รวม role ทุกระบบ: อ่าน ModuleRoles (reuse) มาแสดงให้เลือกในฟอร์มเดียว
  • Portal = single authorization authority ของทุกระบบ (รวม S8):
  • ทุกระบบย่อยลงทะเบียน role catalog ที่ PORTAL.ModuleRoles (SystemCode ของระบบนั้น เช่น S8) + เป็น row ใน RLPDSystems
  • การมอบสิทธิ์ทำที่นี่จุดเดียว → sync ไป Keycloak (client role ต่อระบบ เช่น client s8-cms)
  • ทุกระบบ enforce จาก JWT (resource_access[client].roles) — ไม่มีระบบไหนเก็บ user-role ของตัวเอง

REQ-WPT-006 — ThaiD/Digital ID/AD + SSO

  • Keycloak realm กำหนด Identity Providers: ThaiD (OIDC), Digital ID (OIDC), AD (LDAP federation / SAML)
  • หลัง broker login → upsert PORTAL.UserProfile (เดิม) + บันทึก PORTAL.UserIdentity (provider + external subject) ; IsPrimary = ThaiD
  • Alt credential (O-2, CONSULTANT_USER): ผู้ที่ใช้ ThaiD ไม่ได้ → provider LOCAL ใน AuthProvider/UserIdentity
  • SSO + dynamic downstream (O-3): Keycloak ออก SSO token ; portal บันทึก PORTAL.SsoSession ; กด Card → ออก PORTAL.SsoDownstreamGrant (assertion ไปยังระบบปลายทาง) เข้าระบบย่อยโดยไม่ login ซ้ำ (OIDC/SAML)

REQ-WPT-007 — Login Security Policy (config ได้)

  • Config back-office: PORTAL.SecurityPolicy (1 แถว) — GET/PUT /api/admin/security-policy (PORTAL_ADMIN) → มีผลทันที ไม่ต้อง deploy (NFR-M06)
  • Lockout: ทุก login → insert PORTAL.LoginAttempt ; ถ้า fail ≥ MaxFailedAttempts (4) ใน window → ตั้ง UserSecurityStatus.LockedUntil = now + LockoutMinutes (5)
  • Password aging: เช็ค UserSecurityStatus.PasswordChangedAt > PasswordMaxAgeDays (60) → บังคับเปลี่ยน (MustChangePassword=1)
  • Inactive: LastLoginAt ห่าง > InactiveDays (45) → บังคับเปลี่ยนรหัสตอน login ครั้งถัดไป
  • No reuse: ตอนเปลี่ยนรหัส เทียบ hash กับ PORTAL.PasswordHistory (ReuseHistoryCount) ; admin toggle BypassPasswordReuse (CR-4) ข้ามได้
  • หมายเหตุ: ถ้า password lifecycle อยู่ใน Keycloak → ตั้ง policy ที่ Keycloak ด้วย และ mirror ค่าจาก SecurityPolicy (single config source)

REQ-WPT-008 — Dashboard + สถิติ + Log + Export

  • Audit middleware: ทุก action สำคัญ → PORTAL.AuditLog ; ทุก login/เข้าระบบ → SystemAccessLog (reuse)
  • Rollup job (cron รายวัน): aggregate SystemAccessLog/AuditLogPORTAL.UsageStatDaily (per system/วัน) → dashboard ดึงเร็ว (NFR-P02 ≤5s/30วัน)
  • GET /api/dashboard?from&to&granularity (รายวัน/สัปดาห์/เดือน/ปี) → อ่าน UsageStatDaily
  • User/Audit Log search: GET /api/audit?userId&from&to (AUDITOR) → AuditLog/SystemAccessLog
  • Export: GET /api/dashboard/export?format=xlsx|csv (app layer สร้างไฟล์ ≤10s)

REQ-WPT-009 — Satisfaction Survey

  • แสดงแบบประเมิน: icon บนเว็บ / popup หลังใช้งาน (เช่น หลังยื่น OCIPA สำเร็จ) → GET /api/surveys/active?system=
  • บันทึก: POST /api/surveys/{id}/responsesPORTAL.SurveyResponse (ผู้ตอบ: เพศ/อายุ/วุฒิ/สถานภาพ) + SurveyAnswer (rating 1–5/choice/text)
  • คำถาม: PORTAL.Survey + SurveyQuestion (Section RESPONDENT/SYSTEM/FORMAT/CONTENT/OVERALL)
  • รายงานหลังบ้าน: สรุปภาพรวม + แยกตามระบบ (SurveyResponse.SystemCode) + Export Excel/CSV

4. API Surface (สรุป)

Endpoint Method Auth ตารางหลัก REQ
/api/me/memberships GET ThaiD UserSubsystemMembership 002
/api/portal/cards GET login SubsystemCard (+membership) 003
/api/permission-requests POST login PermissionRequest, RequestedPermissions 005
/api/approvals/{id} PATCH SubsystemOwner PermissionApproval, Notification, UserRole 005
/api/auth/callback (Keycloak) GET broker UserProfile, UserIdentity, SsoSession 006
/api/sso/launch/{system} POST SSO SsoDownstreamGrant 006
/api/admin/security-policy GET/PUT PORTAL_ADMIN SecurityPolicy 007
/api/sc/intake POST API key ServiceCenterRequest, ServiceCenterRouting 001
/api/sc/routing/{id} PATCH staff ServiceCenterRouting 001
/api/published-queries CRUD EXTERNAL_AGENCY PublishedQuery, ApiTokens 001
/api/dashboard, /api/audit, /export GET EXEC/AUDITOR UsageStatDaily, AuditLog, SystemAccessLog 008
/api/surveys/* GET/POST public/admin Survey, SurveyQuestion, SurveyResponse, SurveyAnswer 009

5. AuthZ (Roles)

7 roles (PORTAL_ADMIN, EXECUTIVE, AUDITOR, INTERNAL_USER, EXTERNAL_USER, EXTERNAL_AGENCY, CONSULTANT_USER) — map เป็น Keycloak roles/groups + UserRole(reuse, JSON ราย system) ; ตรวจสิทธิ์ที่ middleware

6. NFR Implementation

NFR วิธีทำ
S06 lockout / S07 aging SecurityPolicy + LoginAttempt + UserSecurityStatus + PasswordHistory
S08 auth Keycloak (ThaiD/Digital ID/AD) + SSO (OIDC/SAML)
S01 TLS / WPT-004 API Gateway SSL termination (infra)
M02 logging structured log → ELK ; AuditLog/SystemAccessLog
P02 dashboard ≤5s pre-aggregate UsageStatDaily
M03 API doc OpenAPI 3.0 ทุก endpoint + EndpointRegistry

7. ลำดับงาน (Build Order)

  1. ALTER PermissionRequest ADD AdOnlyAccess + สร้าง 23 ตารางใหม่ (ตามลำดับ FK ใน schema.sql)
  2. Auth/SSO: Keycloak IdP config + AuthProvider/UserIdentity/SsoSession (WPT-006)
  3. Membership + Cards (WPT-002/003)
  4. Security policy + lockout/aging (WPT-007) + back-office config
  5. Permission approval routing + Notification (WPT-005)
  6. Service Center + routing + PublishedQuery (WPT-001)
  7. Audit/AuditLog + rollup job + Dashboard/Export (WPT-008)
  8. Survey (WPT-009)

8. Rollout (extension — ไม่ใช่ migration ใหญ่)

  • รัน schema.sql บน PSDBPRDDB (ALTER ปลอดภัย: AdOnlyAccess มี DEFAULT)
  • Seed: AuthProvider (THAID/DIGITAL_ID/AD/LOCAL), SecurityPolicy (ค่า O-7: 4/5/60/45), SubsystemCard (ราย RLPDSystems), SubsystemOwner
  • Backfill: UserSecurityStatus จาก UserProfile ที่มีอยู่ ; UserSubsystemMembership จากข้อมูลสมาชิกเดิมของแต่ละระบบย่อย
  • ตรวจ collation ของคอลัมน์ string FK ให้ตรงกับ PSDBPRDDB (ดู note ใน schema.sql)

9. SSO Integration Status

ส่วน สถานะ หมายเหตุ
Keycloak (realm rlpd, sso.rlpd.go.th) ✅ พร้อม (Phase 1) OIDC/OAuth2 — portal ผูกอยู่แล้ว (UserProfile.Id = Keycloak UUID)
ThaiD / Digital ID พร้อม login ประชาชนผ่าน ThaiD ใช้ได้จริง (ไม่ใช่ open item แล้ว)
AD (เจ้าหน้าที่) ✅ พร้อม Keycloak ↔ LDAP federation
SSO ไปทุกระบบใหม่ Phase 2 ⬜ onboard เพิ่ม สร้าง OIDC client ต่อระบบย่อย (REQ-WPT-001/006)
Dynamic downstream SSO (O-3) 🟡 ออกแบบ ระบบย่อยต้องรองรับ SAML/OIDC SP — เก็บ grant ใน SsoDownstreamGrant

หมายเหตุ: DXC SSO (sso.dxc.go.th) แยกต่างหากสำหรับ WPIS/OCIPA (ตรวจประวัติ) — การรวม SSO ต้องครอบคลุมกรณีนี้ด้วย

10. Dependencies / Open items

  • นิยาม "เคส" + สิทธิ์เจ้าหน้าที่ปลายทาง สำหรับ multi-case (O-5)
  • ขอบเขตการ inherit Query Builder จาก Phase 1 (O-6)
  • ระบบย่อยแต่ละตัวต้องรองรับ SAML/OIDC SP สำหรับ dynamic downstream SSO (O-3)
  • ยืนยันค่า default นโยบาย login (O-7) — ตั้งผ่าน SecurityPolicy ได้ (มีค่าเริ่มต้นแล้ว)