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

- 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>
This commit is contained in:
uncle6me-web
2026-07-14 12:10:03 +08:00
parent 7f409646e5
commit dd65b06a11
11 changed files with 1372 additions and 14 deletions
+23 -8
View File
@@ -41,14 +41,29 @@
## P2 — portal_user 模型+認證 APIdesign §2/§4)|觸碰:`cypher-executor/`(新 route 檔)
- [ ] seed template `portal_user``portal_library`init-seed 慣例)
- [ ] PBKDF2-SHA256 雜湊模組(600k iters、常數時間比對、`pbkdf2-sha256$…` 格式)
- [ ] `POST /portal/admin/bootstrap`console owner session 閘)
- [ ] login/logout/session/改密碼(KV `portal_sess:`、TTL var、每請求回讀 record 驗 status
- [ ] admin 用戶 CRUDlibraries 授權+庫目錄 CRUDrole=admin 閘
- [ ] 登入失敗節流(5 次/15 分鐘,KV TTL
- [ ] 順手修 `record-crud.ts` updateRecord grow 路徑漏 owner_iddesign §2.2 附帶)
- [ ] 測試:bootstrap 閘、登入對錯、停用即拒(session 立即失效)、role 閘、雜湊格式、`{tenant}::portal` 子 namespace 隔離(**搜 email 搜不到**
- [x] seed template `portal_user``portal_library`init-seed 慣例)
- 實作註(2026-07-14):種子資料 `cypher-executor/src/lib/portal-seeds.ts`*-seeds.ts 慣例類別);
冪等 ensure 在 `routes/portal.ts` `ensurePortalTemplates()`,掛進 `POST /init/seed`(回應加
`portal_templates` 區塊)+ bootstrap 前自動 ensure(沒跑過 seed 也能 bootstrap)。
- [x] PBKDF2-SHA256 雜湊模組(600k iters、常數時間比對、`pbkdf2-sha256$…` 格式
- `cypher-executor/src/lib/portal-auth.ts`WebCrypto deriveBitsverify 從儲存格式解析 iterations
→ 換 KDF/參數可共存漸進遷移;constantTimeEqual 同 mcp PR#15 實作)。
- [x] `POST /portal/admin/bootstrap`console owner session 閘
- [x] login/logout/session/改密碼(KV `portal_sess:`、TTL var、每請求回讀 record 驗 status
- `PORTAL_SESSION_TTL` 進 wrangler.toml [vars]types.ts(預設 604800)。
- [x] admin 用戶 CRUDlibraries 授權+庫目錄 CRUDrole=admin 閘)
- 安全註:admin 操作目標 record 需過「email head entry 指回同 record_id」成員驗證(防拿任意
record_id 改到不相干 KBDB record);列表/回應一律剝除 password_hash;未帶密碼建帳號 →
server 產一次性密碼隨回應回傳一次(不落地明碼)。
- [x] 登入失敗節流(5 次/15 分鐘,KV TTL
- [x] 順手修 `record-crud.ts` updateRecord grow 路徑漏 owner_iddesign §2.2 附帶)
- 修法:首查 JOIN entries 帶回 record 既有 owner_idgrow 的新 entry 沿用。回歸測試
`kbdb/tests/record-grow-owner.test.ts`3 項:帶 owner/原無 owner 維持 null/非 grow 不 INSERT)。
- [x] 測試:bootstrap 閘、登入對錯、停用即拒(session 立即失效)、role 閘、雜湊格式、`{tenant}::portal` 子 namespace 隔離(**搜 email 搜不到**
- 單元:`cypher-executor/tests/portal-auth.test.ts` 20 項(fetchMock 假 KBDB host,絕不外連);
cypher 全套 133/134(唯一失敗=executor「不存在的零件」pre-existingstash 複驗);kbdb 20/20
兩包 tsc exit 0。端到端:本機雙 worker(隔離 persist33/33 全綠,含**搜尋隔離實測**
(租戶 ns 搜 email/雜湊 0 筆+對照組 leo::portal 搜得到=非假綠)與明碼落地抽查 0 筆。
- **驗收**=#25 驗收):新增用戶能登入;停用登入被拒+既有 session 失效;KBDB 無新表;密碼抽查非明碼
- **工程量**:中(約 1 個 CC 工作天)