fix(test): 修正 AuthRateLimitTest 過期的 throttle 門檻斷言 #27
@@ -0,0 +1,155 @@
|
|||||||
|
# auth-test-coverage Specification
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
TBD - created by archiving change auth-tests. Update Purpose after archive.
|
||||||
|
## Requirements
|
||||||
|
### Requirement: Auth 登入/註冊/登出測試覆蓋(三角色)
|
||||||
|
測試套件 SHALL 對 member、provider、admin 三個角色各自驗證以下場景,確保回歸時能即時偵測。
|
||||||
|
|
||||||
|
#### Scenario: Member 註冊成功
|
||||||
|
- **WHEN** 送出有效的 name / email / password / password_confirmation 至 `POST /api/member/register`
|
||||||
|
- **THEN** 回傳 HTTP 201,body 包含 `{ status: true, data: { user } }`,DB 存在對應 member 用戶
|
||||||
|
|
||||||
|
#### Scenario: Member 重複 Email 註冊失敗
|
||||||
|
- **WHEN** 送出已存在的 email 至 `POST /api/member/register`
|
||||||
|
- **THEN** 回傳 HTTP 422
|
||||||
|
|
||||||
|
#### Scenario: Member 登入成功
|
||||||
|
- **WHEN** 送出正確的 email / password 至 `POST /api/member/login`
|
||||||
|
- **THEN** 回傳 HTTP 200,body 包含 `{ status: true, data: { token, token_type: "Bearer", user } }`
|
||||||
|
|
||||||
|
#### Scenario: Member 登入密碼錯誤
|
||||||
|
- **WHEN** 送出存在的 email 但錯誤的 password
|
||||||
|
- **THEN** 回傳 HTTP 401,`{ status: false, message: "電子郵件或密碼錯誤" }`
|
||||||
|
|
||||||
|
#### Scenario: Member 非活躍帳號拒絕登入
|
||||||
|
- **WHEN** 送出 is_active=false 帳號的正確帳密
|
||||||
|
- **THEN** 回傳 HTTP 403
|
||||||
|
|
||||||
|
#### Scenario: Provider 帳號無法呼叫 Member 登入
|
||||||
|
- **WHEN** role=provider 的帳號嘗試 `POST /api/member/login`
|
||||||
|
- **THEN** 回傳 HTTP 401,`{ status: false, message: "電子郵件或密碼錯誤" }`(查詢以 role 過濾,跨角色帳號視同不存在,不洩漏帳號實際所屬角色)
|
||||||
|
|
||||||
|
#### Scenario: Member 登出成功
|
||||||
|
- **WHEN** 已登入 Member 送出 `POST /api/member/logout`(帶有效 Bearer token)
|
||||||
|
- **THEN** 回傳 HTTP 200,token 被撤銷(後續以此 token 請求受保護資源應得 401)
|
||||||
|
|
||||||
|
#### Scenario: Provider 登出成功
|
||||||
|
- **WHEN** 已登入 Provider 送出 `POST /api/provider/logout`(帶有效 Bearer token)
|
||||||
|
- **THEN** 回傳 HTTP 200,token 被撤銷(後續以此 token 請求受保護資源應得 401)
|
||||||
|
|
||||||
|
#### Scenario: Admin 登出成功
|
||||||
|
- **WHEN** 已登入 Admin 送出 `POST /api/admin/logout`(帶有效 Bearer token)
|
||||||
|
- **THEN** 回傳 HTTP 200,token 被撤銷(後續以此 token 請求受保護資源應得 401)
|
||||||
|
|
||||||
|
#### Scenario: Provider 帳號無法呼叫 Admin 登入
|
||||||
|
- **WHEN** role=provider 的帳號嘗試 `POST /api/admin/login`
|
||||||
|
- **THEN** 回傳 HTTP 401,`{ status: false, message: "電子郵件或密碼錯誤" }`(查詢以 role 過濾,跨角色帳號視同不存在)
|
||||||
|
|
||||||
|
#### Scenario: Provider 註冊成功
|
||||||
|
- **WHEN** 送出有效的 name / email / password / password_confirmation 至 `POST /api/provider/register`
|
||||||
|
- **THEN** 回傳 HTTP 201,body 包含 `{ status: true, data: { user } }`,DB 存在對應 provider 用戶
|
||||||
|
|
||||||
|
#### Scenario: Provider 重複 Email 註冊失敗
|
||||||
|
- **WHEN** 送出已存在的 email 至 `POST /api/provider/register`
|
||||||
|
- **THEN** 回傳 HTTP 422
|
||||||
|
|
||||||
|
#### Scenario: Provider 登入成功
|
||||||
|
- **WHEN** 送出正確的 email / password 至 `POST /api/provider/login`
|
||||||
|
- **THEN** 回傳 HTTP 200,body 包含 `{ status: true, data: { token, token_type: "Bearer", user } }`
|
||||||
|
|
||||||
|
#### Scenario: Provider 非活躍帳號拒絕登入
|
||||||
|
- **WHEN** 送出 is_active=false 的 provider 帳號正確帳密
|
||||||
|
- **THEN** 回傳 HTTP 403
|
||||||
|
|
||||||
|
#### Scenario: Member 帳號無法呼叫 Provider 登入
|
||||||
|
- **WHEN** role=member 的帳號嘗試 `POST /api/provider/login`
|
||||||
|
- **THEN** 回傳 HTTP 401,`{ status: false, message: "電子郵件或密碼錯誤" }`(查詢以 role 過濾,跨角色帳號視同不存在)
|
||||||
|
|
||||||
|
#### Scenario: Admin 註冊成功
|
||||||
|
- **WHEN** 送出有效的 name / email / password / password_confirmation 至 `POST /api/admin/register`
|
||||||
|
- **THEN** 回傳 HTTP 201,body 包含 `{ status: true, data: { user } }`,DB 存在對應 admin 用戶
|
||||||
|
|
||||||
|
#### Scenario: Admin 重複 Email 註冊失敗
|
||||||
|
- **WHEN** 送出已存在的 email 至 `POST /api/admin/register`
|
||||||
|
- **THEN** 回傳 HTTP 422
|
||||||
|
|
||||||
|
#### Scenario: Admin 登入成功
|
||||||
|
- **WHEN** 送出正確的 email / password 至 `POST /api/admin/login`
|
||||||
|
- **THEN** 回傳 HTTP 200,body 包含 `{ status: true, data: { token, token_type: "Bearer", user } }`
|
||||||
|
|
||||||
|
#### Scenario: Admin 登入非 admin 帳號失敗
|
||||||
|
- **WHEN** role=member 帳號嘗試 `POST /api/admin/login`
|
||||||
|
- **THEN** 回傳 HTTP 401,`{ status: false, message: "電子郵件或密碼錯誤" }`(查詢以 role 過濾,跨角色帳號視同不存在)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Requirement: 帳號鎖定測試覆蓋(P2 Fixed Window)
|
||||||
|
測試套件 SHALL 驗證帳號鎖定邏輯的全部關鍵路徑,以防止回歸破壞安全機制。
|
||||||
|
|
||||||
|
#### Scenario: 失敗次數未達閾值不鎖定
|
||||||
|
- **WHEN** 同一帳號連續登入失敗 4 次
|
||||||
|
- **THEN** 每次均回傳 HTTP 401,帳號未鎖定
|
||||||
|
|
||||||
|
#### Scenario: 第 5 次失敗當場觸發鎖定
|
||||||
|
- **WHEN** 同一帳號連續登入失敗第 5 次
|
||||||
|
- **THEN** 回傳 HTTP 423,body 包含 `{ status: false, locked_until: "<ISO8601>" }`
|
||||||
|
|
||||||
|
#### Scenario: 鎖定期間任何登入均被拒絕
|
||||||
|
- **WHEN** 帳號已鎖定,使用者送出(含正確密碼的)登入請求
|
||||||
|
- **THEN** 回傳 HTTP 423,不驗證密碼
|
||||||
|
|
||||||
|
#### Scenario: 不存在帳號不累計失敗計數
|
||||||
|
- **WHEN** 以不存在的 email 連續嘗試登入 10 次
|
||||||
|
- **THEN** 每次均回傳 HTTP 401,Cache 中不建立計數 key,第 10 次後帳號不鎖定
|
||||||
|
|
||||||
|
#### Scenario: 登入成功後清除失敗計數
|
||||||
|
- **WHEN** 帳號失敗 3 次後成功登入,再失敗 4 次
|
||||||
|
- **THEN** 失敗計數從成功後重新累積,第 4 次仍回傳 401(不觸發鎖定)
|
||||||
|
|
||||||
|
#### Scenario: Email 大小寫視為同一帳號
|
||||||
|
- **WHEN** 同一帳號分別用 `user@example.com` 失敗 3 次、`User@Example.COM` 失敗 2 次
|
||||||
|
- **THEN** 第 5 次失敗(累計)觸發鎖定,回傳 HTTP 423
|
||||||
|
|
||||||
|
#### Scenario: Email 前後空白視為同一帳號
|
||||||
|
- **WHEN** 同一帳號分別用 `user@example.com` 失敗 3 次、` user@example.com `(含前後空白)失敗 2 次
|
||||||
|
- **THEN** 第 5 次失敗(累計)觸發鎖定,回傳 HTTP 423
|
||||||
|
|
||||||
|
#### Scenario: 鎖定 cache entry 不存在時帳號恢復可登入
|
||||||
|
- **WHEN** 帳號已鎖定,鎖定的 cache entry 被移除(模擬 TTL 自然過期)
|
||||||
|
- **THEN** 帳號可正常登入,回傳 HTTP 200
|
||||||
|
|
||||||
|
#### Scenario: 跨角色嘗試不會汙染或鎖定正確角色帳號
|
||||||
|
> 註:原情境假設「同一 email 同時擁有 member 與 provider 帳號」,但 `users.email` 在 DB 層級為全域 unique(`database/migrations/2025_05_06_065906_create_orgin_table.php:18`),此前提不可能成立,故改寫為下列可達成、且驗證相同安全性質(角色間鎖定計數互相隔離)的版本。
|
||||||
|
- **WHEN** 一個僅存在 provider 帳號的 email,先對 `/api/member/login` 送出 4 次失敗嘗試,再對 `/api/provider/login` 送出 4 次失敗嘗試
|
||||||
|
- **THEN** member namespace 不建立計數 key(帳號不存在於該 namespace);provider namespace 計數累積為 4、未達閾值不鎖定;provider 帳號仍可用正確密碼正常登入
|
||||||
|
|
||||||
|
#### Scenario: Fixed Window — 失敗不延長 TTL
|
||||||
|
- **WHEN** 帳號失敗 4 次後,第 4 次失敗發生於 TTL 將到期前
|
||||||
|
- **THEN** Cache TTL 不被重設,`locked_until` 仍為第一次失敗時寫入的時間
|
||||||
|
|
||||||
|
#### Scenario: `locked_until` 來自 companion key
|
||||||
|
- **WHEN** 帳號觸發鎖定,回傳 HTTP 423
|
||||||
|
- **THEN** response body 的 `locked_until` 等於 `login_expires_at:{role}:{email}` cache key 的值
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Requirement: OAuth State 驗證測試覆蓋(P2)
|
||||||
|
測試套件 SHALL 驗證 OAuth callback 的 state 比對邏輯,涵蓋正常、缺失、不符三種情境。
|
||||||
|
|
||||||
|
#### Scenario: State 缺失時 redirect 至錯誤頁,且不呼叫 Socialite
|
||||||
|
- **WHEN** 直接呼叫 `GET /auth/google/callback` 而不帶 `state` 參數(session 無 `oauth_state`)
|
||||||
|
- **THEN** 後端 redirect 至包含 `error=oauth_failed` 的 URL,不繼續登入流程;Socialite 的 `user()` 不被呼叫
|
||||||
|
|
||||||
|
#### Scenario: State 不符時 redirect 至錯誤頁,且不呼叫 Socialite
|
||||||
|
- **WHEN** callback 帶 `state=wrong_value`,但 session 中 `oauth_state` 為不同值
|
||||||
|
- **THEN** 後端 redirect 至包含 `error=oauth_failed` 的 URL;Socialite 的 `user()` 不被呼叫
|
||||||
|
|
||||||
|
#### Scenario: State 正確時完成 OAuth 登入
|
||||||
|
- **WHEN** callback 帶的 `state` 與 session 中 `oauth_state` 一致,且 Socialite 回傳有效 Google user
|
||||||
|
- **THEN** 後端完成登入,redirect 至前端並附帶 token(URL fragment `#token=...`)
|
||||||
|
|
||||||
|
#### Scenario: State 成功驗證後從 session 消耗
|
||||||
|
- **WHEN** callback 帶正確 `state` 完成一次登入後,再次以相同 `state` 呼叫 callback
|
||||||
|
- **THEN** session 中已無 `oauth_state`,視為 state 缺失,redirect 至 `error=oauth_failed`
|
||||||
|
|
||||||
Reference in New Issue
Block a user