#history#auth#tenant#session
ภาพรวม
ชั้นตัวตนผู้ใช้/ผู้เช่าถูกเจาะซ้ำ 2026-10-20 .. 2026-10-29: token หมดอายุแล้วหลุด, อีเมล Google ไม่ถูกพิสูจน์, โซนส่วนกลางเปิดกว้าง, ผู้เรียกเลือกฐานเองได้, รหัสผ่านเป็นข้อความธรรมดา
อาการ
- T1 (6ba1a6d9, 9a2ce199): 401 เมื่อ token หมดอายุ ผู้ใช้เด้งออกและเสียหน้าที่ทำอยู่ (ชื่อ commit: 401 token refresh & recovery; 10-27 เพิ่ม modal ล็อกอินใหม่)
- T2 (6e9ae03e, faa6e6d0):
/api/auth/googleรับemailจาก body เมื่อไม่มีidToken(diff ลบbody?.email) = ล็อกอินเป็นอีเมลใดก็ได้; มี token แต่ไม่เช็กaud/email_verified; callback ไม่มีstate(login CSRF) - T3 (7ab65d38, faa6e6d0): production ไม่ตั้ง
ADMIN_API_KEYแล้วเงียบ ๆ ใช้กุญแจสำรองที่รู้กันทั่วไป;/api/admin/*ใช้requireAdminผู้ดูแล workspace เข้าส่วนกลางได้;/api/workspace/*ฝั่ง backend ตรวจแค่กุญแจ - T4 (a47413ec, 7ab65d38): route แผนที่ไม่มี Auth ให้
?db=/?provider=เลือกฐานใดก็ได้; telemetry (fraudwarning,workmonitor) ไม่มี session แล้วตกไปฐานสาธิต; สลับฐานโดยไม่ตรวจรายชื่อที่เข้าได้ - T5 (13f953cc, a94b0748):
sml_user_list.user_passwordเก็บตามที่ admin กรอก - T6 (a3809aba):
/login,/select-database,/adminยิง/query/listก่อนล็อกอิน
สาเหตุ (root cause)
- T1: refresh token ใช้ครั้งเดียว คำขอขนานแย่งกันหมุน (อนุมานจากโค้ดที่เพิ่ม) และ 401 ไม่จำหน้าปัจจุบัน
- T2: ไว้ใจข้อมูลจาก client แทนผลตรวจ token
- T3: fallback เป็นค่าคงที่ใน production; คำว่า "Admin" ปนระหว่างผู้ดูแล workspace กับส่วนกลาง
- T4: เลือกผู้เช่าจากข้อมูลที่ผู้เรียกคุมหรือ default แทน session
- T5: ไม่มี hash ตอนเขียน
- T6: โหลดข้อมูลที่ต้องมี session ในหน้าสาธารณะ
วิธีแก้
- T1:
rotateRefreshTokenแบบ single-flight (map คำขอที่กำลังหมุน + แคชผลสั้น ๆ); 401 เก็บreturnToใน sessionStorage, ยิงเหตุการณ์sml:session-expired, banner/modal ให้ล็อกอินใหม่แล้วกลับหน้าเดิม - T2: บังคับ
idToken(ไม่มี = 401NO_ID_TOKEN), ตรวจaudและemail_verified;stateเป็น cookie httpOnly 10 นาที ใช้ครั้งเดียว เทียบด้วยtimingSafeEqual - T3:
getAdminApiKey()คืน null ใน production ถ้าไม่ตั้ง => 503NO_ADMIN_API_KEY;/api/admin/*ใช้requireSystemAdmin(session.mode === "admin"; สาธิตอ่านอย่างเดียวDEMO_READ_ONLY);isWorkspaceAdminกั้น workspace route - T4: tile route ล็อกฐานสาธิต; telemetry ไม่มี
session.database= 401; สลับฐานได้เฉพาะavailableCompaniesหรือฐานปัจจุบัน (403DB_FORBIDDEN) - T5:
HashPassword(bcrypt) ตอน admin create/update + ขยายคอลัมน์เป็น 255;a94b0748เพิ่ม/api/auth/change-passwordที่ยืนยันรหัสเดิมกับ/auth/loginปฏิเสธบัญชี Google/สาธิต - T6:
isPublicOrNoDbPath()ข้ามการโหลด และตั้งloadedเมื่อมีคำตอบสำเร็จเท่านั้น
commit ที่เกี่ยวข้อง
6ba1a6d9— refresh single-flight + กู้คืนหลัง 401 (2026-10-20);9a2ce199modal (10-27; ตามข้อความ commit)6e9ae03e— Google PDPA multi-tenant auth (2026-10-24; ตรวจเฉพาะ diff google route)faa6e6d0— ตรวจ Google ID token +isWorkspaceAdmin(2026-10-24)7ab65d38— fail-closed key, OAuth state, system-admin-only, DB-switch check, telemetry (2026-10-24)a47413ec— ล็อกฐานของ tile route (2026-10-24)a94b0748(10-24),13f953ccbcrypt,a3809aba(10-29)
โค้ดปัจจุบันที่ป้องกัน
frontend/src/lib/api-server.ts:60getAdminApiKey,:89rotateRefreshToken,:359requireSystemAdminfrontend/src/app/api/auth/google/route.ts:58(state),:233(idToken),:252-260(aud, email_verified);oauth-state.ts:15-23frontend/src/app/api/auth/databases/route.ts:289-296;frontend/src/app/(admin)/layout.tsx:21frontend/src/lib/api-client.ts:77;frontend/shared/layout/PersistentWorkspaceHost.tsx:306,456backend/internal/adapter/inbound/http/monitormap/handler.go:122;backend/internal/domain/identity/identity.go:50,64;admin/handler.go:283,340- ช่องว่างจากการอ่านโค้ด (ไม่ใช่ประวัติ):
Authenticateยังรับรหัสข้อความธรรมดาของแถวเก่า และ diff ที่ตรวจไม่มีการ migrate แถวเก่า
บทเรียน / กฎที่ได้
- ผู้เช่ามาจาก session เท่านั้น ไม่รับจาก query string, body หรือ default ที่หละหลวม
- ตัวตนต้องมาจากผลตรวจ token (aud, verified) ไม่ใช่ข้อมูลจาก client
- กุญแจระบบไม่มี fallback ใน production ให้ fail-closed (503)
- แยกผู้ดูแล workspace จากผู้ดูแลส่วนกลางด้วย
session.mode - refresh token ใช้ครั้งเดียว ต้อง single-flight และจำหน้าที่ทำอยู่
ดูเพิ่ม
ADR-0003 ข้อมูลผู้ใช้และสิทธิ์อยู่ใน DB ของแต่ละผู้เช่า ไม่ใช่ control-plane · เซสชันและการยืนยันตัวตนฝั่ง frontend (Auth session & tokens) · Google Login, Multi-Tenant PDPA และหน้าเลือกโมดูลหลัง Login (Google OAuth, Tenant Isolation, Home Hub) · บทเรียนช่องโหว่เว็บ XSS และ open redirect (Web Vulnerabilities) · history-overview
ที่มาในโค้ด
- git:6ba1a6d9, git:faa6e6d0, git:7ab65d38, git:13f953cc
- frontend/src/lib/api-server.ts:60, :89, :359
- frontend/src/app/api/auth/google/route.ts:233, :252