忘記密碼 / 重設密碼(Forget / Reset Password)
事實基準:2026-07-05 從 FE(
views/auth/ForgotPassword.vue、ChangePassword.vue)/ BE(api/auth、jedi-auth 套件)code 掃出;表結構取自db_schema.json(publicschema);error code 對jedi_auth/common/enum/error_code.py
1. 功能描述
忘記密碼頁讓匿名使用者以 email 申請密碼重設;系統寄出含 token
的連結,使用者於重設密碼頁憑 token
設新密碼。重設密碼頁(ChangePassword.vue)以
type 統一服務忘記密碼 / 首次登入設密碼 /
密碼過期強制變更三情境。本 spec 涵蓋 forget-password 與
reset-password 兩條 route。
主要使用者:忘記密碼 / 需改密碼的匿名使用者。§3 為匿名態(見 _overview §5)。
角色速覽(匿名態,見 _overview §5):
| 角色 | 一句話 |
|---|---|
| 匿名使用者 | 申請重設 / 憑 token
設新密碼(/forget-password、/reset-password
皆匿名可達) |
1.1 功能總覽(本頁全部功能)
| # | 功能 | 說明 | 位置 | 詳述 |
|---|---|---|---|---|
| 1 | 申請重設 | 輸入 email → 寄重設連結(5s 冷卻) | 忘記密碼頁 | UC-FP-01 |
| 2 | 申請節流 | 5 分鐘內僅一筆待用請求 | BE | §4 |
| 3 | token 驗證 | 進重設頁先驗 token(存在 / 未過期 / 未使用) | 重設密碼頁 | UC-FP-01 |
| 4 | 情境動態標題 | type 0 首次 / 2 忘記 / 3 過期 | 重設密碼頁 | §10 |
| 5 | 新密碼設定 | ≥12 字 + 大小寫 + 數字複雜度 | 重設密碼頁 | §4 |
| 6 | 確認密碼 | 須與新密碼相符 | 重設密碼頁 | §6.2 |
| 7 | 密碼防重用 | 不可與近 3 次相同 | BE | §4 |
| 8 | 完成轉登入 | 成功清 localStorage → 轉登入 | 重設密碼頁 | UC-FP-01 |
UC(§2)展開 忘記 → token → 重設 完整鏈路(UC-FP-01)。
2. Use Case
角色速覽(匿名態,見 _overview §5):
| 角色 | 一句話 |
|---|---|
| 匿名使用者 | 申請重設 / 憑 token
設新密碼(/forget-password、/reset-password
皆匿名可達) |
流程圖視覺慣例:菱形 = 判斷、橘底 = 例外 / 非同步、紅框 = 錯誤(標 error code)、綠框 = 成功終點、虛線 = 頻外(email)。
UC-FP-01 忘記密碼 → email token → 重設密碼
| 項目 | 內容 |
|---|---|
| 角色 | 匿名使用者 |
| 前置條件 | 忘記:email 對應啟用帳號;重設:持有效 token(url
?uid=) |
| 產出 / 後置條件 | 忘記 → 建 user_change_password_request(type=2,
status=0, expired=+30 分)+ 寄信;重設 → bcrypt 更新密碼、寫變更
log、token 標 status=1 |
3. 權限矩陣
匿名態(見 _overview §5):
| 操作 | FE 判定 | BE 強制 | BE 檢查位置 |
|---|---|---|---|
| 申請重設 | 匿名頁(Authorization: null) |
無 JWT;email 存在檢查 + 5 分節流 | api/auth/routes/change_password_route.py::ForgetPasswordRoute |
| 驗 token | 匿名頁 | 無 JWT;token 有效性 | 同檔 UserChangePasswordRequestRoute(GET) |
| 設新密碼 | 匿名頁 | 無 JWT;token 有效性 + 密碼相符 + 防重用 | 同檔 ChangePasswordByChangeRequestRoute |
token(
uid)是重設端點的唯一憑證——無 JWT。故 token 的過期 / single-use / 節流是安全關鍵(見 §12)。
4. 狀態機與前置條件
密碼變更請求生命週期見 _overview §4。本頁 gate:
| gate | 效果 | 出處 |
|---|---|---|
| email 不存在 | AUTH_404002(使用者信箱不存在)——回應碼洩漏帳號存在性(§12) |
jedi_auth ...user_change_password_service::request_change_password |
| 5 分鐘內已申請 | AUTH_429002(5 分鐘內已申請),舊請求標 used |
..._request_domain_service::verify_user_change_password_apply |
| token 不存在 | AUTH_401003 |
verify_and_get_user_change_password_request |
| token 過期(>30 分) | AUTH_401005(變更密碼請求已過期) |
同上 |
| token 已使用 | AUTH_401007(變更密碼請求已使用) |
同上 |
| 新密碼 = 近 3 次 | AUTH_401009(新密碼與舊密碼相同) |
..._log_domain_service::verify_user_change_password_log |
| 密碼與確認不符 | AUTH_401010(密碼與確認密碼不相同) |
route 前置檢查 |
| 密碼政策 | ≥12 字 + 大小寫 + 數字(僅 FE vuelidate 檢,BE 未強制內容,§12) | ChangePassword.vue(passwordComplexity
regex) |
5. UI 設計
📸 實際畫面截圖待補(一律亮色模式 + 登出態擷取;截圖另有專責 session)。預計補到
assets/img/: ① 忘記密碼頁forgot-password.png② 重設密碼頁(密碼規則提示)reset-password.png③ token 失效全頁錯誤reset-token-error.png
版面骨架與區塊職責:忘記密碼卡(email 申請 + 節流/防列舉 + 建請求寄信,→ UC-FP-01)與重設密碼卡(token 驗證 + 動態標題 + 新密碼設定,→ UC-FP-01)。
狀態呈現:忘記頁送出後 5s 冷卻(防連點);重設頁進頁先驗 token,失效則顯示全頁錯誤 + 返回登入(隱藏表單);密碼規則以側欄清單提示(12+ 字 / 大小寫 + 數字)。無 socket。
6. API 規格
Envelope:成功 {"status": true, "data": …}、失敗
{"error_code": "AUTH_…", "msg": "…"}。
6.1 總清單(本頁呼叫的全部 endpoint)
| 分類 | Method + Path | 說明 | 完整規格 |
|---|---|---|---|
| 申請 | POST /forget-password |
以 email 申請重設(建請求 + 寄信) | §6.2 |
| 驗 token | GET /user/change-pwd-req/{uid} |
驗證重設 token 並取 type |
§6.2 |
| 設密碼 | POST /user/change-pwd-by-req |
憑 token 設新密碼 | §6.2 |
6.2 核心 endpoint(欄位對照 serializer / service,出處標段末)
[POST] /forget-password
Request:
{ "email": "string" }流程:檢查 email 存在(不存在 AUTH_404002)→ 5
分節流(AUTH_429002)→ 建
user_change_password_request(type=2 忘記 /
status=0 / expired=now+30min /
uid=UUID token)→ 若 notification_service
存在,寄信含
{system_url}/reset-password?uid={uid}&lang=。Response:請求摘要(uid
/ user_id / type / status / expired /
created_at)。出處:api/auth/routes/change_password_route.py::ForgetPasswordRoute、jedi_auth ...user_change_password_service::request_change_password。
[GET] /user/change-pwd-req/{uid} — 驗 token
驗證:存在(AUTH_401003)/
未過期(AUTH_401005)/ 未使用(AUTH_401007)→
回請求(含 type
供動態標題)。出處:change_password_route.py::UserChangePasswordRequestRoute、..._request_domain_service::verify_and_get_user_change_password_request。
[POST] /user/change-pwd-by-req — 設新密碼
Request:
{
"req_uid": "string",
"password": "string",
"confirm_password": "string"
}流程:新密碼與確認相符檢查(否則 AUTH_401010)→ 重驗
token → 取 user → 防重用(bcrypt 比近 3 次,相同擋
AUTH_401009)→ bcrypt(per-user salt)更新
users.password → 寫 user_change_pwd_logs →
token status=1。以 @transaction
原子完成。出處:change_password_route.py::ChangePasswordByChangeRequestRoute、..._service::change_password_by_request。
7. 前端檔案地圖(compliance-manager-fe/)
| 檔案 | 角色 |
|---|---|
src/views/auth/ForgotPassword.vue |
忘記密碼頁(email 輸入 + 送出 + 冷卻) |
src/views/auth/ChangePassword.vue |
重設密碼頁(token 驗證 + 新密碼 + 動態標題) |
src/config/router/index.js:677-685 |
route
forget-password(/forget-password)、reset-password(/reset-password) |
src/config/api/api.js |
FORGET_PWD(/forget-password)/
CHANGE_PWD_REQ(/user/change-pwd-req)/
CHANGE_PWD_BY_REQ(/user/change-pwd-by-req) |
src/config/locales/i18n/*/error-code.json |
CE60001/CE61006/CE61007/CE61010
→ i18n 訊息 |
8. 後端檔案地圖(本頁核心鏈路)
| 鏈路 | Route | App Service | 底層 |
|---|---|---|---|
| 申請 | api/auth/routes/change_password_route.py::ForgetPasswordRoute |
jedi_auth ...user_change_password_service::request_change_password |
user_change_password_request 寫 + jedi-notification
寄信 |
| 驗 token | 同檔 UserChangePasswordRequestRoute |
..._request_domain_service::verify_and_get_... |
user_change_password_request 讀 |
| 設密碼 | 同檔 ChangePasswordByChangeRequestRoute |
..._service::change_password_by_request |
users(bcrypt)+ user_change_pwd_logs 寫 +
token 標 used |
DDD 提醒:密碼變更核心 domain 在 jedi-auth
外部套件;主專案 route 做 HTTP
邊界。change_password_by_request 以
@transaction 包住「更新密碼 + 寫 log + token 標
used」原子。
9. DB
9.1 資料表總清單(本頁讀寫的全部表)
| 表 | 讀/寫 | 說明 | 欄位詳述 |
|---|---|---|---|
| public.user_change_password_request | 讀 / 寫 | 重設請求(token=uid、type、status 0→1、expired) | _overview §2 |
| public.user_change_pwd_logs | 寫 | 密碼變更歷史(防重用比對來源) | _overview §2 |
| public.users | 讀 / 寫(password) | bcrypt 更新密碼;email 存在檢查 | _overview §2 |
9.2 ER 圖(本頁讀寫範圍)
見 _overview §2
認證資料模型(assets/au-auth-data-model.dot)——本頁
users → user_change_password_request → user_change_pwd_logs。
9.3 核心欄位
user_change_password_request(uid / user_id / type
0-1-2-3-9 / status 0-1 / expired)與
user_change_pwd_logs(user_id / type / password)欄位見 _overview §2 / §4。密碼以 bcrypt + per-user salt
雜湊存 users.password。
10. 頁面邏輯與資料對應
流程:忘記頁 email →
POST /forget-password(不論帳號是否存在,FE 皆顯示成功
toast)→ 使用者收信點連結 → 重設頁 ?uid=token →
GET /user/change-pwd-req/{uid} 驗 token 並依
type 顯標題 → 填新密碼 →
POST /user/change-pwd-by-req → 成功清 localStorage
轉登入。
情境動態標題(ChangePassword.vue,依
type):0 首次註冊設密碼 / 2
忘記密碼重設 / 3 密碼過期(90 天)強制變更——同一頁 + 同一
POST 端點服務三情境。
關鍵欄位對應(FE ↔︎ API):
| 畫面元素 | FE state | API 欄位 |
|---|---|---|
| email 輸入 | user_email |
email |
| token(url) | pwd.req_uid(route.query.uid) |
req_uid / path uid |
| 新密碼 / 確認 | password / confirm_password |
password / confirm_password |
| 動態標題 | reqData.type |
type(0/2/3) |
錯誤對應:AUTH_401003(CE60001 無效)/
401005(CE61006 過期)/ 401007(CE61007
已使用)/ 401009(CE61010 與舊密碼相同)/
401010(不相符)/ 404002(信箱不存在)/
429002(5 分節流)→ toast / 全頁錯誤。
11. 背景行為與外部依賴
| 類型 | 內容 |
|---|---|
| Email 寄送 | jedi-notification
NotificationService.send_mail_notification(is_html);SMTP
設定讀 ROOT tenant(tenant_id=1)system_config 的 SMTP
key(host/port/from/user/secret/tls);若 notification_service
為 None 或 SMTP 未設 → 靜默 no-op(dev 常見)(§12) |
| 密碼雜湊 | bcrypt + per-user salt(users.salt);防重用比近 3 筆
user_change_pwd_logs |
| 稽核 | 申請記 EventCode.USER_RESET_PASSWORD |
| jedi-* 套件 | jedi-auth(密碼變更 domain / service /
錯誤碼)、jedi-notification(SMTP)、jedi-common(@transaction
/ 例外) |
| Socket | 本頁無 |
| 系統參數 | 密碼政策(≥12 + 複雜度)目前只在 FE regex;BE 未強制內容(§12) |
12. 邊界情況與已知坑
- 帳號列舉風險(安全):
/forget-password對不存在 email 回AUTH_404002(404),存在則 200——FE 雖統一顯示成功 toast,但回應狀態碼不同,攻擊者可據此列舉有效 email。修法:不存在 email 也回 200(不洩漏)。 - 密碼政策僅 FE 強制:≥12 字 + 大小寫 + 數字由
ChangePassword.vuevuelidate 檢;BE route 只驗欄位型別、不驗密碼內容——繞過 FE 可設弱密碼。應在 BE 補政策檢查。 - token 30 分過期 +
single-use:
expired=now+30min、status0→1;已用 / 過期不可再用。無延長機制。 - 5 分節流:每使用者同時僅一筆待用
type=2請求;新申請把舊的標 used。防濫發但也意味舊連結會失效。 - email 寄送可能
no-op:
notification_service為 None 或 SMTP 未設時靜默不寄(dev 環境常見)——測試重設需直接查 DB 拿user_change_password_request.uid組連結。 - 三情境共用一頁:type 0(首次)/ 2(忘記)/
3(過期)都走
ChangePassword.vue+ 同一 POST;改流程要顧及三情境。 - 重設頁清 localStorage:成功後清 localStorage 再轉登入——避免殘留舊 token / 狀態。
13. 開發與驗證
- 跑起來:BE
python main_socketio.py(port 8000,log 在log/app.log);FE 在compliance-manager-fe/起 Vite dev server。email 寄送需 ROOT tenant SMTP 設定,否則 no-op。BE 改 service code 後必須重啟 - 測試帳號:dev 環境登入帳號密碼見
.env/ 部署文件 - 導航路徑:登入頁「忘記密碼」→
/forget-password;重設連結/reset-password?uid=<token> - 前置資料:忘記需啟用帳號 + 有 email;重設需有效
token(dev 無 SMTP 時直接查
user_change_password_request.uid組連結) - E2E:
compliance-manager-test/repo;以忘記密碼/reset password搜尋 feature 檔 - 相關文件:登入 / MFA 見 登入、密碼變更生命週期見 _overview §4;DB / API 全量見 GAI-SD-02/04(Security Design)