ADDED Requirements
Requirement: 首次安裝 bootstrap 流程
Summary: 平台首次啟動 MUST 自動導向 /bootstrap,收集第一個 Admin 帳號(username / 顯示名稱 / email / 密碼),完成後自動登入並 SHALL 標記不可重入。
The system MUST present a one-time /bootstrap flow on first launch that captures the inaugural Admin account (username, display name, email, password), creates the account, marks bootstrap_completed = true, auto-logs the user in, and writes an audit record. The flow MUST NOT be reachable after completion.
Scenario: 首次安裝 cold start 自動進 bootstrap
- GIVEN 平台首次啟動、
bootstrap_completed 為 false
- AND 使用者尚未建立任何帳號
- WHEN 使用者瀏覽平台任何 URL
- THEN 系統 MUST 重導至
/bootstrap
- AND SHALL 顯示空白 Admin 建立表單(username / 顯示名稱 / email / password)
- AND 提交按鈕 SHALL 預設為 disabled,直到所有欄位填妥且密碼通過強度檢查
Scenario: 成功建立第一個 Admin 並自動登入
- GIVEN 使用者在
/bootstrap 頁、表單已填妥合法值(密碼通過強度檢查)
- WHEN 使用者送出表單
- THEN 系統 MUST 建立 Admin 角色的帳號
- AND SHALL 將
bootstrap_completed 設為 true
- AND SHALL 建立登入 session 並重導至
/dashboard
- AND SHALL 顯示「歡迎 <顯示名稱>」toast
- AND SHALL 寫一筆 audit-log(actor=
system_init, action=bootstrap_admin, target=username, result=success)
Scenario: Bootstrap 完成後再訪問 `/bootstrap` 被拒
- GIVEN
bootstrap_completed 為 true
- WHEN 任何使用者(含未登入)GET
/bootstrap
- THEN 系統 MUST 重導至
/login
- AND SHALL NOT 顯示 bootstrap 表單
- AND SHALL NOT 建立任何新帳號
Scenario: Bootstrap 過程中設定弱密碼被拒
- GIVEN 使用者在
/bootstrap 頁
- AND 密碼策略下限為 8 字 + 含數字
- WHEN 使用者輸入長度 6 字的密碼
- THEN 強度檢查 MUST 即時標記紅色 fail
- AND 提交按鈕 SHALL 保持 disabled
- AND SHALL 顯示具體未達標項目(如「至少 8 字」)
Edge Cases
- 兩瀏覽器同時 bootstrap:A 先提交完成 → B 提交時收 409 並重導至
/login(race condition by atomic flag check + update)
- Bootstrap 過程中後端錯誤(資料庫寫入失敗):表單狀態保留、顯示明確錯誤、重試按鈕 enable;不清空已填欄位
- 使用者關閉瀏覽器再開:
bootstrap_completed 仍 false → 重新進入 /bootstrap(未完成狀態不持久化)
- 直接 POST
/bootstrap API 而不經 UI:仍需通過密碼強度 + 防重入檢查;不可繞過
- Audit-log 寫入失敗:整個 bootstrap transaction MUST rollback,使用者看到「儲存失敗」訊息
Requirement: 密碼強度策略可調且有產品下限
Summary: Admin 可在 Settings 調整密碼策略(最少字數 / 必須包含類型),但系統 MUST 強制下限為 8 字 + 含數字,SHALL NOT 低於此線。
The system MUST enforce a password strength policy with two layers: a hard product floor (minimum 8 characters AND at least one digit) that cannot be lowered, and a configurable customer policy that can only be more strict than the floor. Default policy on fresh install SHALL be 12 characters + uppercase + lowercase + digit + symbol.
Scenario: Admin 可將策略調嚴
- GIVEN Admin 已登入、目前策略 = 預設(12 字 + 大小寫 + 數字 + 符號)
- WHEN Admin 在
/settings/security-policy 將最少字數調整為 16
- AND 按下儲存
- THEN 系統 MUST 套用新策略
- AND 下次有人改密碼 SHALL 套用最少 16 字
- AND SHALL 寫 audit-log(action=
password_policy_update, before/after diff)
Scenario: Admin 無法把策略調到低於產品下限
- GIVEN Admin 在
/settings/security-policy
- AND 產品下限 = 8 字 + 含數字
- WHEN Admin 嘗試將最少字數設為 6
- THEN 系統 MUST 拒絕儲存
- AND SHALL 顯示「不可低於產品下限 8 字」
- AND 欄位 SHALL 自動回到 8
Scenario: 改密時策略即時生效
- GIVEN 目前策略 = 12 字 + 大小寫 + 數字 + 符號
- AND 使用者在改密頁
- WHEN 使用者輸入 11 字密碼
- THEN 強度檢查項「至少 12 字」MUST 顯示紅色 fail
- AND 提交按鈕 SHALL 保持 disabled
Edge Cases
- 多 Admin 同時改策略:以最後一次儲存為準(last-write-wins,audit-log 留所有變更)
- 策略剛調嚴但既有 user 密碼不符新標準:既有密碼仍可用,下次改密強制套新標準(不強制立即改密 — 由獨立的「強制改密」requirement 處理)
- 策略 = 產品下限時 Admin 嘗試再降低:拒絕(已是下限)
- 客戶內網 demo 場景:可調至產品下限,但 install guide 標示「demo 用,生產環境請套預設值」
Requirement: 連續失敗鎖定 + Admin 緊急解鎖
Summary: 同帳號連續 5 次登入失敗 MUST 鎖定 15 分鐘(全 IP 鎖);15 分鐘後 SHALL 自動解鎖;Admin 可在 User Management 手動緊急解鎖。
The system MUST track consecutive failed login attempts per account (not per IP). After 5 consecutive failures within a sliding window, the account MUST be locked for 15 minutes. During lockout, even correct passwords MUST be rejected. Locks MUST auto-release after the timeout; Admins MUST have an emergency unlock action. All lock/unlock events MUST be audited.
Scenario: 連續 5 次失敗觸發鎖定
- GIVEN 使用者
alice 帳號目前狀態 = unlocked
- WHEN
alice 連續 5 次以錯誤密碼嘗試登入
- THEN 系統 MUST 將
alice 狀態設為 locked
- AND SHALL 設定
unlock_at = now + 15 min
- AND SHALL 寫 audit-log(action=
account_locked, reason=failure_threshold, failure_count=5)
- AND 下一次(含第 6 次)登入嘗試 SHALL 回應「帳號已鎖定,15 分鐘後重試或聯絡 Admin」
Scenario: 鎖定期間正確密碼也被拒
- GIVEN 使用者
alice 狀態 = locked、unlock_at 為 14 分鐘後
- WHEN
alice 以正確密碼嘗試登入
- THEN 系統 MUST 拒絕登入
- AND SHALL NOT 建立 session
- AND SHALL 顯示鎖定倒數計時
- AND SHALL 寫 audit-log(action=
login_blocked_locked)
Scenario: 15 分鐘後自動解鎖
- GIVEN 使用者
alice 狀態 = locked、unlock_at 已過
- WHEN
alice 以正確密碼嘗試登入
- THEN 系統 MUST 自動將狀態設回 unlocked
- AND SHALL 重設 failure_count = 0
- AND SHALL 建立 session 並登入成功
- AND SHALL 寫 audit-log(action=
account_auto_unlocked)
Scenario: Admin 緊急手動解鎖
- GIVEN 使用者
alice 狀態 = locked
- AND Admin
bob 已登入
- WHEN
bob 在 User Management 對 alice 點「緊急解鎖」並確認
- THEN 系統 MUST 將
alice 狀態設回 unlocked
- AND SHALL 重設 failure_count = 0
- AND SHALL 寫 audit-log(action=
account_unlocked_admin, actor=bob, target=alice)
- AND alice 下次登入 SHALL 成功(若密碼正確)
Scenario: 鎖定為帳號層級非 IP 層級
- GIVEN 使用者
alice 從 IP1 連續 5 次失敗 → 觸發鎖定
- WHEN 攻擊者改從 IP2 以 alice 帳號嘗試登入
- THEN 系統 MUST 同樣回應「帳號已鎖定」
- AND SHALL NOT 因換 IP 重置鎖定狀態
Edge Cases
- 鎖定中 user 自然到期同時 Admin 手動解鎖:以「先到的事件」為準,audit-log 留兩筆紀錄(auto + admin),不重複扣 failure
- 鎖定期間 unlock_at 設定後系統重啟:persistent 儲存 unlock_at,重啟後仍維持鎖定到指定時間
- 大量帳號同時被鎖(DoS 嘗試):系統 SHALL NOT 因 audit-log 寫入量大而影響登入回應;audit 寫入失敗 → 仍鎖定但記錄「audit-log degraded」warning
- failure_count 計算窗口:用 sliding window(最近 1 小時內 5 次)vs absolute count,本 V1 採 absolute count from last successful login(最簡 implementation,足夠合規)
- Admin 解鎖自己:允許(Admin 可能自己誤鎖自己,必須有自救路徑;但僅限同時存在其他 Admin 可審計時才合理 — 本 V1 不強制此檢查,留 V2)
Requirement: 密碼到期 + 強制改密
Summary: 密碼 MUST 預設 90 天到期;到期前 7 天於登入後 SHALL 顯示提示;到期則 MUST 強制進改密流程;Admin 可手動標記任何 user 為「下次登入強制改密」。
The system MUST enforce a configurable password expiration period (default 90 days). Within 7 days of expiration, users SHALL see a non-blocking warning. Upon expiration, login MUST be intercepted by a forced password-change flow before the user can access any other page. Admins MUST be able to manually flag any user account for force_change_on_next_login = true.
Scenario: 密碼到期前 7 天顯示警告
- GIVEN 使用者
alice 密碼建立於 84 天前(剩 6 天到期)
- WHEN
alice 成功登入
- THEN 系統 MUST 顯示非阻斷式 banner「密碼將於 6 天後到期,立即更新」
- AND Banner SHALL 提供「立即更新」按鈕連到改密頁
- AND 使用者 SHALL 仍可正常使用平台(不阻斷)
Scenario: 密碼到期當天強制改密
- GIVEN 使用者
alice 密碼建立於 90 天前
- WHEN
alice 以正確密碼登入
- THEN 系統 MUST 建立 session 但 redirect 至
/change-password
- AND 任何其他 URL SHALL 重導回
/change-password
- AND 使用者 SHALL 必須完成改密才能存取其他頁面
- AND SHALL 寫 audit-log(action=
password_expired_forced_change)
Scenario: Admin 手動標記強制改密
- GIVEN Admin
bob 已登入
- WHEN
bob 在 User Management 對 alice 勾選「下次登入強制改密」並儲存
- THEN 系統 MUST 將
alice.force_change_on_next_login = true
- AND alice 下次登入 SHALL 被導至
/change-password(不論密碼是否到期)
- AND SHALL 寫 audit-log(action=
force_password_change_set, actor=bob, target=alice)
Scenario: 完成強制改密後可正常使用
- GIVEN 使用者
alice 在 /change-password 頁、force_change_on_next_login = true
- WHEN
alice 輸入符合策略的新密碼並送出
- THEN 系統 MUST 更新密碼 hash
- AND SHALL 將
force_change_on_next_login = false
- AND SHALL 重設
password_changed_at = now
- AND SHALL 重導至
/dashboard
- AND SHALL 寫 audit-log(action=
password_changed, reason=forced)
Edge Cases
- 新密碼與舊密碼相同:拒絕(強度檢查中加入「不可與舊密碼相同」)
- 改密中 session 過期:表單狀態保留,提示重新登入再改密
- Admin 把自己標記為「下次強制改密」:允許但下次登入即觸發
- 客戶把過期天數設 0:拒絕(產品下限 ≥ 30 天,避免日常打擾)— 本 V1 允許 0 = 「永不到期」(demo/POC 場景需求),但 install guide 警告
- 多人共用同一帳號(金融業違規但實務存在):強制改密會打斷所有人;audit-log 揭示違規模式給合規長
Requirement: 密碼以 hash 存儲且全程 HTTPS
Summary: 密碼僅以 Argon2id(或同等強度 KDF)hash 儲存;所有身分相關 endpoint MUST 強制 HTTPS;失敗訊息 MUST NOT 洩漏 username 是否存在。
The system MUST store passwords only as Argon2id (or equivalent memory-hard KDF) hashes. Plaintext passwords MUST NOT be logged, persisted, or returned via API. All authentication-related endpoints (bootstrap, login, change-password) MUST require HTTPS; HTTP requests SHALL be rejected or auto-upgraded. Authentication failure messages MUST NOT reveal whether a username exists.
Scenario: 密碼不以明文儲存
- GIVEN 任何寫密碼的流程(bootstrap / 改密 / Admin 重置)
- WHEN 系統儲存密碼
- THEN 系統 MUST 計算 Argon2id hash(或同等 KDF)
- AND SHALL NOT 將明文密碼寫入任何儲存層(DB / log / cache / temp file)
- AND API response SHALL NOT 包含明文密碼欄位
Scenario: HTTP 請求被拒或升級
- GIVEN 攻擊者嘗試透過 HTTP 訪問
/login
- WHEN 請求到達系統
- THEN 系統 MUST 回應 308 redirect 至 HTTPS 版本
- AND SHALL NOT 接受 HTTP body 內含密碼的請求
Scenario: 失敗訊息不洩漏 username 是否存在
- GIVEN 使用者
alice 存在、bob_not_exists 不存在
- WHEN 攻擊者以 alice 錯密碼 / 以 bob_not_exists 任意密碼分別嘗試登入
- THEN 系統 MUST 對兩種情況回應相同訊息(如「帳號或密碼錯誤」)
- AND 回應時間 SHALL 在統計上不可區分(防 timing attack — V1 用 constant-time compare + 對不存在 username 也做一次 dummy hash 計算)
Edge Cases
- 密碼遷移:若未來換 KDF(Argon2id → 未知後繼),系統 SHALL 支援逐次 rehash on login(不強迫所有 user 立即改密)— 本 V1 不實作但設計層保留 KDF version field
- 環境變數 / config 中含密碼 secret:MUST NOT 寫入 application log;env dump 工具 SHALL mask 敏感欄位
- 反向 proxy 終止 SSL 後內網用 HTTP:可接受但 Trust Boundary 必須明確標示(install guide),且 X-Forwarded-Proto header MUST 被檢查
- 自簽憑證 demo 場景:允許但 install guide 標示「僅 demo」
Requirement: a11y / i18n / 效能基線
Summary: Bootstrap、登入、改密、解鎖頁面 MUST 通過鍵盤導航 + ARIA + 螢幕閱讀器;UI 文字全走 i18n(zh-TW / en);P95 < 500ms(登入)/ < 1s(bootstrap)/ < 50ms(強度檢查)。
All authentication-related UI flows (bootstrap / login / change-password / locked / security-policy) MUST meet WCAG AA accessibility: keyboard navigable, ARIA labels on all interactive elements, screen-reader-friendly error announcements, focus management on modals. All user-facing text MUST be i18n-tokenized (zh-TW + en at minimum). Performance budgets MUST hold: login response P95 < 500ms, bootstrap page initial load < 1s, password strength check < 50ms per keystroke.
Scenario: 鍵盤可走完整 bootstrap 流程
- GIVEN 使用者僅用鍵盤操作
- WHEN 使用者進入
/bootstrap
- THEN Tab 鍵 MUST 依順序聚焦:username → 顯示名稱 → email → password → 提交按鈕
- AND Enter 鍵 SHALL 送出表單
- AND 強度檢查項變化 SHALL 透過
aria-live 區域 announce
Scenario: 語系切換立即生效
- GIVEN 使用者目前 locale = zh-TW
- WHEN 使用者在登入頁切換至 en
- THEN 介面文字 MUST 立即(無 reload)切換為英文
- AND 強度檢查項描述 SHALL 跟著切換語系
- AND 日期 / 數字格式 SHALL 套用 en locale
Scenario: 登入回應達效能基線
- GIVEN 系統處於正常負載(< 100 concurrent users)
- WHEN 使用者送出登入請求
- THEN 系統 MUST 在 P95 ≤ 500ms 內回應
- AND Argon2id 計算 SHALL 不阻塞單一 event loop(async + thread pool)
Edge Cases
- 螢幕閱讀器使用者改密失敗:錯誤訊息 MUST 透過
aria-live="assertive" announce,不只是視覺紅字
- i18n 缺翻譯 fallback:缺繁中翻譯時 fallback 顯示英文 + 一行警告 log(不顯示 token key 給 user)
- 高 Argon2id 參數導致登入慢:產品提供 tuned 預設值(記憶體 64MB / 平行 4 / 迭代 3),客戶可調但下限保證 P95 < 500ms
- 低頻寬環境(金融客戶分行):bootstrap 頁面 critical CSS inline + 不依賴外部資源,確保 < 1s 首次載入
- 強度檢查在低端機器:keystroke 觸發的計算 MUST 用 debounce (100ms) + Web Worker,避免主執行緒卡頓