手冊首頁 / 認證(登入前) / 登入

登入(含 MFA OTP)(Login / OTP Verify)

功能群:認證|認證資料模型、登入流程、匿名態特性先讀 功能群總覽

事實基準:2026-07-05 從 FE(views/auth/Login.vueOtpVerification.vue)/ BE(api/auth、jedi-login / jedi-mfa / jedi-captcha 套件)code 掃出;表結構取自 db_schema.jsonpublic schema);error code 對 jedi-login / jedi-mfa / common/code/error_code.py

變更紀錄:2026-07-09 FR-047 已知坑修復 —— /otp-resend type/mfa_type key bug 修正(route 改讀 type、serializer OneOf(["email"]) fail loudly);新增 OTP 發送 server-side 節流(jedi-mfa redis,防 email bombing,MFA_429001 + retry_after);修重整直達 auth 頁 data-theme 未套、OTP 輸入框露暗色底

1. 功能描述

登入頁是系統的匿名入口:帳密(或 LDAP)+ Cloudflare Turnstile captcha 登入,依 MFA_REQUIRED 環境變數與使用者 mfa_type 決定是否走 MFA OTP(轉 OTP 驗證頁),並處理首次 / 過期改密碼分流。OTP 頁(/otp-verify)輸入 6 位碼驗證後才發 JWT。本 spec 涵蓋 login 與 otp-verify 兩條 route。

主要使用者:所有未登入者。§3 權限與其他頁不同——匿名可達(見 _overview §5)。

角色速覽(匿名態,見 _overview §5):

角色 一句話
未登入者 登入 / OTP 驗證(/login/otp-verify/otp-qrcode 匿名可達;/totp-secret/logout/refresh 需 token)

1.1 功能總覽(本頁全部功能)

# 功能 說明 位置 詳述
1 帳密登入 帳號 + 密碼 → POST /login 登入頁 UC-LG-01
2 認證方式切換 密碼 / LDAP(provider 登入頁 tab §6.2
3 Captcha Cloudflare Turnstile,登入前驗證 登入頁 §11
4 記住我 cm_persist_mode session vs local 登入頁 §12
5 登入失敗鎖定 失敗 3 次鎖 15 分鐘 BE §4
6 MFA 分流 mfa_required → 轉 OTP 頁(token 未發) 分流 UC-LG-02
7 改密碼分流 change_password → 轉重設密碼頁 分流 忘記密碼 / 重設密碼
8 OTP 驗證 6 位碼(email / TOTP),驗證後發 JWT OTP 頁 UC-LG-02
9 OTP 重送 email 型重送(60s 冷卻) OTP 頁 §6.2
10 TOTP 啟用 QR 掃碼綁定(/totp-secret + /otp-qrcode (登入態) UC-LG-02
11 改用 Email 驗證 TOTP 型切回 email OTP OTP 頁 §6.1
12 Token refresh / logout /refresh 換 access、/logout 撤銷 全站 §6.1

UC(§2)展開 登入分流(UC-LG-01)與 MFA/OTP(UC-LG-02)。

2. Use Case

角色速覽(匿名態,見 _overview §5):

角色 一句話
未登入者 登入 / OTP 驗證(/login/otp-verify/otp-qrcode 匿名可達;/totp-secret/logout/refresh 需 token)

流程圖視覺慣例:菱形 = 判斷、橘底 = 分流 / 例外、紅框 = 錯誤(標 error code)、綠框 = 成功終點、虛線 = 可選路徑。

UC-LG-01 登入(captcha + 鎖定 + 分流)

項目 內容
角色 未登入者
前置條件 有效帳號(status=1 啟用);captcha 通過
產出 / 後置條件 一般 → 發 JWT + 存 token;需 MFA → 轉 OTP 頁(token 未發);需改密碼 → 轉重設頁
uc_lg_01 UC-LG-01 登入(captcha + 鎖定 + 分流) start 登入頁(匿名)輸入帳密 + captcha post POST /login {username, password, provider, cf_turnstile_token} start->post cap captcha 有效?(Turnstile) post->cap caperr UnauthorizedError(captcha 失敗) cap->caperr lock 帳號已鎖定?(失敗≥3 且 15 分內) cap->lock lockerr LOGIN_401004 登入失敗帳號已鎖定 lock->lockerr cred 帳密正確 & 帳號啟用? lock->cred crederr LOGIN_401001 登入失敗(failed_count++) / 401011 停用 / 401012 未啟用過期 cred->crederr branch 回應分流 cred->branch mfa mfa_required → 轉 /otp-verify (token 尚未發) branch->mfa 需 MFA chpwd change_password → 轉 /reset-password branch->chpwd 需改密碼 ok 發 JWT + 存 token write login_tokens / login_logs → /project/dashboard branch->ok 一般
UC-LG-01:captcha → 鎖定檢查 → 帳密驗證 → MFA/改密碼/一般 三分流

UC-LG-02 MFA OTP 驗證 + TOTP 啟用

項目 內容
角色 需 MFA 的使用者
前置條件 POST /loginmfa_required=true,帶 uid + mfa_type 到 OTP 頁
產出 / 後置條件 OTP 正確 → generate_jwt_token 發 access + refresh;失敗 10s 冷卻、5 次退回登入
uc_lg_02 UC-LG-02 MFA OTP 驗證 + TOTP 啟用 enroll (首次啟用 TOTP,需登入態) POST /totp-secret → secret + uri GET /otp-qrcode/{uid} → QR PNG 掃碼綁定驗證器 otp /otp-verify 頁:輸入 6 位碼 verify POST /otp-verify {uid, type(email/totp), code} otp->verify resend email 型:POST /otp-resend 重送(60s 冷卻) otp->resend chk OTP 正確? verify->chk err MFA_401001 驗證失敗 / AUTH_MFA_401001 TOTP 驗證失敗 (10s 冷卻;5 次退回登入) chk->err ok 發 JWT(generate_jwt_token) 存 access + refresh token chk->ok
UC-LG-02:OTP 驗證(email/TOTP)+ TOTP 啟用(QR 掃碼)→ 發 JWT

3. 權限矩陣

本群為匿名態(見 _overview §5)。「權限」= 端點是否需認證:

操作 FE 判定 BE 強制 BE 檢查位置
登入 匿名頁 無 JWT;captcha + 帳密驗證 api/auth/routes/login_route.py::LoginRoute
OTP 驗證 匿名頁(帶 uid) 無 JWT;驗 OTP api/auth/routes/otp_route.py::MfaVerifyRoute
OTP 重送 匿名 無 JWT otp_route.py::OtpCodeResendRoute
取 TOTP QR 匿名 無 JWT(帶 uid path) otp_route.py::TotpQrcodeRoute
註冊 TOTP secret 需 JWT(登入態) otp_route.py::TotpSecretRoute
refresh / logout 需 JWT(refresh / access token) login_route.py::LoginRefreshRoute / LogoutRoute

4. 狀態機與前置條件

gate 效果 出處
captcha 無效 擋下(UnauthorizedError),不進帳密驗證 login_route.pycaptcha_service.verify_turnstile_token,登入前)
登入失敗 users.login_failed_count++ + last_login_failed_time,即時 commit jedi-login LoginDomainService.is_password_correct
失敗 ≥ 3 且 15 分內 帳號鎖定 LOGIN_401004MAX_LOCK_COUNT=3USER_LOCK_TIME=900s jedi-login is_user_locked_within_limit
帳號停用 / 未啟用 LOGIN_401011 / 401012;長期未用 401008 jedi-login status 檢查
MFA_REQUIRED=true mfa_required不發 JWT,轉 OTP 頁 login_route.py(env 全域旗標)
登入成功 login_failed_count=0 + last_login_success_time;發 JWT 寫 login_tokens jedi-login

5. UI 設計

📸 實際畫面截圖待補(一律亮色模式 + 登出態擷取;截圖另有專責 session)。預計補到 assets/img/: ① 登入頁(帳密 + captcha + 記住我) login-page.png ② OTP 驗證頁(6 格 + 重送) otp-verify.png ③ TOTP QR 綁定 totp-qrcode.png ④ 鎖定 / 錯誤提示 login-error.png

版面骨架與區塊職責:本質兩畫面——登入卡(帳密/LDAP + Captcha + 記住我 + 鎖定/分流,→ UC-LG-01)與 OTP 驗證卡(6 碼 email/TOTP + 重送 + TOTP 啟用,→ UC-LG-02)。

匿名入口:登入頁(/login)→ 分流 → OTP 驗證頁(/otp-verify) 本頁無登入態;成功才發 JWT(本質兩畫面,不同 route) 開啟:UC-LG-01 登入(captcha + 鎖定 + 分流) 登入卡:帳密 / LDAP + Captcha + 記住我 密碼登入(password) LDAP(provider) 帳號 username 密碼 password(顯示切換) Cloudflare Turnstile(cf_turnstile_token,登入前驗) 主題隨深淺色同步;驗證後才進帳密驗證 記住我(cm_persist_mode) 開啟:忘記密碼 / 重設密碼 spec 忘記密碼?(連結) 登入 → POST /login 三分流(依 response 旗標) mfa_required → 轉 OTP 頁(右卡,token 未發) change_password → 轉重設密碼頁 一般 → 發 JWT 進 dashboard 失敗鎖定 gate 失敗 3 次鎖 15 分(LOGIN_401004);停用/未啟用 401011/401012 → UC-LG-01(點我開啟) POST /login(captcha → 鎖定檢查 → 帳密驗證 → 分流) 開啟:UC-LG-02 MFA OTP 驗證 + TOTP 啟用 OTP 驗證卡:email / TOTP 6 碼 自動跳格 / 貼上 / 倒退;填滿自動送出 驗證 → POST /otp-verify 失敗 / 重送冷卻(條件層) 失敗 10s 冷卻、5 次退回登入;重送 60s(email 型) 重送 OTP 改用 Email 驗證 POST /otp-resend(type/mfa_type 已知欄位不一致,見 §12) TOTP 啟用(登入態,QR 掃碼綁定) QR code POST /totp-secret(註冊 secret) GET /otp-qrcode/{uid}(掃碼) 綁定後回登入切 TOTP 通道 → UC-LG-02(點我開啟) POST /otp-verify(email/TOTP → generate_jwt_token) GET /otp-qrcode/{uid} ・ POST /totp-secret 彩色=主要互動區塊,整塊可點擊跳轉本頁 UC/§6|灰=本頁結構|虛線框=dialog/條件顯示層 API 僅列代表性 2~3 條,完整規格見 §6;本質兩畫面:登入卡(/login)與 OTP 驗證卡(/otp-verify)
登入頁(登入卡:captcha/鎖定/分流)+ OTP 驗證頁(OTP 卡:驗證/重送/TOTP 啟用),可點跳轉 UC-LG-01/UC-LG-02

狀態呈現:captcha 主題隨深淺色同步;密碼欄顯示切換;OTP 6 格自動跳格 / 貼上 / 倒退、填滿自動送出;OTP 失敗 10s 冷卻、5 次退回登入;重送 60s 冷卻。無 socket

6. API 規格

Envelope:成功 {"status": true, "data": …}、失敗 {"error_code": "…", "msg": "…"}

6.1 總清單(本頁呼叫的全部 endpoint)

分類 Method + Path 說明 完整規格
登入 POST /login 帳密 + captcha 登入,回分流旗標 §6.2
MFA POST /otp-verify 驗 OTP,發 JWT §6.2
MFA POST /otp-resend email OTP 重送 §6.2
MFA GET /otp-qrcode/{uid} TOTP QR PNG GAI-SD-02
MFA POST /totp-secret 註冊 TOTP secret(需 JWT) GAI-SD-02
Token POST /refresh 換新 access token(需 refresh) GAI-SD-02
Token POST /logout 撤銷 token(login_tokens.is_revoked=1 GAI-SD-02
Captcha POST /verify-captcha Turnstile 驗證(captcha 模組) GAI-SD-02

6.2 核心 endpoint(欄位對照 serializer / service,出處標段末)

[POST] /login

Request:

{
  "username": "string",   // 
  "password": "string",   // 
  "provider": "password", // 預設 password|ldap
  "cf_turnstile_token": "string" // 
}

流程:取 client IP → 驗 captcha → login_service.process_login → 依 MFA_REQUIRED 決定發 token 或走 MFA。

Response dataLoginResponse):

欄位 型別 說明
access_token string null 時代表待 MFA
refresh_token string null 時代表待 MFA
user_info object {uid, login_name, nickname, email, is_admin, tenant_id, org_unit_id}
tenants array
org_units array
change_password bool
change_uid
mfa_required bool
mfa_type email/totp

出處:api/auth/routes/login_route.py::LoginRouteapp/auth/service/login_service.py、jedi-login process_login

[POST] /otp-verify

Request:

{
  "uid": "string",  // 
  "type": "email",  // email/totp
  "code": "string"  // 6 
}

驗 OTP(email 走 EmailMfaService、totp 走 TotpMfaService);失敗擋 MFA_401001(TOTP 為 AUTH_MFA_401001);成功 generate_jwt_token(user.uid) 發 JWT。Response:{result, access_token, refresh_token}。出處:api/auth/routes/otp_route.py::MfaVerifyRoute、jedi-mfa。

[POST] /otp-resend ・ TOTP 啟用

/otp-resend{uid, type} → email 型重送新 OTP。TOTP 啟用(需登入態):POST /totp-secret 註冊 secret + uri → GET /otp-qrcode/{uid} 取 QR PNG 掃碼。出處:otp_route.py::OtpCodeResendRoute / TotpSecretRoute / TotpQrcodeRoute

⚠️ 已知 BE bug/otp-resend serializer 宣告欄位為 type,route 讀 mfa_type kwarg(FE 送 type)→ 讀不到而靜默 default 為 email(見 §12)。

7. 前端檔案地圖(compliance-manager-fe/)

檔案 角色
src/views/auth/Login.vue 登入頁(帳密 / LDAP / captcha / 記住我 / 分流)
src/views/auth/OtpVerification.vue OTP 驗證頁(6 格 / 重送 / TOTP↔︎email 切換)
src/utils/authPersist.jssessionShare.js token 持久化(cm_persist_mode)+ 心跳(cm_last_active
src/stores/userStore.js 使用者 / tenant / org 狀態
src/config/router/index.js:671-692 route login//login)、otp-verify//otp-verify
src/config/api/api.js:14-20 LOGIN / LOGOUT / REFRESH / OTP_VERIFY / OTP_RESEND / TOTP_QRCODE / TOTP_SECRET

8. 後端檔案地圖(本頁核心鏈路)

鏈路 Route App Service 底層
登入 api/auth/routes/login_route.py::LoginRoute app/auth/service/login_service.py::process_login jedi-login LoginService / LoginDomainService(鎖定 / 狀態)、jedi-captcha
OTP api/auth/routes/otp_route.py::MfaVerifyRoute jedi-mfa MFAService / EmailMfaService / TotpMfaService
refresh / logout login_route.py::LoginRefreshRoute / LogoutRoute jedi-login LoginTokenServicelogin_tokens jti 撤銷)

DDD 提醒:登入 / MFA 核心 domain 在 jedi-login / jedi-mfa 外部套件;主專案 route 做 HTTP 邊界 + captcha + MFA 分流編排。登入成功後由 jedi-auth user_lookup_loader 於後續請求解 JWT 注入 RLS session 變數(tenant / org paths)。

9. DB

9.1 資料表總清單(本頁讀寫的全部表)

讀/寫 說明 欄位詳述
public.users 讀 / 帳密驗證;login_failed_count / last_login_* 更新;mfa_type / topt_secret _overview §2
public.login_tokens 發 JWT 寫 jti;logout 改 is_revoked=1 _overview §2
public.totp_secrets 讀 / TOTP 註冊寫 secret+uri;驗證讀 _overview §2
public.login_logs 登入稽核 _overview §2

9.2 ER 圖(本頁讀寫範圍)

_overview §2 認證資料模型assets/au-auth-data-model.dot)——本頁讀寫 users / login_tokens / totp_secrets / login_logs。

9.3 核心欄位

users 認證相關欄位(password / status 0-1--1--2 / login_failed_count / last_login_success_time / last_login_failed_time / topt_secret / mfa_type)見 _overview §2login_tokens(jti / token_type / user_identity / is_revoked / expires)、totp_secrets(secret / uri)同。

10. 頁面邏輯與資料對應

登入時序:填帳密 + captcha → POST /login → 依回應分流(一般存 token 進 dashboard / mfa_required 轉 OTP / change_password 轉重設)。OTP:填 6 碼 → POST /otp-verify → 存 token。

關鍵欄位對應(FE ↔︎ API):

畫面元素 FE state API 欄位
帳號 / 密碼 user.username / user.password username / password
認證方式 user.provider provider(password/ldap)
captcha token turnstileToken cf_turnstile_token
記住我 rememberMecm_persist_mode —(前端持久化)
OTP 6 碼 v1..v6 code
MFA 型別 / uid mfa_type / user_uid type / uid

Token 流:一般登入回 access_token + refresh_token;MFA 待 OTP 驗證後才回 token。cm_persist_mode=local(記住我)token 跨瀏覽器重啟保留;=session 心跳過期清 token(§12)。

錯誤對應LOGIN_401001(登入失敗)/ 401004(鎖定)/ 401011(停用)/ 401012(未啟用過期)/ 401008(長期未用)/ LOGIN_404001(不存在);OTP MFA_401001 / AUTH_MFA_401001(TOTP)→ toast。

11. 背景行為與外部依賴

類型 內容
Captcha Cloudflare Turnstile;sitekey 由 VITE_TURNSTILE_SITE_KEY;BE 以 jedi-captcha 驗 token + client IP(登入前)
Email OTP email 型 MFA 由 EmailMfaService 產碼 + 非同步寄信(jedi-notification / SMTP,見 forget-password §11
JWT jedi-login / flask-jwt-extended;access + refresh;login_tokens jti 供撤銷;/refresh 換新 access
RLS 登入本身不設 RLS 變數;後續請求由 jedi-auth user_lookup_loader 解 JWT 注入 tenant / org paths
鎖定 / 冷卻 登入失敗 3 次鎖 15 分(BE);OTP 失敗 10s、重送 60s(FE)
Socket 本頁
Anthropic / Claude (認證流程不涉 LLM)

12. 邊界情況與已知坑

  1. MFA 是全域旗標MFA_REQUIRED 環境變數對所有使用者生效,無 per-tenant / per-user override;使用者 mfa_type 只決定 email vs totp 通道。
  2. /otp-resend 欄位不一致已修(2026-07-08 FR-047):原本 serializer 宣告 type、route 卻讀 mfa_type kwarg → FE 送 type 讀不到、靜默 default email(TOTP 型使用者按重送實際走 email)。已改 route 讀 kwargs.get("type")、serializer typeOneOf(["email"])(resend 僅支援 email、收到 totp 等值 400 fail loudly,不再靜默 fallback)。
  3. 鎖定門檻硬編:失敗 3 次 / 鎖 15 分(MAX_LOCK_COUNT / USER_LOCK_TIME config);鎖定狀態即時 commit 到 DB(叢集一致)。
  4. cm_persist_mode 心跳陷阱=session 時,心跳(cm_last_active)過期(>8s 且無其他分頁)判定瀏覽器關閉 → 清 token;曾被誤判為「登入壞掉」(pilot 截圖教訓)。維護 session 相關邏輯注意此心跳機制。
  5. MFA 待驗證期 token 未發mfa_required=true/login 回應 token 為 null,須等 /otp-verify 成功才發——FE 已正確等待,勿在 login 回應就存 token。
  6. 欄名 topt_secret:users 表 MFA 密鑰欄拼寫為 topt_secret(非 totp_secret);另有獨立 totp_secrets 表存正式 secret+uri(見 _overview §6)。
  7. captcha 在帳密驗證前:captcha 失敗不會增加 login_failed_count(先擋 captcha 再驗帳密)——避免 captcha 錯誤污染鎖定計數。
  8. OTP 發送有 server-side 節流(2026-07-09 FR-047,防 email bombing):登入首發 + 重送共用 jedi-mfa redis 節流(key otp_cooldown:{user_id}OTP_COOLDOWN_SECONDS 預設 60s);cooldown 內再發回 429 MFA_429001OtpResendTooFrequentError),response data.retry_after 帶剩餘秒數供 FE 精確倒數。原本只有 FE cooldown(ref、重整即重置),重整 / 換瀏覽器 / 直打 API 都能繞——已補 BE 權威節流 + FE localStorage 持久化倒數。

13. 開發與驗證

  • 跑起來:BE python main_socketio.py(port 8000,log 在 log/app.log);FE 在 compliance-manager-fe/ 起 Vite dev server。需設 VITE_TURNSTILE_SITE_KEY(FE)+ captcha 後端 key;MFA_REQUIRED 控 MFA。BE 改 service code 後必須重啟
  • 測試帳號:dev 環境登入帳號密碼見 .env / 部署文件(memory reference_dev_login
  • 導航路徑//login;MFA 時自動轉 /otp-verify
  • 前置資料:啟用帳號(status=1);TOTP 測試需先綁定驗證器
  • E2Ecompliance-manager-test/ repo;登入為所有 E2E 前置(auth-states 重產見 memory reference_e2e_env_switch_and_auth
  • 相關文件:忘記 / 重設密碼見 忘記密碼 / 重設密碼、認證模型見 功能群總覽;DB / API 全量見 GAI-SD-02/04(Security Design)