portal-auth P3(#24 #25):Portal UI+/portal/data/* server-side enforce(不 merge,待總管審) #52

Merged
Leo merged 5 commits from portal-auth-p3-portal-ui into main 2026-07-14 07:19:02 +00:00
Owner

portal-auth P3:Portal UI+查詢 enforce(design §1/§3.3/§3.4/§5/§6;Gitea #24/#25)

不 merge,待總管審(B 類流程)。P1(#50)/P2(#51)之後的第三棒,本 SDD 最大件。順帶補上 P1 順延的 kbdb-proxy library 透傳一行,並補記 #47-#51 的 wiki 接關斷層(步驟 0)。

改動

檔案 內容
cypher-executor/src/routes/portal-data.ts(新) 安全核心/portal/data/* server-side enforce(search/entries/:id 逐筆驗庫/graph D-4 粗閘/workflows D-8 唯讀)
cypher-executor/src/routes/portal-ui.ts(新) GET /portal 單檔 HTML 殼:登入+搜尋(keyword/semantic/graph 三模式+source 溯源+卡片詳頁)+設定(改密碼/看權限/主題)+工作流(admin)
cypher-executor/src/routes/portal.ts export 共用 helpers;graphSourceLibraries/hasGraphAccess/workflowsVisible/portal/sessiongraph_allowed/workflows_visible;libraries PATCH 收 graph_source
cypher-executor/src/lib/portal-seeds.ts portal_library seed 加 graph_source slot;ensurePortalTemplates 冪等 grow 舊 template 缺的 slots
cypher-executor/src/routes/kbdb-proxy.ts P1 順延項:/kbdb/search/kbdb/entries 透傳 library(owner/admin 面自選庫;portal 面不經這)
cypher-executor/wrangler.toml / types.ts PORTAL_SHOW_WORKFLOWS(預設 admin)
tests/portal-data.test.ts(新 21 項)+ P2 session 測試補欄位+wrangler.test.toml 補 mock
system-dev/:wiki status(含 #47-#51 補記)+ portal-auth tasks.md 勾項

安全模型(vs console 的關鍵差異,design §3.3)

  • console:登入後把 CONSOLE_TENANT 下發前端直打 /kbdb/*portal:前端只持 session token,server 從 session→回讀 user record→注入 owner_idlibrary 後轉發 KBDB。
  • caller 自帶 owner_id/library 參數一律被 server 值靜默覆蓋(不回錯——回錯會變參數名 oracle)。
  • 越庫/跨租戶/不存在的 entry id 一律同一句 404(不洩存在性);graph 粗閘按 SDD 明定回 403(唯一例外)。
  • 隱藏頁/模式=路由層擋(server 403/404),前端 graph_allowed/workflows_visible 只是顯示提示。
  • 密碼/session token 不進 log;workflows 回應無 webhook_url/trigger 把手。

驗收證據表(本機端到端,隔離雙 worker 9790/9791,demo 8787/8788 未碰;47/47 全綠)

# 驗證 結果
1 bootstrap 無 console owner session → 401;有 → 建 admin
2 登入回應/HTML/session 零租戶字串grep '"tenant"'grep "'leo'" 0 命中)
3 user(finance) 搜尋:只命中 finance 1 筆;hr 內容 0 命中
4 越庫攻擊①:user 自帶 ?library=hr&owner_id=other-tenant → 仍只 finance 1 筆(參數被 server 覆蓋)
5 越庫攻擊②:拿 hr entry id 直打 /portal/data/entries/:id → 404
6 越庫攻擊③:拿跨租戶(owner_id=other-tenant)entry id → 404
7 404 訊息與「不存在 id」逐字相同(不洩存在性)
8 graph 粗閘:user 無 general → 403(upstream 完全沒被打);admin ["*"]/授 general 後 → 放行轉發(upstream 404=閘已過)
9 未標記 library 舊資料歸 general:有 general 者可讀(200)、無者 404;搜尋 fallback 同
10 admin 改權限 PATCH → 同一 session 立即生效(1 筆→2 筆,不需重登)
11 workflows:user 403;admin 200 唯讀(read_only:true、無 webhook_url/trigger)
12 admin ["*"] 搜尋見本租戶全部 3 筆;跨租戶第 4 筆不可見
13 未登入打 data API → 401;GET /portal 只回登入殼
14 HTML 斷言:title=Arcrun Portal(brand var)、零 X-Arcrun-API-Key、不打 /kbdb/*、零 Mira;inline JS node --check
15 semantic 未開 → 誠實降級 keyword+hint,library 照 enforce
16 改密碼全鏈:舊密錯 401→改成功→新密碼可登入

單元:cypher 154/155(唯一失敗=executor「不存在的零件」pre-existing,git stash 複驗)+新增 portal-data 21 項;kbdb 20/20;兩包 tsc exit 0;wrangler deploy --dry-run 打包過。

UI 重用取捨(一句話)

複製 console 樣式子集(紙感/明體/琥珀/深淺主題)而非抽共用模組——抽共用要動 console.ts(違「console 現狀不動」鐵律),代價=樣式雙份、改版要兩邊同步。

部署注意(merge 後,gated leo21c wrangler 直推)

  • 新 var PORTAL_SHOW_WORKFLOWS(預設 admin,可不設)。
  • semantic 的 library filter 線上路徑仍依 P1 部署清單:Vectorize library metadata index+POST /embed/backfill {"reindex":true}
  • 舊實例已 seed 過 portal_library 的:重跑一次 /init/seed(或任一次 bootstrap/登記庫)即冪等補 graph_source slot。

下一棒:P4(admin 帳號管理頁+庫目錄管理 UI)。

🤖 Generated with Claude Code

## portal-auth P3:Portal UI+查詢 enforce(design §1/§3.3/§3.4/§5/§6;Gitea #24/#25) **不 merge,待總管審**(B 類流程)。P1(#50)/P2(#51)之後的第三棒,本 SDD 最大件。順帶補上 P1 順延的 kbdb-proxy `library` 透傳一行,並補記 #47-#51 的 wiki 接關斷層(步驟 0)。 ### 改動 | 檔案 | 內容 | |---|---| | `cypher-executor/src/routes/portal-data.ts`(新) | **安全核心**:`/portal/data/*` server-side enforce(search/entries/:id 逐筆驗庫/graph D-4 粗閘/workflows D-8 唯讀) | | `cypher-executor/src/routes/portal-ui.ts`(新) | `GET /portal` 單檔 HTML 殼:登入+搜尋(keyword/semantic/graph 三模式+source 溯源+卡片詳頁)+設定(改密碼/看權限/主題)+工作流(admin) | | `cypher-executor/src/routes/portal.ts` | export 共用 helpers;`graphSourceLibraries`/`hasGraphAccess`/`workflowsVisible`;`/portal/session` 補 `graph_allowed`/`workflows_visible`;libraries PATCH 收 `graph_source` | | `cypher-executor/src/lib/portal-seeds.ts` | portal_library seed 加 `graph_source` slot;`ensurePortalTemplates` 冪等 grow 舊 template 缺的 slots | | `cypher-executor/src/routes/kbdb-proxy.ts` | P1 順延項:`/kbdb/search`+`/kbdb/entries` 透傳 `library`(owner/admin 面自選庫;portal 面不經這) | | `cypher-executor/wrangler.toml` / `types.ts` | `PORTAL_SHOW_WORKFLOWS`(預設 admin) | | `tests/portal-data.test.ts`(新 21 項)+ P2 session 測試補欄位+`wrangler.test.toml` 補 mock | | | `system-dev/`:wiki status(含 #47-#51 補記)+ portal-auth tasks.md 勾項 | | ### 安全模型(vs console 的關鍵差異,design §3.3) - console:登入後把 `CONSOLE_TENANT` 下發前端直打 `/kbdb/*`。**portal:前端只持 session token**,server 從 session→回讀 user record→注入 `owner_id`+`library` 後轉發 KBDB。 - caller 自帶 `owner_id`/`library` 參數**一律被 server 值靜默覆蓋**(不回錯——回錯會變參數名 oracle)。 - 越庫/跨租戶/不存在的 entry id 一律**同一句 404**(不洩存在性);graph 粗閘按 SDD 明定回 403(唯一例外)。 - 隱藏頁/模式=路由層擋(server 403/404),前端 `graph_allowed`/`workflows_visible` 只是顯示提示。 - 密碼/session token 不進 log;workflows 回應無 `webhook_url`/trigger 把手。 ### 驗收證據表(本機端到端,隔離雙 worker 9790/9791,demo 8787/8788 未碰;47/47 全綠) | # | 驗證 | 結果 | |---|---|---| | 1 | bootstrap 無 console owner session → 401;有 → 建 admin | ✅ | | 2 | 登入回應/HTML/session **零租戶字串**(`grep '"tenant"'`/`grep "'leo'"` 0 命中) | ✅ | | 3 | user(finance) 搜尋:只命中 finance 1 筆;hr 內容 0 命中 | ✅ | | 4 | **越庫攻擊①**:user 自帶 `?library=hr&owner_id=other-tenant` → 仍只 finance 1 筆(參數被 server 覆蓋) | ✅ | | 5 | **越庫攻擊②**:拿 hr entry id 直打 `/portal/data/entries/:id` → 404 | ✅ | | 6 | **越庫攻擊③**:拿跨租戶(owner_id=other-tenant)entry id → 404 | ✅ | | 7 | 404 訊息與「不存在 id」逐字相同(不洩存在性) | ✅ | | 8 | graph 粗閘:user 無 general → **403**(upstream 完全沒被打);admin `["*"]`/授 general 後 → 放行轉發(upstream 404=閘已過) | ✅ | | 9 | 未標記 library 舊資料歸 general:有 general 者可讀(200)、無者 404;搜尋 fallback 同 | ✅ | | 10 | admin 改權限 PATCH → **同一 session 立即生效**(1 筆→2 筆,不需重登) | ✅ | | 11 | workflows:user 403;admin 200 唯讀(`read_only:true`、無 webhook_url/trigger) | ✅ | | 12 | admin `["*"]` 搜尋見本租戶全部 3 筆;跨租戶第 4 筆不可見 | ✅ | | 13 | 未登入打 data API → 401;`GET /portal` 只回登入殼 | ✅ | | 14 | HTML 斷言:title=`Arcrun Portal`(brand var)、零 `X-Arcrun-API-Key`、不打 `/kbdb/*`、零 Mira;inline JS `node --check` 過 | ✅ | | 15 | semantic 未開 → 誠實降級 keyword+hint,**library 照 enforce** | ✅ | | 16 | 改密碼全鏈:舊密錯 401→改成功→新密碼可登入 | ✅ | 單元:cypher 154/155(唯一失敗=executor「不存在的零件」**pre-existing,git stash 複驗**)+新增 portal-data 21 項;kbdb 20/20;兩包 tsc exit 0;`wrangler deploy --dry-run` 打包過。 ### UI 重用取捨(一句話) 複製 console 樣式子集(紙感/明體/琥珀/深淺主題)而非抽共用模組——抽共用要動 console.ts(違「console 現狀不動」鐵律),代價=樣式雙份、改版要兩邊同步。 ### 部署注意(merge 後,gated leo21c wrangler 直推) - 新 var `PORTAL_SHOW_WORKFLOWS`(預設 admin,可不設)。 - semantic 的 library filter 線上路徑仍依 P1 部署清單:Vectorize `library` metadata index+`POST /embed/backfill {"reindex":true}`。 - 舊實例已 seed 過 portal_library 的:重跑一次 `/init/seed`(或任一次 bootstrap/登記庫)即冪等補 `graph_source` slot。 下一棒:P4(admin 帳號管理頁+庫目錄管理 UI)。 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Leo added 5 commits 2026-07-14 05:27:11 +00:00
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- portal.ts:export 共用 helpers(requirePortalUser/parseLibraries/kbdbFetch/run…)
  +graphSourceLibraries/hasGraphAccess/workflowsVisible;/portal/session 補
  graph_allowed、workflows_visible(顯示提示,真閘在 /portal/data/* 路由層)
- portal_library seed 加 graph_source slot;ensurePortalTemplates 冪等補舊 template
  缺的 slots(updateRecord 對 template 外 slot 會 reject,不補就寫不進標記)
- admin libraries PATCH 收 graph_source(boolean);toPublicLibrary 回 graph_source
- kbdb-proxy /kbdb/search 與 /kbdb/entries 白名單加 library(tasks.md P1 順延一行)
- PORTAL_SHOW_WORKFLOWS var(D-8,預設 admin)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- portal-data.ts(安全核心):search(server 注入 owner_id+library,caller 的
  owner_id/library 參數一律被覆蓋);entries/:id 逐筆驗租戶+庫(越庫/不存在同一句
  404 不洩存在性);graph D-4 粗閘(無權 403,SDD 明定例外);workflows D-8
  (admin/all/off,唯讀+最近執行,無 trigger、無 webhook_url)
- portal-ui.ts:獨立 HTML 殼(不動 console;樣式複製 console 設計語言子集)——
  登入/搜尋(三模式+source 溯源+卡片詳頁)/設定(改密碼/看權限/主題)/工作流
  (admin);前端零租戶字串、零 X-Arcrun-API-Key,只持 portal session token
- index.ts 掛載兩個新 router;tsc exit 0

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- portal-data.test.ts:/portal HTML 殼機械斷言(無 'leo'/X-Arcrun-API-Key//kbdb//Mira);
  search 注入證明(caller 帶 library=hr&owner_id=evil 被靜默覆蓋);entries/:id 越庫/
  跨租戶/不存在同一句 404+NULL→general;graph 403 不打 plugin/放行轉發/disabled
  來源排除;workflows 非 admin 403、admin 唯讀無 webhook_url、workflowsVisible 單元
- portal-auth.test.ts session 測試補 P3 能力欄位(graph_allowed/workflows_visible)
- wrangler.test.toml 補 ANALYTICS_KV mock+KBDB_GRAPH_URL 假 host(絕不外連)
- cypher 154/155(唯一失敗=executor 不存在零件 pre-existing,git stash 複驗);
  kbdb 20/20;兩包 tsc exit 0

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