Files
Arcrun/system-dev/docs/3-specs/portal-auth/tasks.md
T

151 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# portal-auth — Tasks(實作切分,每階段可獨立 PR)
> 狀態:**全部未動工——本 SDD 待總管/leo 審後才開工**(B 類流程:SDD 審 → 實作 PR → component-pr-review-standard → 總管 review → gated 部署 leo21c
> 對應:Gitea #24/#25design.md 各 §
依賴鏈:P1 → P2 → P3 → P4(P2 不依賴 P1 可並行起,但 P3 要兩者都齊)。
---
## P1 — KBDB「庫」filter 地基(design §3.2/§3.3)|觸碰:`kbdb/`
- [x] 開工第一件事:實測 Vectorize metadata filter `$in` 支援與否(決定主路徑 vs fan-out fallback
- **核實結論(2026-07-14****支援,走主路徑 `$in`,不需 fan-out fallback**。證據:① 官方文件
developers.cloudflare.com/vectorize/reference/metadata-filtering/ 明列 8 運算子
`$eq/$ne/$in/$nin/$lt/$lte/$gt/$gte`,「For $in and $nin, filter object values can be arrays of
string, number, boolean, or null values」;② 本 repo `@cloudflare/workers-types@4.20260702.1`
原生 typing `VectorizeVectorMetadataFilterCollectionOp = "$in" | "$nin"`index.d.ts:15782),
wrangler 4.98.0。限制:metadata filtering 只對 2023-12-06 之後建的 index 有效(本專案 index 皆是)。
本機無 Vectorize runtime`$in` 的**線上實跑**併入部署排練驗(見部署清單)。
- [x] `/entries/search``/entries``library` 多值參數(D1 json_extract INNULL→general fallback
- 實作註:SQL 用 `COALESCE(json_extract(metadata_json,'$.library'),'general') IN (…)`——與 design §3.3
的 OR 形狀語意完全等價(單組佔位符較簡)。本機 miniflare+local D1 十案例實跑全過(PR 附證據)。
- [x] embed upsert metadata 加 `library` 欄;semanticSearch filter 支援 library**$in 主路徑**fan-out 不需)
- 實作註:Vectorize 端在**寫入時正規化**(未標記→`'general'`)——Vectorize filter 做不了 COALESCE
寫入端蓋章後查詢端單純 `$in`;與 D1 查詢端 fallback 語意對齊。
- [x] Vectorize `library` metadata index 建立步驟+reindex backfill 寫進部署清單
- **部署清單(待雲端排練驗——本機無 Vectorize runtimesemantic 路徑 code 完成、線上實跑未驗)**:
1. `wrangler vectorize create-metadata-index arcrun-kbdb-embed --property-name library --type string`
2. `POST /embed/backfill {"reindex":true}` 分批到 remaining=0index 只收建立後 upsert 的向量)
3. 驗收抽查:semantic 帶 `library=` 過濾命中/不帶行為不變
4. 掛號:`cli/src/lib/deploy.ts` `ensureVectorizeMetadataIndexes()` 待補 `library`cli/ 屬 P1
派工範圍外「只動 kbdb」,隨 P3 或部署 PR 補——kbdb/wrangler.toml 註解已標)
- [x] cypher `kbdb-proxy` 透傳 `library` 參數(供 owner/admin 面用;portal 面走 P3 的注入,不經這)
- ⚠️ 範圍註(2026-07-14):P1 派工紅線「只動 kbdb、不碰 cypher-executor」與本項矛盾——照窄範圍
執行,本項順延(一行 query 透傳,隨 P3 動 cypher 時一併)。
- ✅ 已隨 P3 補上(2026-07-14 P3 PR):`/kbdb/search``/kbdb/entries` 透傳白名單各加 `library`
- [x] 測試:D1 filter 單元測、NULL fallback、多值、semantic filtermock VECTORIZE
- `kbdb/tests/library-filter.test.ts` 11 項(SQL 形狀/route 解析/向後相容/降級仍 enforce/$in 構造/
寫入端正規化)+既有 6 項全綠(17/17);tsc exit 0。
- **驗收**curl `/entries/search?q=&library=finance` 只回 finance+未標記條目歸 general 可驗;semantic 同
- **工程量**:小-中(0.5–1 個 CC 工作天)
## P2 — portal_user 模型+認證 APIdesign §2/§4)|觸碰:`cypher-executor/`(新 route 檔)
- [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 工作天)
## P3 — `/portal` UI:登入殼+搜尋頁+設定頁+scope enforcedesign §1/§3.3/§5/§6)|觸碰:`cypher-executor/`
- [x] `/portal` HTML 殼(重用 console 樣式/搜尋 view 抽共用 helper`CONSOLE_BRAND` 品牌;零 Mira 字樣)
- 實作註(2026-07-14):`routes/portal-ui.ts` 單檔 HTML+原生 JSconsole 同形態)。**UI 重用取捨=
複製 console 樣式子集而非抽共用模組**——抽共用要動 console.ts(違「console 現狀不動」鐵律),
代價=樣式雙份、改版要兩邊同步(PR 說明誠實記)。零 Mira 字樣由測試機械斷言。
- [x] 未登入只見登入殼;登入後兩頁:搜尋(keyword/semantic/graph 三模式+source 溯源+卡片詳頁)+設定(改密碼/看自己權限/主題)
- graph 模式按 session `graph_allowed` 顯示(D-4);工作流頁 admin 額外可見(D-8,
`PORTAL_SHOW_WORKFLOWS` 預設 admin,唯讀不開 trigger)——前端藏只是 UX,真閘在路由層。
- [x] `/portal/data/*` server-side enforcesession→record→注入 `owner_id``library`**前端絕不下發租戶字串**
- `routes/portal-data.ts`(安全核心):caller 自帶 owner_id/library 一律被 server 值靜默覆蓋
e2e 攻擊實測);`["*"]`=不注 library;HTML 殼經測試機械斷言零租戶字串/零
X-Arcrun-API-Key/不打 /kbdb/*。
- [x] 卡片詳頁逐筆驗 library(越庫 id 直讀 → 404
- 雙重驗證:owner_id=CONSOLE_TENANT(防拿跨租戶 id 直讀)+library∈用戶集合(NULL→general);
越庫/跨租戶/不存在**同一句 404**(不洩存在性,e2e 逐字比對)。
- [x] graph 粗閘(D-4:無 graph 來源庫權限 → 模式不顯示+API 403)
- 來源庫=portal_library `graph_source='true'` 集合(seed 補 slotensurePortalTemplates 冪等
grow 舊 template 缺的 slotsadmin libraries PATCH 可標記/取消),**沒有任何標記→預設
['general']**D-4 定案);無權 403、前端模式鈕不顯示;停用庫不算來源。
- [x] 測試:curl 帶 user session 直打 data API 驗 filter 繞不過(=#24 驗收 3 的 server-side 證明)
- 單元:`cypher-executor/tests/portal-data.test.ts` 21 項(fetchMock 假 host,絕不外連);
cypher 全套 154/155(唯一失敗=executor「不存在的零件」pre-existingstash 複驗);
kbdb 20/20;兩包 tsc exit 0。端到端:本機隔離雙 worker9790/9791demo 8787/8788 不碰)
**47/47 全綠**——含越庫 id 直讀 404、攻擊參數被覆蓋、graph 403→授 general 後放行、
admin 全庫+跨租戶不可見、權限 PATCH 即時生效、HTML 殼斷言+inline JS node --check、
改密碼全鏈。`wrangler deploy --dry-run` 打包過。
- **驗收**=#24 驗收):leo21c `/portal` 可開;未登入只見登入殼;三模式+溯源可用;A 用戶(僅 general)搜不到 finance 內容——UI 與 curl 雙驗
- 本機端到端已全數驗過(上項證據);**leo21c 線上面=部署後驗**gated wrangler 直推,B 類流程;
semantic 線上路徑仍依 P1 部署清單的 Vectorize indexreindex)。
- **工程量**:大(1–1.5 個 CC 工作天,UI 是最大件)
## P4 — admin 頁+工作流顯示(design §6)|觸碰:`cypher-executor/`
- [x] 帳號管理頁(admin-only nav):列表/新增/停用/重設密碼+每帳號庫權限勾選
- 實作註(2026-07-14):`portal-ui.ts` 加「管理」頁(v-adminnav 只對 role=admin 顯示,
真閘=/portal/admin/* role 閘 403)。新增未帶密碼 → server `generatePassword` 一次性
密碼**只在畫面顯示一次**(otpbox,關閉即逝;server 只存 hash);每帳號庫勾選含
`["*"]` 全庫選項(勾選連動停用個別庫框);可勾清單=庫目錄啟用中 ∪ general ∪ 該帳號
既有授權(目錄外舊授權不憑空消失)。admin API P2 已交付不重做;本波補
**last-admin 鎖死保護**(總管派工明定,design 未明寫——PATCH 停用/降級「最後一個
active admin」→ 409;只在目標是 active admin 且 patch 會使其失格時多打一次 list)。
- [x] 庫目錄管理(登記/停用庫)
- 同頁第二區:登記(name/display_name/description)+停用/啟用+ `graph_source`
圖譜來源標記(D-4,P3 已有 API,本波補 UI)。
- [x] 工作流頁(唯讀 list+最近執行;`PORTAL_SHOW_WORKFLOWS` 預設 admin;不開 trigger)
- **P3 已交付**`/portal/data/workflows` routev-workflows 頁+D-8 閘),P4 沿用零改動。
- **驗收**:admin 全流程「發帳號→授庫→同仁登入查詢→停用」在 UI 走通;一般用戶看不到 admin 頁與(預設下)工作流頁
- 單元:`tests/portal-admin.test.ts` 14 項(鎖死保護含「另一 admin 是 disabled 不算數」
「停用一般 user 不多打 list」;一次性密碼/reset 明碼不進 KBDB bodyHTML 殼加 admin 頁後
仍零租戶字串/零 /kbdb//零 API key/零 Mira)。cypher 全套 168/169(唯一失敗=executor
「不存在的零件」pre-existing);kbdb 20/20;兩包 tsc 0dry-run 打包過;inline JS
node --check 過(2 段)。
- 端到端:本機隔離雙 worker9790/9791demo 8787/8788 不碰)**41/41 全綠**——bootstrap→
admin 建帳號拿一次性密碼→新 user 登入→勾庫後**同一 session 搜尋範圍即時變化**(1 筆→2 筆、
越庫 404→200)→停用 session 立即失效→重設密碼舊密 401 新密 200→last-admin 409(含降級
即時生效:原 admin session 打 admin API 變 403)→一般 user 打 admin API 403。
- leo21c 線上面=部署後驗(gated wrangler 直推,B 類流程)。
- **工程量**:中(約 1 個 CC 工作天)
---
## BugfixP1-P4 交付後)
- [x] **PBKDF2 600k → 100kT6-cloud 部署抓到的框架蟲,2026-07-14,分支 `fix-pbkdf2-cf-limit`**
CF Workers 正式 runtime PBKDF2 上限 100,000 iterations600k 時 `crypto.subtle.deriveBits`
直接拒絕 → `/portal/admin/bootstrap` 真雲 500uncle6 實撞,證據 arcrun-rag
`docs/manual/uncle6-deploy-record.md`);miniflare 無此限制=本機測試全綠假象。
修:`portal-auth.ts` `PBKDF2_ITERATIONS` 100_000+註明平台封頂;design D-5 已加修訂註。
verify 相容性:迭代數從儲存值解析,補測試「600k 舊 hash 仍通」(miniflare 造);誠實邊界
=真雲 deriveBits 同樣封頂、600k hash 真雲仍拒,但真雲 bootstrap 從未成功過 → 無存量 hash、
無遷移面。同 PR 順手:`.component-builds/http_request/wrangler.toml`
`global_fetch_strictly_public` flagself-hosted 同帳號 fetch 1042 正解,Arcrun#33 同族,
官方行為不變原理同 cypher 前例 rules/03 1042 段)。
## 第二波(不在本 SDD 動工範圍,掛號)
- MCP token 綁庫集合(design §9PR#15 擴充,只動 `mcp/`
- graph 逐節點/逐邊細粒度過濾
- Google OAuth2 fast-follow、審計 log、忘記密碼 email 自助、舊資料 library 回填清潔工