การเข้าสู่ระบบและ JWT (Auth)
ภาพรวม
โมดูล auth เป็นประตูทางเข้าของ cms-web ทั้งหมด และทำหน้าที่สองอย่างพร้อมกัน
- ให้ endpoint สำหรับ login, เลือก LINE OA, ต่ออายุ token และเพิกถอน token
- ให้ global JWT guard ที่ router นำไปครอบ route group
authedของทุกโมดูล
จุดสำคัญของระบบนี้คือการ login เป็น สองขั้น ขั้นแรกผู้ใช้ได้ token ที่ยังไม่ผูกกับ LINE OA
ขั้นที่สองผู้ใช้เลือก OA แล้วจึงได้ token ที่มี lineOaId อยู่ใน claim
ทำให้ route ส่วนใหญ่ที่ต้องการ tenant scope สามารถบังคับได้ว่าผู้ใช้ต้องเลือก OA ก่อนเสมอ
Business Flow
POST /api/auth/login— ตรวจสอบ email และ password ด้วย bcrypt แล้วออก access token พร้อม refresh token โดย token ขั้นนี้ยังไม่มี claimlineOaId- cms-web แสดงรายการ LINE OA ที่ผู้ใช้เข้าถึงได้ ให้ผู้ใช้เลือกหนึ่ง OA
POST /api/auth/login-with-line-oaผ่าน guardJwtLoginAuthซึ่งยอมรับ token ที่ยังไม่มีlineOaIdจากนั้นออก token ชุดใหม่ที่มีlineOaId,lineOaHash,organizationIdและroleIdหาก OA นั้นยังไม่มีlineOaHashระบบจะ generate ให้แล้วเขียนกลับลงตารางline_oaเป็นการ backfill- token ทุกใบที่ออกจะถูก cache ไว้ใน Redis hash ชื่อ
h_session:<userId>โดยเก็บเพียง 15 ตัวอักษรแรกของ SHA256(token) ไว้ในฟิลด์accessTokenและrefreshToken - ทุก request ที่เข้ามาที่ group
authedจะผ่านJwtAuthซึ่งจะ verify signature, ตรวจว่าต้องมีlineOaId, เติมค่าลง CLS แล้วเทียบ hash กับที่เก็บใน Redis หาก hash ไม่ตรง (ถูก revoke หรือมีการ login ที่อื่นทับ) จะตอบ 401 พร้อม codeAPP_001 POST /api/auth/refresh-access-tokenผ่าน guardRefreshTokenAuthต้องใช้ refresh token ที่ยังอยู่ใน Redis และต้องมี claimnbfจึงจะออก access token ใหม่ให้POST /api/auth/revoke-tokenลบ keyh_session:<userId>ทำให้ token ทุกใบของผู้ใช้รายนั้นใช้งานไม่ได้ทันที
ไฟล์และฟังก์ชันหลัก
โค้ดอยู่ที่ internal/modules/auth/ ประกอบด้วย controller.go, service.go, guards.go,
jwt.go, hash.go, lineoa.go, oaaccess.go และ module.go
route ของโมดูลนี้ถูก register จาก cmd/api/main.go โดยตรง ไม่ได้อยู่ใน featureModules
เพราะโมดูลนี้ต้องให้ guard กลางแก่ router ด้วย
| Method | Route | Handler | Guard |
|---|---|---|---|
| POST | /api/auth/login | m.login | ไม่มี (public) |
| POST | /api/auth/login-with-line-oa | m.loginWithLineOa | JwtLoginAuth |
| POST | /api/auth/refresh-access-token | m.refreshAccessToken | RefreshTokenAuth |
| POST | /api/auth/revoke-token | m.revokeToken | ไม่มี (มี TODO เดิมระบุว่ายังไม่ได้ตรวจ admin role) |
Guard ที่ export ให้โมดูลอื่นใช้ (internal/modules/auth/guards.go)
(*Module).JwtAuth()— global guard ของ groupauthedบังคับว่าต้องมีlineOaId(*Module).JwtLoginAuth()— guard สำหรับ route ที่ยังไม่ได้เลือก OA(*Module).RefreshTokenAuth()— guard ของ refresh flowauth.SuperAdmin()— บังคับว่าroleIdต้องเท่ากับ 1auth.InternalApiKey()— ตรวจ headerX-Internal-Keyเทียบกับ environmentINTERNAL_API_KEY
ฟังก์ชันอื่นที่โมดูลอื่นเรียกใช้ ได้แก่ auth.GenerateLineOaHash, auth.GenerateShortHash
และ interface app.AuthTokenService (IsAllowToken, RevokeToken)
จุดเชื่อมต่อกับ Service อื่น
- Permission — ตัว route ไม่ต้องการ permission เพราะเป็น public แต่โมดูลนี้เป็นตัวเติมค่าลง CLS ที่ระบบ permission ทั้งหมดพึ่งพา
- ตาราง —
user,line_oa,user_line_oa(สิทธิ์เข้าถึง OA รายบุคคล) และsystem_role - Redis — hash
h_session:<userId>สำหรับเก็บ token hash โดยใช้ namespace ตามREDIS_NAMESPACE - Environment สำคัญ —
JWT_SECRET(ถ้าไม่มีระบบจะ boot ไม่ผ่าน) และINTERNAL_API_KEY - Error code — 401 ตอบ
APP_001ส่วน refresh ที่ล้มเหลวตอบ 400 พร้อมAPP_002หรือAPP_003 - เอกสารที่เกี่ยวข้อง — สิทธิ์ผู้ใช้ Permission และ Module Gate, และ User Management