12.1 · Auth mechanisms inventory
แพลตฟอร์มนี้ไม่มี auth mechanism เดียว — มีอย่างน้อย 9 รูปแบบที่แตกต่างกัน กระจายอยู่ตามยุคที่เขียนและทีมที่เขียน ไม่มี central identity provider ที่ทุก service ไปตรวจสอบร่วมกัน (ไม่มี JWKS, ไม่มี OAuth authorization server ภายใน)
A. GraphQL @auth directive + apiKey header (legacy core)
| ระบบ | vtrc-api |
|---|---|
| กลไก | GraphQL directive @auth(accessRole: [...], accessMenu: [...]) ที่ resolver level + header apiKey (ไม่ใช่ apiKey field แต่มาจาก API_DEVICE_KEY[].key — ดู Canonical Truths) ที่ map ไปหา APP_TYPE (WEB/MOBILE/WEB_ADMIN) |
| JWT | payload มีแค่ id, uid — ไม่มี role/scope ใน token เอง role ถูก query ใหม่ทุกครั้งจาก DB ผ่าน getCurrentRoleUser() |
| จุดอ่อน | JWT_SECRET default 'secret' ถ้า env ไม่ตั้ง (SEC-VTRC-API-02), jwt.verify() ไม่ระบุ algorithm explicit (เปิดช่อง algorithm confusion ในทางทฤษฎี), getCurrentRoleUser/checkMenuAuth มี RBAC bug หลายจุด (ดู Volume 3 vtrc-api/03-auth-session) |
| apiKey เป็น device-level ไม่ใช่ user-level | ทุก user ที่ใช้ app ประเภทเดียวกัน (เช่นทุกคนที่เข้า vtrc-web) ส่ง apiKey ตัวเดียวกัน — ไม่ใช่ต่อ user ไม่ใช่ต่อ session ดู 12.2 |
B. LDAP/Active Directory bind (legacy core, HR identity)
| ระบบ | cu-central-api |
|---|---|
| กลไก | bind ตรงกับ AD ของ HRMI (Chula) ด้วย adUsername/password ผ่าน activeDirectory.connector.js |
| จุดอ่อน | rejectUnauthorized: false (SEC-CU-06) — ไม่ verify TLS certificate ของ LDAP server เปิดช่อง MITM, มี REST route /auth/signinbypass ที่ passwordless (SEC-CU-04), ignoreAuth directive ข้าม auth ได้ถ้ามี apiKey เพียงอย่างเดียว (SEC-CU-05) |
| ดูรายละเอียด | Volume 3 cu-central-api/02-auth-ldap-directive |
C. NestJS JWT guard + @Public() override (domain microservices, ส่วนใหญ่ของ Volume 6)
รูปแบบที่ใช้มากที่สุดในทั้งแพลตฟอร์ม — พบใน benefit-api, recruitment-api, pms-api, vtrc-common, sso-api, 2way-api, ส่วนใหญ่ของ chat-center-api
| ส่วนประกอบ | รายละเอียด |
|---|---|
| Global guard | JwtAuthGuard ผูกที่ APP_GUARD ระดับ module — ทุก request ต้องมี valid JWT เป็น default |
| Override | decorator @Public() + Reflector เช็คว่า route ไหนยกเว้น |
| จุดอ่อนร่วม | การตัดสินใจว่า endpoint ไหนควร @Public() ทำแบบ ad-hoc ต่อ repo — พบ endpoint ที่ควร private กลับเป็น @Public() ใน benefit-api (wdHospitals — SEC-BENEFIT-03), 2way-api (เกือบทุก write endpoint — SEC-2WAYAPI-04), 2way-line-service (ทุก endpoint — SEC-2WAYLINE-01), vtrc-common (ทุก endpoint จริง — SEC-COMMON-01), pms-api (/budget/getEmployeeSalary — SEC-PMS-01), 2way-vtrc-api (POST /2way/getProfile — SEC-2WAYVTRC-02), meeting-vtrc-api (/meeting/mockuser/one — SEC-MTVTRC-02) |
| Copy-paste ไม่ใช่ shared library | libs/jwt/jwt-auth.guard.ts เหมือนกันไบต์ต่อไบต์ระหว่าง vtrc-common/chat-center-api/job-scheduler-api (QUAL-COMMON-02) — แปลว่า pattern เดียวกันถูก copy ไปทุกที่ รวมถึง bug/design choice เดิมด้วย |
pms-api เพิ่ม ThrottlerGuard เป็น global guard ชั้นเดียวที่มีใน Volume 6 (100 req/60s ต่อ IP) — เป็น defense-in-depth ที่ repo อื่นในกลุ่มไม่มี
D. Self-managed JWT ชั้นที่สอง (parallel auth layer)
พบใน recruitment-api, pms-api — มี AuthDataService ที่เรียก jwt.sign()/jwt.verify() ตรงด้วย jsonwebtoken แยกจาก Passport strategy ของ Layer C ข้างบน
- access token (15 นาที) + refresh token (1 วัน) เก็บลง
authDatatable เพื่อ revoke ได้ (ต่างจาก Layer C ที่ revoke ไม่ได้จนกว่า token expire) recruitment-apiเช็คdecodedToken.iss === 'redcross.or.th:vtrc'เพื่อแยกว่า token มาจากvtrc-apiหรือตัวเอง — ใช้ได้เพราะ shared secret (ดู Volume 11.4)
E. Global middleware JWT verify (Express, ไม่มี guard/decorator pattern)
| ระบบ | ai-recruitment-api |
|---|---|
| กลไก | middleware verifyJWT ผูกกับทุก route แบบ global (Express ไม่ใช่ NestJS) เรียก jwt.verify(token, SECRET_KEY) ตรง |
| จุดอ่อน | ไม่มี role/scope check เลยหลังจากยืนยัน signature — token ที่ valid (จาก secret ที่แชร์กับ 4 service) ผ่านได้ทุก endpoint (SEC-AIRECRUIT-02) |
F. ไม่มี auth guard เลย (exception เดียวใน Volume 6)
| ระบบ | workflow-api |
|---|---|
| กลไก | ไม่มี JwtAuthGuard ระดับ global หรือ module — อ่าน role ตรงจาก JWT payload โดยไม่ verify signature ก่อน (SEC-WORKFLOW-01) |
| ผลกระทบ | ใครก็ตามส่ง JWT ที่ decode ได้ (ไม่ต้อง valid signature) พร้อม payload ที่อ้างว่าเป็น role สูง สามารถ impersonate ได้ — ยิ่งอันตรายเพราะ workflow-api มี DB connection ตรงไปยัง 4 database ของ owner อื่น (Volume 11.2) |
G. Frontend-to-frontend SSO handoff ผ่าน URL
| ระบบ | รูปแบบ |
|---|---|
vtrc-backoffice-new | login flow ส่ง access token + refresh token เป็น path segment ของ URL ข้าม origin (SEC-BONEW-01) — token หลุดเข้า browser history/server log |
vtrc-rc-backoffice | SSO bypass ผ่าน /pathLogin/:token/:refreshToken (C3) — รูปแบบเดียวกัน |
| จุดร่วม | ทั้งสอง repo ใช้ URL path เป็นช่องทางส่ง credential ข้าม frontend แทน POST body/header — เป็น anti-pattern ที่ปรากฏซ้ำ 2 ครั้งข้าม repo คนละยุค |
H. Static/hardcoded backdoor credential (ไม่ใช่ auth mechanism ที่ตั้งใจแต่ทำงานเป็น mechanism จริงในโค้ด)
| ระบบ | รูปแบบ |
|---|---|
2way-api | username/password hardcode สำหรับ login backdoor (SEC-2WAYAPI-01) |
2way-vtrc-api | static master bypass token "testnaja" (SEC-2WAYVTRC-01) |
cu-central-api | /auth/signinbypass (SEC-CU-04) |
centralize-api | ทุก route mark @Public() เท่ากับ auth bypass 100% แม้จะมี @Public() decorator แบบ NestJS ทางเทคนิค (CRIT-03) |
รายละเอียด trust-boundary ของกลุ่มนี้ ดู 12.2
I. External SSO delegation (ไม่ verify JWT เอง แต่พึ่ง service อื่น)
| ระบบ | delegate ไปที่ |
|---|---|
meeting-vtrc-api | เรียก vtrc-api เพื่อ verify token (ไม่มี JWT secret ของตัวเองสำหรับ user-facing auth) — ดู Volume 7 meeting-vtrc-api/03-persistence-integrations |
meeting-backoffice | เรียก meeting-vtrc-api REST (auth bypass endpoint ตามที่ persistence chapter บันทึกไว้) |
sso-api | มี JwtModule.register() ประกาศไว้แต่ JwtService ไม่ถูก inject ไปใช้จริง (CORR-SSO-01) — signIn() ไม่ออก JWT ของตัวเอง endpoint อื่นที่ guard ด้วย JWT จึงใช้งานไม่ได้จริงในทางปฏิบัติ เป็น auth mechanism ที่ "ประกาศไว้แต่ broken" |
J. Mobile token storage + payload encryption
| App | Token storage | Payload encryption |
|---|---|---|
vtrc-mobile | AsyncStorage | RSA private key hardcode ในซอร์ส/bundle (SEC-MOBILE-01) |
new-vtrc-mobile | AsyncStorage plaintext (regression จาก vtrc-mobile, SEC-NEW-02) | RSA key เดียวกับ vtrc-mobile (SEC-NEW-01) |
vtrc-application-front | ไม่พบ Critical finding ด้าน auth — ปัญหาหลักคือ IPA binary commit เข้า repo (LEG-FRONT-01) |
K. Recruitment/OCR pipeline — OTP + AES encryption ที่มี backdoor
| ระบบ | รูปแบบ |
|---|---|
recruitment-web | hardcoded OTP bypass (SEC-RCWEB-03) + AES key (REACT_APP_SECRET_KEY) เข้ารหัส PII แต่ key ฝัง bundle (SEC-RCWEB-01) |
vtrc-ocr-api | ไม่มี auth-related Critical — เป็น mock engine ที่ไม่ได้เชื่อม OCR จริง (QUAL-OCRAPI-01) |
ตารางเปรียบเทียบสรุป
| Mechanism | จำนวน repo ที่ใช้ | Central verification? | Revocable? |
|---|---|---|---|
GraphQL @auth + apiKey (legacy) | 1 (vtrc-api) | ไม่ — ตรวจ role จาก DB ตรง | Session-based, revoke ได้ |
| LDAP/AD bind | 1 (cu-central-api) | ไม่ — bind ตรงกับ AD | N/A |
NestJS JWT guard + @Public() | ≥ 7 repo | ไม่ — secret แชร์กันเองเป็นกลุ่ม ไม่มี JWKS | ไม่ (จนกว่า token expire) เว้นแต่มี Layer D |
Self-managed JWT (AuthDataService) | 2 (recruitment-api, pms-api) | ไม่ | ได้ (ผ่าน authData table) |
| Global middleware verify (Express) | 1 (ai-recruitment-api) | ไม่ | ไม่ |
| ไม่มี guard เลย | 1 (workflow-api) | ไม่มี auth จริง | N/A |
| Frontend SSO ผ่าน URL | 2 (vtrc-backoffice-new, vtrc-rc-backoffice) | ไม่ | ขึ้นกับ token เอง |
| Backdoor/static bypass | ≥ 4 repo | N/A | N/A |
| External delegation | 3 (meeting-vtrc-api, meeting-backoffice, sso-api แบบ broken) | ขึ้นกับ upstream | ขึ้นกับ upstream |
ไม่มี repo ใดใน 25 repo ใช้ OAuth2/OIDC มาตรฐาน, JWKS, หรือ central identity provider ที่ verify ได้จากภายนอกโดยไม่ต้อง copy secret — ทุก "SSO" ที่พบในชื่อไฟล์/ตัวแปร (เช่น sso-api) หมายถึง gateway ไปยัง SSO ภายนอกของสภากาชาด ไม่ใช่ internal SSO ระหว่าง 25 repo นี้เอง
รายละเอียดว่า trust boundary เหล่านี้ blur กันตรงไหนบ้าง ดู 12.2 Shared trust boundaries →