portal-auth P2(#24 #25):portal_user 模型+認證 API(不 merge,待總管審) #51

Merged
Leo merged 1 commits from portal-auth-p2-users-auth into main 2026-07-14 04:19:54 +00:00
Owner

範圍(SDD portal-auth tasks.md P2,design §2/§4/§5)

P2=用戶模型+認證 API。不含 P3 UI/P4 admin 頁。console-auth 既有行為零改動。

實作

位置 要點
KDF 模組 cypher-executor/src/lib/portal-auth.ts PBKDF2-SHA256 600k(WebCrypto deriveBits)、格式 pbkdf2-sha256$600000$salt$hash(verify 從儲存值解析 iterations=漸進遷移)、常數時間比對(同 mcp PR#15 實作)。rule 2.1/2.2 界線照 design D-5:UI session 登入=console-auth 同類先例,非 workflow credential 原語,hook 未擋
template seed lib/portal-seeds.tsroutes/init-seed.ts portal_user(8 slots,§2.1)+portal_library(§3.2),created_by='system';冪等 ensure 掛 /init/seed+bootstrap 前自動補。零新表
認證 API routes/portal.ts login / logout / session / me/password / bootstrap(console owner session 閘,D-7) / 節流(5 次鎖 15 分,KV TTL)
admin API 同上 users list/create/patch(status·role·libraries)/reset-password+庫目錄 CRUD(role=admin 閘)。目標 record 需過「email head entry 指回同 record_id」成員驗證;回應一律剝 password_hash
子 namespace design D-2 一切帳號資料 owner_id="{CONSOLE_TENANT}::portal"+email head entry O(1) 查找(§2.3)
session design §4.3 KV portal_sess:{token} 只存 record_id、TTL PORTAL_SESSION_TTL([vars] 預設 604800);每請求回讀 record=停用/改權限即時生效;/portal/session 絕不回租戶字串
順修 bug kbdb/src/actions/record-crud.ts updateRecord grow-slot 建 entry 漏 owner_id → JOIN 帶回 record 既有 owner_id 沿用+回歸測試 kbdb/tests/record-grow-owner.test.ts

驗收證據表(本機真跑)

# 項目 指令/方式 結果
1 kbdb 測試 npx vitest run 20/20 綠(含 grow-owner 回歸 3 項)
2 cypher 測試 npx vitest run 133/134(新增 portal-auth 20 項全綠;唯一失敗=executor「不存在的零件」stash 複驗 pre-existing
3 tsc 兩包 npx tsc --noEmit exit 0
4 端到端 本機雙 worker(kbdb:8971/cypher:8972,--persist-to 隔離目錄,demo 8787/8788 未碰 33/33 全綠
4a seed POST /init/seed portal_templates created=[portal_user, portal_library]
4b bootstrap 閘 無 console session → 401;console setup→login→bootstrap → 200 建 admin;二次 bootstrap → 409
4c 登入 對→200 發 token(回應無 tenant 欄位、無 'leo' 字串);錯→401 通用訊息;停用→403
4d session 回讀 200(display_name/role/libraries,無租戶字串);停用後既有 session 立即 403 且 KV 清除
4e 改密碼 舊密錯 401;改成功後舊密登入 401、新密 200
4f 節流 連錯 5 次 → 第 6 次(正確密碼)429
4g role 閘 一般 user 打 /portal/admin/users → 403
4h 庫目錄 登記 200/重複 409/列表 count=1

搜尋隔離實測(design §10 P2 驗收明測)

查詢 結果
cypher proxy GET /kbdb/search?q=admin@corp.co(X-Arcrun-API-Key: leo) count=0
cypher proxy 搜同仁 email/搜 pbkdf2 count=0/0
KBDB base 直打 ?q=admin@corp.co&owner_id=leo count=0
對照組 owner_id=leo::portal 同 query count>0(資料真的在,證明非假綠)
全庫搜明碼 admin-pass-123 count=0(落地只有 pbkdf2-sha256$…

grow-slot 修復證據

  • 回歸測試 3 項綠(帶 owner/原 null 維持 null/非 grow 不 INSERT)
  • 端到端:portal_library record(缺 description slot)PATCH grow → 新 entry 以 owner_id=leo::portal 落地,owner-scoped 搜尋命中(修前為孤兒 NULL 搜不到)

撞到的問題

  • hook(pre-write-guard 2.x/7.x)全程未擋——檔名/內容照 D-5 界線避開黑名單字樣,無繞行。
  • 無 SDD 矛盾。tasks.md P1 遺留一項 [ ] cypher kbdb-proxy 透傳 library(P1 範圍註明順延隨 P3),本 PR 未動(屬 P3 注入路徑,非 P2 範圍)。

請總管 review,不自行 merge。 部署屬 gated leo21c wrangler 直推(B 類),不在本 PR。

🤖 Generated with Claude Code

## 範圍(SDD portal-auth tasks.md P2,design §2/§4/§5) P2=用戶模型+認證 API。不含 P3 UI/P4 admin 頁。console-auth 既有行為零改動。 ## 實作 | 件 | 位置 | 要點 | |---|---|---| | KDF 模組 | `cypher-executor/src/lib/portal-auth.ts` | PBKDF2-SHA256 **600k**(WebCrypto deriveBits)、格式 `pbkdf2-sha256$600000$salt$hash`(verify 從儲存值解析 iterations=漸進遷移)、常數時間比對(同 mcp PR#15 實作)。rule 2.1/2.2 界線照 design D-5:UI session 登入=console-auth 同類先例,非 workflow credential 原語,**hook 未擋** | | template seed | `lib/portal-seeds.ts`+`routes/init-seed.ts` | portal_user(8 slots,§2.1)+portal_library(§3.2),`created_by='system'`;冪等 ensure 掛 `/init/seed`+bootstrap 前自動補。零新表 | | 認證 API | `routes/portal.ts` | login / logout / session / me/password / **bootstrap(console owner session 閘,D-7)** / 節流(5 次鎖 15 分,KV TTL)| | admin API | 同上 | users list/create/patch(status·role·libraries)/reset-password+庫目錄 CRUD(role=admin 閘)。目標 record 需過「email head entry 指回同 record_id」成員驗證;回應一律剝 password_hash | | 子 namespace | design D-2 | 一切帳號資料 `owner_id="{CONSOLE_TENANT}::portal"`+email head entry O(1) 查找(§2.3) | | session | design §4.3 | KV `portal_sess:{token}` 只存 record_id、TTL `PORTAL_SESSION_TTL`([vars] 預設 604800);**每請求回讀 record**=停用/改權限即時生效;`/portal/session` **絕不回租戶字串** | | 順修 bug | `kbdb/src/actions/record-crud.ts` | updateRecord grow-slot 建 entry 漏 owner_id → JOIN 帶回 record 既有 owner_id 沿用+回歸測試 `kbdb/tests/record-grow-owner.test.ts` | ## 驗收證據表(本機真跑) | # | 項目 | 指令/方式 | 結果 | |---|---|---|---| | 1 | kbdb 測試 | `npx vitest run` | **20/20 綠**(含 grow-owner 回歸 3 項) | | 2 | cypher 測試 | `npx vitest run` | **133/134**(新增 portal-auth 20 項全綠;唯一失敗=executor「不存在的零件」**stash 複驗 pre-existing**) | | 3 | tsc | 兩包 `npx tsc --noEmit` | exit 0 | | 4 | 端到端 | 本機雙 worker(kbdb:8971/cypher:8972,`--persist-to` 隔離目錄,**demo 8787/8788 未碰**) | **33/33 全綠** | | 4a | seed | `POST /init/seed` | portal_templates created=[portal_user, portal_library] | | 4b | bootstrap 閘 | 無 console session → 401;console setup→login→bootstrap → 200 建 admin;二次 bootstrap → 409 | | 4c | 登入 | 對→200 發 token(回應無 tenant 欄位、無 'leo' 字串);錯→401 通用訊息;停用→403 | | 4d | session | 回讀 200(display_name/role/libraries,無租戶字串);停用後既有 session 立即 403 且 KV 清除 | | 4e | 改密碼 | 舊密錯 401;改成功後舊密登入 401、新密 200 | | 4f | 節流 | 連錯 5 次 → 第 6 次(正確密碼)429 | | 4g | role 閘 | 一般 user 打 `/portal/admin/users` → 403 | | 4h | 庫目錄 | 登記 200/重複 409/列表 count=1 | ## 搜尋隔離實測(design §10 P2 驗收明測) | 查詢 | 結果 | |---|---| | cypher proxy `GET /kbdb/search?q=admin@corp.co`(X-Arcrun-API-Key: leo) | **count=0** | | cypher proxy 搜同仁 email/搜 `pbkdf2` | **count=0/0** | | KBDB base 直打 `?q=admin@corp.co&owner_id=leo` | **count=0** | | 對照組 `owner_id=leo::portal` 同 query | **count>0**(資料真的在,證明非假綠) | | 全庫搜明碼 `admin-pass-123` | **count=0**(落地只有 `pbkdf2-sha256$…`) | ## grow-slot 修復證據 - 回歸測試 3 項綠(帶 owner/原 null 維持 null/非 grow 不 INSERT) - 端到端:portal_library record(缺 description slot)PATCH grow → 新 entry 以 `owner_id=leo::portal` 落地,owner-scoped 搜尋命中(修前為孤兒 NULL 搜不到) ## 撞到的問題 - hook(pre-write-guard 2.x/7.x)**全程未擋**——檔名/內容照 D-5 界線避開黑名單字樣,無繞行。 - 無 SDD 矛盾。tasks.md P1 遺留一項 `[ ] cypher kbdb-proxy 透傳 library`(P1 範圍註明順延隨 P3),本 PR 未動(屬 P3 注入路徑,非 P2 範圍)。 **請總管 review,不自行 merge。** 部署屬 gated leo21c wrangler 直推(B 類),不在本 PR。 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Leo added 1 commit 2026-07-14 04:11:06 +00:00
- lib/portal-auth.ts:PBKDF2-SHA256 600k(WebCrypto deriveBits)、pbkdf2-sha256$… 格式、
  常數時間比對(UI session 登入,console-auth 同類先例,非 workflow credential 原語)
- lib/portal-seeds.ts + init-seed:portal_user / portal_library template 冪等種入(零新表)
- routes/portal.ts:login/logout/session/me-password/bootstrap(console owner 閘)/
  admin users CRUD + reset-password / 庫目錄 CRUD;帳號資料全寫 {tenant}::portal 子 namespace
  (D-2)+ email head entry O(1) 查找(§2.3);session KV 只存 record_id、每請求回讀 record
  → 停用即時生效;絕不回租戶字串;登入節流 5 次/15 分(KV TTL)
- [vars] PORTAL_SESSION_TTL(預設 604800)+ types.ts
- 順修 kbdb record-crud updateRecord grow-slot 漏 owner_id(design §2.2)+回歸測試

驗證:kbdb 20/20、cypher 133/134(1 失敗 stash 複驗=pre-existing)、雙包 tsc 0;
本機雙 worker 端到端 33/33(含搜尋隔離實測:租戶 ns 搜 email/雜湊 0 筆、對照組
leo::portal 搜得到;明碼落地抽查 0 筆)。console-auth 既有行為未動;demo 8787/8788 未碰。

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