Skip to content

02 · Core pipeline — sso-api

Pipeline หลัก — SSO sign-in flow

Client (frontend/backoffice)
   │  POST /signIn { username, password }

main.ts bootstrap
   ├─ setGlobalPrefix('{BASE_PATH}{API_VERSION_PATH}')     sso-api/src/main.ts:22-23
   ├─ useGlobalGuards(new JwtAuthGuard(reflector))          sso-api/src/main.ts:26-27

JwtAuthGuard.canActivate()
   └─ handler มี @Public() → skip                          sso-api/src/app.controller.ts:30-31

AppController.signIn()                                     sso-api/src/app.controller.ts:30-34

AppService.signIn()                                         sso-api/src/app.service.ts:143-191
   ├─ เข้ารหัส username/password ด้วย aesEncrypt() (AES-256-CBC)   sso-api/libs/common/aes-encrypt.ts:3-13
   ├─ POST {ssoEndpoint}/ssoLogin ด้วย HTTP Basic Auth (ssoUsername/ssoPassword)   sso-api/src/app.service.ts:145-164
   └─ คืนค่า { username, requestAppId, requestId } — accessToken ไม่ถูกออกให้ (บล็อกโค้ด sign JWT ถูก comment ปิดไว้ทั้งหมด)   sso-api/src/app.service.ts:168-181

จุดสำคัญsignIn() ไม่ออก JWT ของ sso-api เอง ให้ผู้เรียก (บรรทัด const token = this.jwtService.sign(payload) ถูก comment ปิดไว้ — sso-api/src/app.service.ts:174) ในขณะที่ JwtService เองก็ไม่ได้ inject ผ่าน constructor เลย (private readonly jwtService: JwtService; เป็น class property เปล่า ไม่มี dependency injection ที่ใช้งานได้จริง — sso-api/src/app.service.ts:27) ผลคือ endpoint ที่เหลือทั้งหมด (signOut, verifySession, request2FA, verify2FA) ที่ ไม่มี @Public() และต้องพึ่ง JwtAuthGuard (sso-api/src/main.ts:26-27) ไม่มีทางถูกเรียกสำเร็จจากไคลเอนต์ที่ผ่าน signIn() ของ service นี้เพียงอย่างเดียว เพราะไม่มี token ให้ใช้ — endpoint เหล่านี้ต้องพึ่ง JWT ที่ออกจาก service อื่น (เช่น vtrc-api) แทน ซึ่งสอดคล้องกับ comment // TODO ที่ผู้พัฒนาทิ้งไว้เหนือ signOut/verifySession (sso-api/src/app.controller.ts:36,42) ดู CORR-SSO-01 ใน scorecard

Pipeline รอง — Auto-provision username ใหม่ (checkEmpLogin)

POST /checkEmpLogin { username } (@Public())                sso-api/src/app.controller.ts:24-28

AppService.checkEmpLogin()                                    sso-api/src/app.service.ts:38-141
   ├─ query 'main' (MSSQL): SELECT * FROM SSOUsers WHERE empCode=@0 OR ssoId=@1 (parameterized — ปลอดภัยจาก SQL injection)   sso-api/src/app.service.ts:47-50
   ├─ ถ้าพบแถว → ตรวจ IsFirstTime แล้วตอบ isVerify: 'verify'/'success'/'error'
   └─ ถ้าไม่พบแถว →
        ├─ query cross-database: SELECT ... FROM dbHRMI_Center.dbo.temp_pee_na_table JOIN dbHRMI_Center.dbo.emPerson   sso-api/src/app.service.ts:78-96
        ├─ เรียก getNewUsername() → POST {SSO_END_POINT}/ssoGetNewUsername (Basic Auth)   sso-api/libs/common/get-new-username.ts:4-26
        ├─ ถอดรหัส username ที่ได้กลับด้วย aesDecrypt()   sso-api/src/app.service.ts:125
        └─ INSERT ลงตาราง SSOUsers (connection 'main')   sso-api/src/app.service.ts:121-130

checkEmpLogin เป็น endpoint เดียวใน service นี้ที่มีการเขียนฐานข้อมูล (entityManager.save(SSOUsers, ...)) และเรียก SSO ภายนอกแบบ auto-provisioning โดยไม่ต้องมี JWT ก่อน (เป็น @Public())

จุดที่ error handling พัง

AppService.signIn() มี catch block ที่เข้าถึง error.response.data?.responseCode โดยไม่เช็คว่า error.response เป็น undefined ก่อน (sso-api/src/app.service.ts:184-189) — ถ้า axios request ล่ม (timeout, DNS fail, connection refused ไปยัง SSO ภายนอก) error.response จะเป็น undefined และการเข้าถึง .data จะ throw TypeError ใหม่ที่ไม่ถูกจับ ทำให้ client ได้ 500 ที่ไม่มีข้อความอธิบายแทน error message ที่ตั้งใจไว้ ดู CORR-SSO-02 ใน scorecard