登入(含 MFA OTP)(Login / OTP Verify)
功能群:認證|認證資料模型、登入流程、匿名態特性先讀 功能群總覽
事實基準:2026-07-05 從 FE(
views/auth/Login.vue、OtpVerification.vue)/ BE(api/auth、jedi-login / jedi-mfa / jedi-captcha 套件)code 掃出;表結構取自db_schema.json(publicschema);error code 對 jedi-login / jedi-mfa /common/code/error_code.py變更紀錄:2026-07-09 FR-047 已知坑修復 ——
/otp-resendtype/mfa_typekey bug 修正(route 改讀type、serializerOneOf(["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-02 MFA OTP 驗證 + TOTP 啟用
| 項目 | 內容 |
|---|---|
| 角色 | 需 MFA 的使用者 |
| 前置條件 | POST /login 回 mfa_required=true,帶
uid + mfa_type 到 OTP 頁 |
| 產出 / 後置條件 | OTP 正確 → generate_jwt_token 發 access + refresh;失敗
10s 冷卻、5 次退回登入 |
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.py(captcha_service.verify_turnstile_token,登入前) |
| 登入失敗 | users.login_failed_count++ +
last_login_failed_time,即時 commit |
jedi-login LoginDomainService.is_password_correct |
| 失敗 ≥ 3 且 15 分內 | 帳號鎖定
LOGIN_401004(MAX_LOCK_COUNT=3、USER_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)。
狀態呈現: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 data(LoginResponse):
| 欄位 | 型別 | 說明 |
|---|---|---|
| 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::LoginRoute、app/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-resendserializer 宣告欄位為type,route 讀mfa_typekwarg(FE 送type)→ 讀不到而靜默 default 為
7. 前端檔案地圖(compliance-manager-fe/)
| 檔案 | 角色 |
|---|---|
src/views/auth/Login.vue |
登入頁(帳密 / LDAP / captcha / 記住我 / 分流) |
src/views/auth/OtpVerification.vue |
OTP 驗證頁(6 格 / 重送 / TOTP↔︎email 切換) |
src/utils/authPersist.js・sessionShare.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 LoginTokenService(login_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
§2;login_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 |
| 記住我 | rememberMe → cm_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. 邊界情況與已知坑
- MFA 是全域旗標:
MFA_REQUIRED環境變數對所有使用者生效,無 per-tenant / per-user override;使用者mfa_type只決定 email vs totp 通道。 /otp-resend欄位不一致已修(2026-07-08 FR-047):原本 serializer 宣告type、route 卻讀mfa_typekwarg → FE 送type讀不到、靜默 defaultemail(TOTP 型使用者按重送實際走 email)。已改 route 讀kwargs.get("type")、serializertype加OneOf(["email"])(resend 僅支援 email、收到 totp 等值 400 fail loudly,不再靜默 fallback)。- 鎖定門檻硬編:失敗 3 次 / 鎖 15
分(
MAX_LOCK_COUNT/USER_LOCK_TIMEconfig);鎖定狀態即時 commit 到 DB(叢集一致)。 cm_persist_mode心跳陷阱:=session時,心跳(cm_last_active)過期(>8s 且無其他分頁)判定瀏覽器關閉 → 清 token;曾被誤判為「登入壞掉」(pilot 截圖教訓)。維護 session 相關邏輯注意此心跳機制。- MFA 待驗證期 token
未發:
mfa_required=true時/login回應 token 為 null,須等/otp-verify成功才發——FE 已正確等待,勿在 login 回應就存 token。 - 欄名
topt_secret:users 表 MFA 密鑰欄拼寫為topt_secret(非totp_secret);另有獨立totp_secrets表存正式 secret+uri(見 _overview §6)。 - captcha 在帳密驗證前:captcha 失敗不會增加
login_failed_count(先擋 captcha 再驗帳密)——避免 captcha 錯誤污染鎖定計數。 - OTP 發送有 server-side 節流(2026-07-09 FR-047,防 email
bombing):登入首發 + 重送共用 jedi-mfa redis 節流(key
otp_cooldown:{user_id}、OTP_COOLDOWN_SECONDS預設 60s);cooldown 內再發回 429MFA_429001(OtpResendTooFrequentError),responsedata.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/ 部署文件(memoryreference_dev_login) - 導航路徑:
/或/login;MFA 時自動轉/otp-verify - 前置資料:啟用帳號(
status=1);TOTP 測試需先綁定驗證器 - E2E:
compliance-manager-test/repo;登入為所有 E2E 前置(auth-states 重產見 memoryreference_e2e_env_switch_and_auth) - 相關文件:忘記 / 重設密碼見 忘記密碼 / 重設密碼、認證模型見 功能群總覽;DB / API 全量見 GAI-SD-02/04(Security Design)