portal-auth P4(#24 #25):admin 帳號+庫管理頁+last-admin 鎖死保護(不 merge,待總管審) #53

Merged
Leo merged 4 commits from portal-auth-p4-admin-ui into main 2026-07-14 08:54:42 +00:00
Owner

portal-auth SDD 最後一棒(P1 #50 / P2 #51 / P3 #52 之後)。不 merge,待總管審(B 類流程)。

範圍(design §5/§6、tasks.md P4)

  1. admin API 補完——P2 已交付 users CRUD/reset-password/libraries(含 graph_source),不重做;本波只補缺的一塊:
    • last-admin 鎖死保護(總管派工明定;design 未明寫,誠實記):PATCH /portal/admin/users/:id 停用或降級「最後一個 active admin」→ 409。另一個 admin 是 disabled 不算數。只在「目標是 active admin 且 patch 會使其失格」時才多打一次 list(平時零成本)。
  2. /portal 管理頁 UI(admin-only nav;前端藏=UX,真閘=/portal/admin/* role 閘 403):
    • 帳號管理:同仁列表(email/顯示名/角色/狀態/可查庫)+新增(未給密碼 → generatePassword 一次性密碼只在畫面顯示一次,otpbox 關閉即逝)+停用/啟用+重設密碼+每帳號勾選可查庫(含 ["*"] 全庫選項,勾選連動停用個別庫框;可勾清單=庫目錄啟用中 ∪ general ∪ 該帳號既有授權)
    • 庫目錄管理:登記/停用/graph_source 圖譜來源標記(D-4;API P3 已有,本波補 UI)
  3. 工作流頁(P4 第三項)=P3 已交付(route+v-workflows+D-8 閘),零改動沿用。
  4. 不越界:MCP per-user token、審計 log、OAuth2 均未動(第二波清單)。

紅線自查

  • 零新 D1 表(帳號/庫目錄都是 KBDB 萬用表 record);帳號資料只在 {tenant}::portal
  • 密碼永不明碼儲存/不進 log;一次性密碼只出現在 API 回應一次,不落庫(庫裡只有 pbkdf2 hash,單元測試機械斷言 KBDB body 不含明碼)
  • demo 8787/8788 不碰(e2e 用 9790/9791 隔離 persist);console.ts 零改動

驗收證據表

驗收項 證據
admin 建帳號拿一次性密碼 e2e:generated_password 16 碼;回應無 password_hash;KBDB body 無明碼(單元+e2e)
新 user 登入成功 e2e:一次性密碼登入 200,回應零租戶字串
勾庫 → user 搜尋範圍即時變化 e2e:同一 user session,授權前搜 1 筆/越庫 id 404 → PATCH libraries 後 2 筆/同 id 200
停用 → session 立即失效 e2e:PATCH disabled 後既有 session 403、登入 403
重設密碼 e2e:舊密 401、新密 200
最後一個 admin 不可停用/降級 e2e:停用 409+訊息講人話、降級 409;建第二個 admin 後降級放行 200;降級即時生效(原 admin session 打 admin API 變 403);admin2 成唯一 admin 後自我停用又 409。單元 5 項含「另一 admin disabled 不算數」「停用一般 user 不多打 list」
一般 user 打 admin API/頁面 403 e2e:GET users 403、POST libraries 403;nav 由 session role 隱藏(真閘在 server)
庫目錄 e2e:登記 finance、graph_source PATCH true、重複登記 409
HTML 殼紅線不回退 單元+e2e:加 admin 頁後仍零租戶字串/零 /kbdb//零 X-Arcrun-API-Key/零 Mira

測試:新增 tests/portal-admin.test.ts 14 項;cypher 全套 168/169(唯一失敗=executor「不存在的零件」pre-existing,P2/P3 同筆);kbdb 20/20;兩包 tsc exit 0;wrangler deploy --dry-run 打包過;inline JS 求值後 node --check 過(mistakes #24 手法)。
端到端:本機隔離雙 worker(kbdb 9790/cypher 9791,wrangler.test.tomlKBDB_BASE_URL 覆蓋,獨立 persist)41/41 全綠

部署備忘(隨 T6-cloud 排練)

沿 P1 部署清單:Vectorize library metadata index+POST /embed/backfill {"reindex":true};gated leo21c wrangler 直推。本 PR 無新 var/secret(PORTAL_SESSION_TTL/PORTAL_SHOW_WORKFLOWS P2/P3 已進 toml)。

portal-auth SDD P1-P4 全完成,tasks.md 全 [x]、wiki status 已更新。

🤖 Generated with Claude Code

portal-auth SDD **最後一棒**(P1 #50 / P2 #51 / P3 #52 之後)。**不 merge,待總管審**(B 類流程)。 ## 範圍(design §5/§6、tasks.md P4) 1. **admin API 補完**——P2 已交付 users CRUD/reset-password/libraries(含 graph_source),**不重做**;本波只補缺的一塊: - **last-admin 鎖死保護**(總管派工明定;design 未明寫,誠實記):`PATCH /portal/admin/users/:id` 停用或降級「最後一個 active admin」→ **409**。另一個 admin 是 `disabled` 不算數。只在「目標是 active admin 且 patch 會使其失格」時才多打一次 list(平時零成本)。 2. **`/portal` 管理頁 UI**(admin-only nav;前端藏=UX,真閘=`/portal/admin/*` role 閘 403): - 帳號管理:同仁列表(email/顯示名/角色/狀態/可查庫)+新增(未給密碼 → `generatePassword` **一次性密碼只在畫面顯示一次**,otpbox 關閉即逝)+停用/啟用+重設密碼+每帳號勾選可查庫(含 `["*"]` 全庫選項,勾選連動停用個別庫框;可勾清單=庫目錄啟用中 ∪ general ∪ 該帳號既有授權) - 庫目錄管理:登記/停用/`graph_source` 圖譜來源標記(D-4;API P3 已有,本波補 UI) 3. 工作流頁(P4 第三項)=**P3 已交付**(route+v-workflows+D-8 閘),零改動沿用。 4. 不越界:MCP per-user token、審計 log、OAuth2 均未動(第二波清單)。 ## 紅線自查 - 零新 D1 表(帳號/庫目錄都是 KBDB 萬用表 record);帳號資料只在 `{tenant}::portal` - 密碼永不明碼儲存/不進 log;**一次性密碼只出現在 API 回應一次,不落庫**(庫裡只有 pbkdf2 hash,單元測試機械斷言 KBDB body 不含明碼) - demo 8787/8788 不碰(e2e 用 9790/9791 隔離 persist);console.ts 零改動 ## 驗收證據表 | 驗收項 | 證據 | |---|---| | admin 建帳號拿一次性密碼 | e2e:`generated_password` 16 碼;回應無 `password_hash`;KBDB body 無明碼(單元+e2e) | | 新 user 登入成功 | e2e:一次性密碼登入 200,回應零租戶字串 | | 勾庫 → user 搜尋範圍**即時**變化 | e2e:同一 user session,授權前搜 1 筆/越庫 id 404 → PATCH libraries 後 2 筆/同 id 200 | | 停用 → session 立即失效 | e2e:PATCH disabled 後既有 session 403、登入 403 | | 重設密碼 | e2e:舊密 401、新密 200 | | **最後一個 admin 不可停用/降級** | e2e:停用 409+訊息講人話、降級 409;建第二個 admin 後降級放行 200;降級**即時生效**(原 admin session 打 admin API 變 403);admin2 成唯一 admin 後自我停用又 409。單元 5 項含「另一 admin disabled 不算數」「停用一般 user 不多打 list」 | | 一般 user 打 admin API/頁面 403 | e2e:GET users 403、POST libraries 403;nav 由 session role 隱藏(真閘在 server) | | 庫目錄 | e2e:登記 finance、graph_source PATCH true、重複登記 409 | | HTML 殼紅線不回退 | 單元+e2e:加 admin 頁後仍零租戶字串/零 `/kbdb/`/零 X-Arcrun-API-Key/零 Mira | **測試**:新增 `tests/portal-admin.test.ts` 14 項;cypher 全套 168/169(唯一失敗=executor「不存在的零件」**pre-existing**,P2/P3 同筆);kbdb 20/20;兩包 tsc exit 0;`wrangler deploy --dry-run` 打包過;inline JS 求值後 `node --check` 過(mistakes #24 手法)。 **端到端**:本機隔離雙 worker(kbdb 9790/cypher 9791,`wrangler.test.toml`+`KBDB_BASE_URL` 覆蓋,獨立 persist)**41/41 全綠**。 ## 部署備忘(隨 T6-cloud 排練) 沿 P1 部署清單:Vectorize `library` metadata index+`POST /embed/backfill {"reindex":true}`;gated leo21c wrangler 直推。本 PR 無新 var/secret(`PORTAL_SESSION_TTL`/`PORTAL_SHOW_WORKFLOWS` P2/P3 已進 toml)。 **portal-auth SDD P1-P4 全完成**,tasks.md 全 [x]、wiki status 已更新。 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Leo added 4 commits 2026-07-14 07:40:36 +00:00
系統至少保留一個 active admin——bootstrap 只能跑一次(已有 admin 409),
若最後一個 admin 被停用/降級,帳號管理即永久鎖死。只在「目標是 active admin
且 patch 會使其失格」時多打一次 list(平時零成本)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 帳號管理:同仁列表(email/顯示名/角色/狀態/可查庫)+新增(一次性密碼只顯示
  一次,server 不留明碼)+停用/啟用+重設密碼+每帳號勾選可查庫(含 ["*"] 全庫
  選項,勾選連動停用個別庫)
- 庫目錄管理:登記/停用庫+graph_source 圖譜來源標記(D-4)
- nav 只對 admin 顯示(/portal/session role);前端藏只是 UX,真閘在
  /portal/admin/* role 閘 403(P3 guard 精神)
- 工作流頁 P3 已交付(v-workflows+D-8 閘),本次不重做

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
last-admin 409(含另一 admin 是 disabled 不算數、停用一般 user 不多打 list);
generated_password/reset-password 一次性回傳且明碼不進 KBDB body;libraries
["*"] 與壞庫名 400;庫目錄子 namespace+graph_source;/portal HTML 殼加 admin
頁後仍零租戶字串/零 /kbdb//零 X-Arcrun-API-Key/零 Mira。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Leo merged commit 52dbd31dea into main 2026-07-14 08:54:42 +00:00
Sign in to join this conversation.