feat: 實作管理端指令群 jwks/accounts/audit-logs(issue #12)

- docs/commands.md 新增 §3.8 jwks、§3.9 accounts、§3.10 audit-logs 規格
- Api 新增 jwks/accounts/audit-logs 端點(共用 api_request)
- 新增 Jwks/Accounts/AuditLogs/Admin 模組;CLI 接線三個指令群
- 403 → 退出碼 8 提示需 admin;一次性密碼只在成功當下輸出
- 測試 100 例全綠(fake API 注入,比照 cli_test.exs);基於含 PR #15 的最新 main
This commit is contained in:
2026-09-10 03:15:53 +08:00
parent e23be309f8
commit d70aa9e541
9 changed files with 1720 additions and 2 deletions
+158 -1
View File
@@ -512,6 +512,162 @@ TOTP 兩因子管理(WebAuthn/passkey 不在範圍)。
---
### 3.8 `bear jwks`:簽章金鑰管理指令群(admin,已實作)
> 依賴:bear 伺服器端 **JWK 管理 JSON API**(alterminal/bear#36/PR #46,資源前綴 `/api/v1/jwks`,PAT Bearer 認證、admin 限定)。API 已上線,本節指令群已實作(issue #12)。
比照 `apps` 的「資源群組」結構。金鑰材質(`key_data`)**永不經 CLI 顯示**(API 端即不序列化;公開 JWK 走 `/.well-known/jwks.json`),本群組僅管理公開中繼資料。
```
bear jwks list [--json]
bear jwks show <kid> [--json]
bear jwks create --kid KID [--alg ALG] [--json]
bear jwks toggle <kid> [--json]
```
**認證與授權(群組共通)**:一律使用既有 PAT 憑證(`bear login --token` 或 `BEAR_TOKEN` 環境變數)作為 Bearer,僅限 admin 角色。
`401`(token 無效)→ 退出碼 `3`,提示重新 `bear login`;`403`(非 admin)→ 退出碼 `8`,提示此操作需 admin 權限,不重試。
**識別與退出碼**:金鑰一律以 `kid`(人類可讀)識別;API 路徑參數為 UUID,由 CLI 先以 list 比對解析。退出碼同 §3.6 群組共通表(0/1/2/3/6/8)。
#### 3.8.1 `bear jwks list`
**行為**:`GET /api/v1/jwks`(Bearer PAT),列出所有金鑰(active 與 inactive),最新在前;人類可讀輸出為表格(ID 前 8 碼、KID、KTY、ALG、ACTIVE、CREATED AT)。
```
ID KID KTY ALG ACTIVE CREATED AT
0192cccc sig-2026-01 RSA RS256 active 2026-09-09T00:00:00Z
0192cccc sig-2025-12 EC ES256 inactive 2025-12-01T00:00:00Z
```
**`--json` 輸出範例**:`{"ok": true, "jwks": [{…}]}`(僅公開欄位,無 `key_data`)。
#### 3.8.2 `bear jwks show <kid>`
**行為**:`GET /api/v1/jwks/{id}`(以 kid 解析),印出公開欄位(id、kid、kty、alg、use、active、created_at);找不到 kid → 退出碼 `1`。
#### 3.8.3 `bear jwks create --kid KID [--alg ALG]`
| 選項 | 預設 | 說明 |
|------|------|------|
| `--kid` | (必填) | 金鑰識別碼 |
| `--alg` | `RS256` | `RS256`/`RS384`/`RS512`/`ES256`/`ES384`/`ES512` |
**行為**:`POST /api/v1/jwks`(body 為 kid 與 alg;金鑰由伺服器產生)。成功印出 `JWK 已建立:<kid>(<alg>/active)`。`alg` 非法 → 退出碼 `2`(本地檢查);kid 重複等 422 → 退出碼 `1` 逐欄位列錯。
**`--json` 輸出範例**:`{"ok": true, "jwk": {…}}`
#### 3.8.4 `bear jwks toggle <kid>`
**行為**:`POST /api/v1/jwks/{id}/toggle`(以 kid 解析),切換 `active ⇄ inactive`;成功印出如 `sig-2026-01:active → inactive`。
**`--json` 輸出範例**:`{"ok": true, "jwk": {"id": "…", "kid": "…", "active": false}}`
---
### 3.9 `bear accounts`:帳號管理指令群(admin,已實作)
> 依賵:bear 伺服器端 **帳號管理 JSON API**(alterminal/bear#36/PR #46,資源前綴 `/api/v1/accounts`,PAT Bearer 認證、admin 限定)。API 已上線,本節指令群已實作(issue #12)。
帳號 JSON 不含機密欄位(hash_password、totp_secret、recovery codes;API 端保證),CLI 亦不顯示。敏感規則與伺服器端一致:不可移除自己的 admin 角色(403)、不可刪除自己(403)。
```
bear accounts list [--page N] [--per-page N] [--json]
bear accounts show <email> [--json]
bear accounts create --email EMAIL [--role user|admin] [--generate-password] [--json]
bear accounts update <email> --role user|admin [--json]
bear accounts set-password <email> [--generate-password] [--json]
bear accounts delete <email> [--json]
```
**認證與授權(群組共通)**:同 §3.8(PAT Bearer、僅限 admin;401 → 3;403 → 8)。退出碼同 §3.6 群組共通表。
**識別與密碼**:帳號一律以 `email` 識別;API 路徑參數為 UUID,由 CLI 先以 list 分頁比對解析。密碼不進命令列(避免 shell history 與 ps):交互提示輸入(不回顧)或 `--generate-password` 由 CLI 產生。初始/新密碼只在成功當下一次性輸出。
#### 3.9.1 `bear accounts list`
**行為**:`GET /api/v1/accounts`(Bearer PAT,分頁,最新在前);人類可讀輸出為表格。
```
EMAIL ROLE TOTP CREATED AT
admin@example.com admin on 2026-01-01T00:00:00Z
alice@example.com user off 2026-02-01T00:00:00Z
```
**`--json` 輸出範例**:`{"ok": true, "page": 1, "per_page": 20, "total": 2, "accounts": […]}`
#### 3.9.2 `bear accounts show <email>`
**行為**:`GET /api/v1/accounts/{id}`(以 email 解析),印出公開欄位(id、email、role、totp_enabled、created_at、updated_at);找不到 → 退出碼 `1`。
#### 3.9.3 `bear accounts create`
| 選項 | 預設 | 說明 |
|------|------|------|
| `--email` | (必填) | 帳號 email(唯一) |
| `--role` | `user` | `user` 或 `admin` |
| `--generate-password` | 互動輸入 | 由 CLI 產生 16 字元隨機密碼(含字母與數字) |
**行為**:`POST /api/v1/accounts`(免信箱驗證;密碼以明文放入 `hash_password`,伺服器端驗強度後雜湏)。成功印出帳號摘要與一次性密碼區塊:
```
帳號已建立:new@example.com(user)
初始密碼(只顯示這一次,請立即保存並轉交當事人):
xK7mP9qR2sL4wY6z
```
**`--json` 輸出範例**:`{"ok": true, "account": {…}, "initial_password": "…"}`(密碼只出現這一次)。
#### 3.9.4 `bear accounts update <email> --role user|admin`
**行為**:`PUT /api/v1/accounts/{id}`(body 為 role)。成功印出 `alice@example.com:角色 → admin`。伺服器拒絕自降 admin(403)→ 退出碼 `8`。`--role` 罪給或非法 → 退出碼 `2`。
#### 3.9.5 `bear accounts set-password <email>`
**行為**:`PUT /api/v1/accounts/{id}/password`(管理員設新密碼,免舊密碼)。密碼來源同 create;非 TTY 且未給 `--generate-password` → 退出碼 `2`,提示加 `--generate-password`。成功後一次性顯示新密碼(同 create 提示)。
#### 3.9.6 `bear accounts delete <email>`
**行為**:`DELETE /api/v1/accounts/{id}`(管理端硬刪除,免驗證碼)。成功印出 `已刪除帳號:<email>`;伺服器拒絕自刪(403)→ 退出碼 `8`。
**`--json` 輸出範例**:`{"ok": true, "email": "alice@example.com", "deleted": true}`
---
### 3.10 `bear audit-logs`:稽核日誌指令群(admin,已實作)
> 依賵:bear 伺服器端 **稽核日誌 JSON API**(alterminal/bear#36/PR #46,`/api/v1/audit-logs`,PAT Bearer 認證、admin 限定)。API 已上線,本節指令群已實作(issue #12)。
```
bear audit-logs list [--page N] [--per-page N] [--category CATEGORY] [--json]
```
**認證與授權**:同 §3.8(PAT Bearer、僅限 admin;401 → 3;403 → 8)。
#### 3.10.1 `bear audit-logs list`
| 選項 | 預設 | 說明 |
|------|------|------|
| `--page` | `1` | 分頁頁碼 |
| `--per-page` | `50` | 每頁筆數(伺服器上限 200) |
| `--category` | 全部 | 過濾類別(伺服器端支援;如 `account`、`jwk`) |
**行為**:`GET /api/v1/audit-logs`(Bearer PAT,最新在前);回應附 `emails` 對照(account/actor id → email),人類可讀輸出以 email 呈現、無對照時顯示 id 前 8 碼:
```
TIME CATEGORY EVENT ACTOR ACCOUNT
2026-09-09T01:00:00Z account account_created_by_admin admin@example.com alice@example.com
```
**`--json` 輸出範例**:`{"ok": true, "page": 1, "per_page": 50, "total": 1, "total_pages": 1, "emails": {…}, "entries": […]}`
---
## 4. 退出碼總表
| 碼 | 語意 |
@@ -524,7 +680,7 @@ TOTP 兩因子管理(WebAuthn/passkey 不在範圍)。
| `5` | 授權被拒絕(access_denied) |
| `6` | 網路/伺服器錯誤 |
| `7` | 設定檔/憑證檔格式損毀 |
| `8` | 權限不足(HTTP 403;App 管理操作僅限 admin) |
| `8` | 權限不足(HTTP 403;管理端操作僅限 admin:apps/jwks/accounts/audit-logs) |
---
@@ -613,6 +769,7 @@ TOTP 兩因子管理(WebAuthn/passkey 不在範圍)。
| `status` | 顯示 access token 剩餘秒數 | 純本機判定:顯示模式(PAT/BEAR_TOKEN 環境變數)與 issuer |
| `apps` | P3 暫緩 | ✅ 已實作(issue #10):`list`/`show`/`create`/`update`/`rotate-secret`/`toggle`,對接 alterminal/bear#28 的 `/api/v1/apps` JSON API(PAT Bearer、admin 限定)。`client_id` → UUID 由 CLI 以 list 解析;一次性 `client_secret` 僅於 create/rotate-secret 成功當下輸出 |
| `profile`/`password`/`email`/`sessions`/`tokens`/`mfa` | P3 暫緩 | ✅ 已實作(issue #11):`profile show/set`、`password change`、`email change`、`sessions list/revoke/revoke-others`、`tokens list/create/revoke`、`mfa status/setup/disable/recovery-codes`,對接 alterminal/bear#38 的 `/api/v1/profile*` JSON API(PAT Bearer、任何有效帳號)。敏感輸入互動提示;一次性明文(PAT 明文、recovery codes)僅於成功當下輸出;mfa setup 的 ASCII QR 由本機 eqrcode 生成 |
| `jwks`/`accounts`/`audit-logs` | P3 暫緩 | ✅ 已實作(issue #12):對接 alterminal/bear#46 的管理端 JSON API(PAT Bearer、admin 限定);規格見 §3.8–§3.10 |
退出碼差異:`login`/`whoami` 的 token 無效(401)依 §3.1 為 `3`(本節實作一致)。