SSO-API · Auth design + Endpoint reference (branch: master)
Auth architecture
HTTP request
→ JwtAuthGuard (global, main.ts:26-27)
├── @Public() → allow
└── else → JwtStrategy.validate(payload)
├── ExtractJwt.fromAuthHeaderAsBearerToken()
└── secretOrKey: JWT_SECRET_KEY
→ AppController → AppServiceJwtService DI bug
sso-api/src/app.service.ts:27-32:
private readonly jwtService: JwtService; // declared as property
constructor(
@InjectEntityManager('main')
private readonly entityManager: EntityManager,
) {} // ← JwtService NEVER injectedแม้จะ uncomment jwtService.sign() ก็จะ crash ที่ runtime เพราะ this.jwtService เป็น undefined
Why JWT issue is commented
sso-api/src/app.service.ts:166-181:
const { requestAppId, requestId /* ,loginToken */ } = response.data;
// const payload = {
// username: body.username,
// requestAppId,
// requestId,
// loginToken,
// };
// const token = this.jwtService.sign(payload);
return {
username: body.username,
requestAppId,
requestId,
// accessToken: token,
};Inference จากโค้ด (ไม่มี comment อธิบาย intent):
- ทีม scaffold JWT infra (
JwtModule,JwtStrategy, global guard) ครบ - วางแผน wrap upstream
loginTokenเข้า JWT payload - หยุดกลางคัน —
sign()ถูก comment +JwtServiceไม่ได้ inject // TODOที่signOut/verifySessioncontrollers บ่งชี้ incomplete design
ผล: signIn เป็น @Public() และคืน {username, requestAppId, requestId} โดยไม่มี accessToken → client ไม่สามารถเรียก guarded endpoints ต่อได้ผ่าน flow นี้ alone
Endpoint catalog (7 endpoints)
Global prefix: {BASE_PATH}{API_VERSION_PATH} (UNVERIFIED)
| Method | Path | Auth | Handler | Service lines | Behavior |
|---|---|---|---|---|---|
| GET | / | @Public() | controller:18-22 | service:34-36 | Running {PROJECT_NAME} |
| POST | /checkEmpLogin | @Public() | controller:24-28 | service:38-141 | Auto-provision SSO username จาก HRMI |
| POST | /signIn | @Public() | controller:30-34 | service:143-191 | Proxy → external /ssoLogin; no JWT out |
| POST | /signOut | JWT required | controller:36-40 | service:193-218 | // TODO; proxy → /ssoLogout |
| POST | /verifySession | JWT required | controller:42-46 | service:220-223 | Stub — return { body } |
| POST | /request2FA | JWT required | controller:48-51 | service:225-254 | Proxy → /ssoRequest2FA |
| POST | /verify2FA | JWT required | controller:53-56 | service:256-284 | Proxy → /ssoVerify2FA |
Flow A — checkEmpLogin (provisioning)
POST /checkEmpLogin { username }
→ SELECT SSOUsers WHERE empCode OR ssoId = username
├── exists → return { isVerify: verify|success|error }
└── missing
→ SELECT temp_pee_na_table JOIN emPerson (dbHRMI_Center cross-schema)
→ POST {SSO_END_POINT}/ssoGetNewUsername (Basic Auth)
→ INSERT SSOUsers
→ return { isVerify: verify }Evidence — app.service.ts:38-141, cross-schema query ที่ app.service.ts:78-96:
const empData = await this.entityManager.query(
`SELECT ... FROM dbHRMI_Center.dbo.temp_pee_na_table t
JOIN dbHRMI_Center.dbo.emPerson p ON ...
WHERE ... = @0`,
[username],
);ใช้ parameterized @0 — ไม่มี SQL injection ที่จุดนี้
temp_pee_na_table shared กับ chat-center-api — ไม่มี ownership contract (SEC-SSO-08)
Flow B — signIn (broken JWT chain)
POST /signIn { username, password }
→ AES-256-CBC encrypt credentials (aes-encrypt.ts)
→ POST {SSO_END_POINT}/ssoLogin (Basic Auth: SSO_USERNAME/SSO_PASSWORD)
→ upstream returns { requestAppId, requestId, loginToken }
→ loginToken DISCARDED (commented out)
→ return { username, requestAppId, requestId } // NO accessTokenEvidence — app.service.ts:143-191
AES keys จาก AES_SECRET_KEY + AES_INIT_VECTOR env
Flow C — 2FA (real proxy, blocked by JWT)
request2FA และ verify2FA ไม่ใช่ stub — เป็น HTTP proxy จริงไป upstream:
| Step | Upstream | Evidence |
|---|---|---|
request2FA | POST /ssoRequest2FA | app.service.ts:225-254 |
verify2FA | POST /ssoVerify2FA + header token | app.service.ts:256-284 |
ปัญหา: ทั้งคู่ต้องการ Bearer JWT แต่ signIn ไม่ออก token → client ต้องได้ JWT จากแหล่งอื่น (หรือ flow นี้ใช้ไม่ได้)
The real stub — verifySession
sso-api/src/app.service.ts:220-223:
async verifySession(body: verifySessionDto) {
return { body };
//
}คืน request body กลับมาเฉย ๆ — ไม่ verify อะไรเลย
External SSO integration
| Upstream path | Called from | Auth |
|---|---|---|
/ssoLogin | signIn | Basic Auth + AES body |
/ssoLogout | signOut | Basic Auth + AES |
/ssoRequest2FA | request2FA | Basic Auth |
/ssoVerify2FA | verify2FA | Basic Auth + token header |
/ssoGetNewUsername | get-new-username.ts | Basic Auth |
Inferred URLs จาก sibling repos (ไม่ใช่จาก sso-api เอง):
- UAT:
https://uatssoapi.redcross.or.th/SSOService_dev/api(2way-api/.env.example:41) - Prod:
https://ssoapi.redcross.or.th/SSOService/api(2way-api/.env.prod:40)