Skip to content

11.3 · Identity across systems

v1 Volume 8.9 เคยสรุปไว้ว่า legacy 3 service (vtrc-api, centralize-api, cu-central-api) ไม่มี canonical employee ID เดียว — มี userId (UUID, MariaDB), EmpID/EmpCode (MSSQL HRMI) map กันด้วย field refId ที่เป็น TEXT ไม่ enforce format

v2 พบว่าปัญหานี้ ไม่ได้ถูกจำกัดอยู่แค่ legacy — เมื่อขยาย scope ไปอีก 22 repo พบว่าแต่ละ domain microservice, 2way platform, meeting platform, mobile, และ recruitment pipeline ต่างมีนิยาม "identity" ของตัวเองเพิ่มเติม โดยไม่มีตัวไหนอ้างอิงกลับไปที่ canonical source เดียวกันอย่างเคร่งครัด


3 โดเมนของ "identity" ที่ต้องแยกกันคิด

โดเมนตัวแทนระบบที่เป็นเจ้าของ
Employee identityEmpID/EmpCode (HRMI), userId (vtrc), empCode claim ใน JWT (domain microservices)HRMI (Chula) เป็น source ที่ authoritative ที่สุดสำหรับข้อมูลบุคคล แต่ไม่มี service ใด treat มันเป็น single source of truth ในทางเทคนิค
Candidate identity (pre-employment)citizenId, birthDate, applicant record ใน recruitment-api/recruitment_ai/recruitment-web/vtrc-ocr-apirecruitment-api เป็นเจ้าของ record หลัก — candidate ยังไม่มี EmpCode จนกว่าจะผ่านการจ้าง
External/social identityLINE userId (2way, chat-center), SSO external account (sso-api)ผู้ให้บริการภายนอก (LINE Platform, external SSO provider) — VTRC เก็บ mapping ไปหา employee identity เท่านั้น

3 โดเมนนี้ทับซ้อนกันตรงจุดที่พนักงานคนเดียวมี LINE account, ใช้ SSO, และเคยเป็น candidate มาก่อน — แต่ไม่มี service ใดเก็บ mapping ครบทั้ง 3 โดเมนในที่เดียว


Employee identity — identity graph (ขยายจาก v1 Volume 8.9)

┌────────────────────────────────────────────────────────────────┐
│ HRMI dbHRMI_Center (MSSQL, Chula) — authoritative แต่ไม่ enforced │
│ Employee.EmpID (PK, STRING 36) + Employee.EmpCode (STRING 50)   │
└───────────────────┬──────────────────────────────────────────────┘
                     │ EmpCode (string match, ไม่มี FK ข้าม engine)
        ┌────────────┼─────────────────┬─────────────────┬───────────────┐
        ▼            ▼                 ▼                 ▼               ▼
┌───────────────┐ ┌────────────┐ ┌─────────────┐ ┌───────────────┐ ┌─────────────┐
│vtrc-centralize│ │ benefit DB │ │recruitment DB│ │  pms DB       │ │ workflow-api│
│.Users.refId   │ │.empCode    │ │(candidate →  │ │.empCode       │ │ (ไม่มี DB    │
│(TEXT, MariaDB)│ │(string col)│ │ employee หลัง│ │(string col)   │ │  ของตัวเอง  │
└───────────────┘ └────────────┘ │ จ้างงาน)     │ └───────────────┘ │ อ่าน empCode│
                                  └─────────────┘                    │ จาก 4 DB   │
                                                                      └─────────────┘

        │ JWT payload มี empCode claim (shared secret 4 service — ดู 11.4)

┌─────────────────────────────────────────────────────────────────┐
│ benefit-api / recruitment-api / ai-recruitment-api / pms-api      │
│ — เชื่อ empCode จาก JWT payload โดยตรง ไม่ re-verify กับ HRMI      │
└─────────────────────────────────────────────────────────────────┘

จุดอ่อนที่เพิ่มจาก v1

  • JWT payload กลายเป็น "identity provider" โดยพฤตินัยbenefit-api, recruitment-api, ai-recruitment-api, pms-api ทั้ง 4 ตัว decode JWT ที่เซ็นด้วย secret เดียวกันแล้วอ่าน empCode ตรงจาก payload โดยไม่ query กลับไปยืนยันกับ HRMI ว่า empCode นั้นยังใช้งานอยู่จริง (ดู Volume 6 recruitment-api/04-auth-integrations, ai-recruitment-api/04-auth-integrations) — ถ้า secret รั่ว ผู้โจมตี forge empCode อะไรก็ได้ในระบบทั้ง 4 ตัว
  • workflow-api เป็นจุดเดียวที่เห็น empCode ของทั้ง 4 owner พร้อมกัน (vtrc, HRMI ×2, benefit) ผ่าน connection โดยตรง — แต่ตัวมันเองไม่มี JWT guard (SEC-WORKFLOW-01) จึงเป็น "single point of identity confusion" ที่มีความเสี่ยงสูงสุด
  • vtrc-centralize.Users.refId ยังเป็น TEXT ไม่ enforce format เหมือนที่ v1 พบ — ปัญหาเดิมยังไม่ถูกแก้ใน v2

Candidate identity — namespace ที่แยกจาก employee โดยสิ้นเชิง

recruitment-api เก็บ candidate record ที่มี citizenId, birthDate เป็น PK-adjacent field ก่อนที่จะมี EmpCode — พบ 2 ความเสี่ยงที่เกี่ยวกับ identity โดยเฉพาะ:

  1. recruitment_ai.dbo.jobReqPosition เป็นสำเนาคู่ขนานที่ augmented ของ recruitment.dbo.jobReqPosition (ดู 11.2 #12) — ไม่ใช่ปัญหาเรื่อง employee identity แต่เป็นปัญหาเรื่อง "record identity" (ตำแหน่งงานเดียวกันมี 2 primary record คนละ database)
  2. report-log commit request_*.log ที่มี citizenId, birthDate, empCode ของบุคคลจริงเข้า git (SEC-REPORTLOG-01, Critical) — แปลว่า candidate/employee identity รั่วออกจากทั้ง 3 โดเมนพร้อมกันในไฟล์เดียว เพราะ log อาจถูกเขียนจากทั้ง flow recruitment และ flow HR

External/social identity — LINE userId และ SSO account

ระบบIdentity ที่เก็บMapping ไปหา employee identity
2way-api / 2way-vtrc-api (userLine table)LINE userIdmap ไปหา empCode — แต่ 2way-vtrc-api's POST /2way/getProfile ไม่มี auth guard (SEC-2WAYVTRC-02, Critical) ทำให้ mapping นี้ query ได้โดยไม่ต้อง login
chat-center-api (LINE webhook)LINE userIdcross-schema query ไปหา dbHRMI_Center.dbo.temp_pee_na_table — ตารางที่ชื่อขึ้นต้นด้วย temp_ ถูกใช้เป็น identity bridge แบบ production ทั้งที่ชื่อบอกว่าเป็น scratch table
sso-apiexternal SSO accountcross-schema query ไปหา temp_pee_na_table ตารางเดียวกันกับที่ chat-center-api ใช้ — สอง repo คนละกลุ่มพึ่งพา identity bridge ตัวเดียวกันที่ไม่มี schema contract
meeting-vtrc-api (login table, "game-like fields" ตามที่ Volume 7 บันทึกไว้)ไม่ชัดเจนว่า map กับ employee identity ผ่านช่องทางไหนโดยตรง — ดึงจาก "TwoWay" employee directory ภายนอกผ่าน cdbConnector (สงสัยว่าคือ cu-central-api แต่ยัง UNVERIFIED)ดู Volume 7 meeting-vtrc-api/03-persistence-integrations

temp_pee_na_table เป็นจุดที่ควรระวังที่สุดในหมวดนี้ — เป็น shared identity bridge ระหว่าง 2 repo คนละ platform group (chat-center-api ↔ Domain Microservices, sso-api ↔ Domain Microservices) โดยไม่มี document, ไม่มี versioning, และชื่อบอกเป็นนัยว่าไม่ได้ตั้งใจให้เป็น stable interface


Token/session-layer identity (frontend + mobile)

ทุก frontend (vtrc-web, vtrc-backoffice, vtrc-backoffice-new, vtrc-rc-backoffice) และ mobile app (vtrc-mobile, new-vtrc-mobile) เก็บ JWT ที่ payload มี id/uid (ตาม Volume 3 vtrc-api/03-auth-session) — เป็นตัวแทน vtrc-centralize.Users.userId เดียวกัน ไม่ใช่ identity ใหม่ แต่ ที่เก็บ token ต่างกันทำให้ blast radius ต่างกัน:

Appที่เก็บ tokenrisk
vtrc-backoffice, vtrc-backoffice-newlocalStorage (ไม่มี expiry เพราะ hardcoded isRemember=1)token คงอยู่ตลอดไปแม้ user ปิด browser — ดู Volume 4 vtrc-backoffice/04-scorecard
vtrc-mobile, new-vtrc-mobileAsyncStorage (plaintext ใน new-vtrc-mobile, regression จาก vtrc-mobile)ถ้า device ถูก root/jailbreak อ่าน token ได้ตรง — ดู Volume 8 new-vtrc-mobile/02-scorecard
2way-meeting-backofficelocalStorageเดียวกับ backoffice — ดู Volume 5 2way-meeting-backoffice/04-scorecard

สรุป — canonical identity source มีจริงหรือไม่

ไม่มี ทั้งในทางเทคนิคและทางองค์กร — คำตอบเดียวกับ v1 แต่ scope กว้างขึ้น:

LayerStable IDเปลี่ยนได้ไหมใครถือเป็น source of truth ในทางทฤษฎีใครถือเป็น source of truth ในทางปฏิบัติ
HRMI EmpIDstableไม่เปลี่ยนHRMI (Chula)HRMI (Chula) — ตรงกับทฤษฎี
HRMI EmpCodeunstableเปลี่ยนตาม org policyHRMี (Chula)HRMI เขียน, แต่ 6+ repo cache ค่าเก่าไว้ในตัวเอง (refId, empCode column ต่าง ๆ) โดยไม่มี sync event
vtrc.Users.userIdstableไม่เปลี่ยนvtrc-apivtrc-api — แต่ frontend/mobile หลายตัวเก็บ token แยกกันไม่มี central revocation
JWT empCode claimderived, ไม่ verify ซ้ำตามอายุ tokenไม่มีเจ้าของจริง — เป็น "fact" ที่ trust โดยไม่ตรวจ4 domain microservice เชื่อ claim นี้ตรง
LINE userIdstable (จาก LINE)ไม่เปลี่ยน (เว้นแต่ unlink)LINE Platformchat-center-api/sso-api ผ่าน temp_pee_na_table ที่ไม่มี contract

ข้อเสนอ modernization (canonical employeeId เดียว, event-driven identity sync) เหมือนที่ v1 เคยเสนอไว้ใน Volume 8.12 — ยังใช้ได้ และตอนนี้มี evidence เพิ่มว่าปัญหาขยายไปทั่วทั้งแพลตฟอร์ม ไม่ใช่แค่ legacy core รายละเอียด debt ID ดู 11.5 Scorecard


secret ที่ทำให้ JWT payload กลายเป็น "identity provider" ได้ (เพราะ secret เดียวกันข้าม 4 service) อยู่ที่ 11.4 Shared secrets & credentials