feat: 完善 OIDC 页面级分析连接与会话隔离 - #239
Conversation
Review 结论:FIX_FIRST(当前不要合入)本轮评审针对根仓 PR 头提交 整体架构方向是合理的:Viewer 继续使用页面本地 WASM,AI 后端连接使用当前页面/当前 Trace 的 scoped lease;OIDC 身份、SSE、overlay、session 与持久化数据也在向页面级隔离收敛。但是当前存在两个必须先修的功能问题,以及一个必须遵守的子模块落地顺序。请按下面 1~10 项逐项完成,不要只让 CI 变绿而跳过行为边界。 1. 修复非 OIDC 请求被无条件改成
|
| 场景 | 期望 |
|---|---|
| OIDC,调用方未指定 credentials | credentials: 'include' |
| OIDC mutation | credentials: 'include',并按现有逻辑附加 CSRF header |
| 非 OIDC,调用方未指定 credentials | 不新增 credentials 字段,保持浏览器默认行为 |
| 非 OIDC,调用方显式指定 credentials | 保留调用方的显式值 |
| OIDC 401 | 继续触发当前的 authority invalidation/reload 路径 |
| 非 OIDC 401 | 不清除 OIDC page authority 状态 |
推荐的最小修复是让共享 helper 直接复用已有条件化策略:
return fetch(input, withSmartPerfettoAuth(init))
.then(handleSmartPerfettoAuthResponse);如果采用其他实现,也必须满足上面的行为表。不要通过把所有旧测试的期望改成 include 来“修复”红灯;这里需要修的是 OIDC 边界外的行为扩张。
2. 修正并补全 credentials 回归测试
当前 PR 新增的 ui/src/core/smartperfetto_backend_fetch_unittest.ts 第一条测试名称说验证 HttpOnly OIDC cookie,但测试本身没有启用 OIDC,导致它把错误的全局默认行为固化了。
请至少完成这些测试:
- 测试 OIDC 默认 cookie 时,显式设置:
并准备需要的有效 session。
window.__SMARTPERFETTO_CONFIG__ = {oidcEnabled: true};
- 新增非 OIDC 测试:未显式传 credentials 时,最终 fetch init 的
credentials必须是undefined。 - 保留 OIDC mutation 的 CSRF +
include覆盖。 - 保留非 OIDC 401 不触发 OIDC authority invalidation 的覆盖。
- 保留显式 credential mode 的覆盖,确保调用方明确传值时不会被非 OIDC helper 改写。
- 以下已有测试必须在不篡改其非 OIDC契约的前提下重新通过:
ui/src/plugins/com.smartperfetto.AIAssistant/codebase_api_unittest.ts:51ui/src/plugins/com.smartperfetto.AIAssistant/codebase_api_unittest.ts:107ui/src/plugins/com.smartperfetto.AIAssistant/codebase_api_unittest.ts:203ui/src/plugins/com.smartperfetto.AIAssistant/provider_events_unittest.ts:106
已复现证据:
- feat: 完善 SmartPerfetto OIDC 会话与分析连接隔离 perfetto#15 全量 UI Vitest:202 个 test files 中 200 passed、2 failed;3150 tests passed、1 skipped、4 failed。
- 4 个失败全部是
expected credentials to be undefined, received "include"。 - 同一条全量测试命令在基线子仓提交
4a4fae46a08cac4177cdb6339832729121272c19上:195 个 test files 全部通过,3095 tests passed、1 skipped。 - 因此这不是本机既有环境噪声,而是本 PR 引入的契约冲突。
3. 修复 OIDC session 失效后 AI Panel 的无保护身份读取
相关控制流:
ui/src/core/smartperfetto_request_context.ts- OIDC 模式下,tenant/user/workspace getter 会调用
requireSmartPerfettoAuthSession()。 - session 不存在时会抛异常;这是正确的 fail-closed 语义,不应改成默认身份。
- OIDC 模式下,tenant/user/workspace getter 会调用
ui/src/plugins/com.smartperfetto.AIAssistant/ai_panel.ts:3517view()无保护调用getSmartPerfettoRequestContext()。
ui/src/plugins/com.smartperfetto.AIAssistant/ai_panel.ts:6768附近- analysis-context 保存/加载路径也会无保护读取 request context。
handleOidcAuthSessionChange()- session 过期、logout、401 或跨 tab invalidation 后会清理运行态并触发 redraw。
初始未登录页面虽然由 index.html bootstrap gate 拦住,但 session 可以在 AIPanel 已挂载后失效。此时 redraw 仍会进入上述无保护调用,导致渲染或后续设置路径抛异常,而不是稳定展示重新登录/不可用状态。
修复要求:
- OIDC 模式下,如果
getSmartPerfettoAuthSession()不存在,AIPanel 必须进入明确的 unauthenticated/auth-expired UI 状态。 - 在该状态下,不得调用任何需要 tenant/user/workspace 的读取、保存或恢复函数。
- 不得回退到 localStorage 中的默认 tenant/user/workspace,也不得伪造占位 identity;OIDC 必须继续 fail closed。
- session invalidation 后继续完成现有 teardown:停止 SSE/异步请求、清理 overlay/note、清空 agent session/run/trace/lease 绑定、使旧 generation 的异步回调失效。
- auth-expired 状态应禁用发送分析、保存 analysis context、恢复旧会话等依赖身份的动作,并提供明确的重新登录或刷新提示。
- session 恢复后,只能从新的服务端 session 重新构造 request context,并重新加载这个新 identity scope 下的状态;旧用户/旧 workspace 的状态不得复活。
- 入口保护需要覆盖
oninit、view、settings/analysis-context change、auth invalidation 后 redraw,以及 panel 重新挂载/模式切换,不要只在其中一个调用点加 try/catch。 - 建议提供一个语义清晰的“可选 OIDC request context”入口或 AIPanel 级 guard;不要在多个调用点散落吞异常的 catch。
4. 为 OIDC no-session / auth-expiry 补回归测试
至少新增以下场景:
oidcEnabled: true且 session 为undefined时,AIPanel 初始化和view()不抛异常。- 有效 OIDC session 下挂载 AIPanel,然后清除 session 并触发
smartperfetto-auth-session-changed,下一次 redraw 不抛异常。 - 上述 invalidation 后,SSE、active story/operation、analysis request generation、overlay/note、backend connection 和 session save timer 均按现有契约完成 teardown。
- invalidation 后打开/切换 tab、sidebar、floating panel 或 settings,不会因读取 request context 崩溃。
- invalidation 状态下不能发送分析请求,也不能写入旧 identity 的 analysis context/session storage。
- 注入一个新的有效 session 后,只恢复新 tenant/user/workspace scope 的状态。
- 非 OIDC local/API-key 模式的初始化、provider/codebase 请求和现有 session 行为保持不变。
测试应验证用户可观察结果和身份隔离,不要只验证某个 mock 函数“被调用”。
5. 在 Perfetto 子仓先完成源码修复和完整验证
上述两组源码修复应先提交到 Gracker/perfetto#15。至少运行:
node ui/build.mjs --typecheck --no-build --no-wasm --no-depscheck
node ui/build.mjs --run-unittests --no-build --no-wasm --no-depscheck针对性复现可用:
ui/node_modules/.bin/vitest run \
--config ui/vitest.config.mjs \
ui/src/core/smartperfetto_backend_fetch_unittest.ts \
ui/src/plugins/com.smartperfetto.AIAssistant/codebase_api_unittest.ts \
ui/src/plugins/com.smartperfetto.AIAssistant/provider_events_unittest.ts \
ui/src/plugins/com.smartperfetto.AIAssistant/ai_panel_navigation_unittest.ts完成标准是:全量 UI tests 0 failure,新增 auth-expiry 场景真实通过,类型检查通过,git diff --check 通过。
Gracker/perfetto#15 当前的 enforce-reviews failure 是上游针对 fork PR 的 Google 组织 review 策略,不是本次源码问题;不要为了它篡改仓库 workflow。需要关注的功能门禁是本地全量 UI tests,以及该 PR 的 ui / ui、repo-checks / repo-checks 最终结果。
6. 子仓修完后,重新同步根仓 gitlink 和生成的 frontend
根 PR 当前 gitlink 指向 3a2bb0cd7cab1461e69d5266d2e28fc5a8dd73c0。修复后必须:
- 让根仓
perfetto/gitlink 指向包含上述修复的子仓提交。 - 不要手改根仓生成的
frontend/文件。 - 从根仓运行:
./scripts/update-frontend.sh npm run check:frontend-prebuild
- 确认生成的 frontend version、bundle、service worker、manifest/hash 与新的子仓提交一致。
- 只提交实际重新生成的产物,不要夹带本地缓存、out 目录或无关格式化。
7. 重新运行根仓验证
更新根仓 gitlink和 frontend 后,至少运行当前项目定义的门禁:
cd backend && npm run build
cd ..
npm run check:frontend-prebuild
npm run verify:pr
git diff --check同时保留并重跑本 PR 已新增的后端回归:
backend/src/__tests__/runtimeEnvironment.test.tsbackend/src/routes/__tests__/enterpriseTraceMetadataRoutes.test.ts
当前评审中这两个 Jest suite 共 23 tests 已通过;修复 UI 后不能破坏这些后端结果。
如果本地 npm run verify:pr 仍受明确的 Rust toolchain 版本限制,请贴出精确失败阶段和版本信息,并以 GitHub 对应 runner 的成功结果补足,不能笼统写“应该通过”。
8. 做一次架构级简化复核,不要扩大修复范围
修复时请检查以下边界:
- credentials/CSRF 策略只有一个权威 helper,避免 provider、codebase、AI panel 各自 hardcode。
- OIDC request context 只有服务端 session 是身份真相;不得从浏览器持久化恢复身份。
- auth invalidation teardown 与正常 trace switch/panel disposal 使用一致的 generation/cancellation 语义。
- Viewer 的 WASM target 与 AI backend 的 scoped lease 继续保持分离。
enterprise_auth_card.ts、analysisbackendconnection.ts已较大;如果新修复继续增加分支,应提取明确的 auth gate/connection state helper。但不要为了“拆文件”做无行为收益的大规模重构。- 只简化本次相关代码;不要顺手修改无关文件或生成物模板之外的内容。
9. 完成真实场景 QA
自动测试全部通过后,至少手工验证:
- OIDC 未登录时由 bootstrap gate 正常阻止进入分析页面。
- 登录后打开 trace,Viewer 使用 WASM,不注册/复用用户的全局 HTTP RPC target。
- 页面级 AI lease 从 preparing 到 ready,分析请求可以正常发起。
- AIPanel 打开期间执行 logout,页面不抛异常、不继续写回旧会话,并显示明确的重新登录状态。
- AIPanel 打开期间模拟 session 401/expiry,结果同上。
- 跨 tab invalidation 后,旧 tab 不继续 SSE/analysis/overlay 写回。
- workspace/session 切换后,不恢复旧 workspace 的 trace/session/run/lease/analysis-context。
- 非 OIDC local/API-key 模式下,provider switcher、provider form、codebase picker/register/delete 和普通 AI 请求继续工作,未显式指定时不强制
credentials: 'include'。 - Docker/
./start.sh使用提交后的frontend/,无需现场重新编译 UI。
请在 PR 评论中记录 QA 环境、实际操作、结果和仍未覆盖的边界,不要只写“手测通过”。
10. 合入顺序与最终交付证据
完成后请在本 PR 贴出:
- Perfetto 修复提交 SHA。
- 根仓更新后的 head SHA。
- 全量 Perfetto UI test 摘要。
- typecheck 摘要。
- 根仓
npm run verify:pr摘要。 npm run check:frontend-prebuild结果。- GitHub CI 最终状态。
- 手工 OIDC expiry/logout 与非 OIDC 回归结果。
- 明确说明是否仍有 deferred 项及原因。
合入顺序必须是:
- 先合入 feat: 完善 SmartPerfetto OIDC 会话与分析连接隔离 perfetto#15,使修复提交从
Gracker/perfetto/main或其祖先关系上持久可达。 - 如果子仓采用 squash/rebase/merge 后落地主提交发生变化,更新根仓 gitlink 到实际已落地提交,并重新生成
frontend/。 - 确认根仓 PR feat: 完善 OIDC 页面级分析连接与会话隔离 #239 的所有检查与上面验收项通过。
- 最后再合入根仓 PR feat: 完善 OIDC 页面级分析连接与会话隔离 #239。
在这两个功能问题修复、完整 UI tests 归零、以及子仓提交 durable landing 之前,本 PR 保持不合入。
|
已更新 fork 分支,根仓当前提交为 已将子模块 gitlink 同步至 此 PR 继续依赖子仓 PR #15。 |
变更说明
frontend/预构建产物,便携/Docker 路径无需重新编译 UI。依赖
验证
node ui/build.mjs --no-depscheck通过;npm run check:frontend-prebuild通过。/agent/analyze的 CSRF 403 已消失。npm run verify:pr:governance、文档、i18n、rendering、quality、frontend prebuild、Rust fmt 均通过;本机的 Debian cargo 1.75 无法解析仓库的 Cargo.lock v4,因此在cargo check前停止。该限制来自本地 Rust toolchain 版本,非本次源码断言失败。