Skip to content

feat(access): 冷启动 httpOnly cookie 静默续期接线 - #65

Merged
ghbvf merged 3 commits into
developfrom
feat/access-cold-start-cookie
Jun 19, 2026
Merged

ghbvf merged 3 commits into
developfrom
feat/access-cold-start-cookie

Conversation

@ghbvf

@ghbvf ghbvf commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Summary

落地 #27 / #12 H2「冷启动无感续期」:依赖后端 BR-005(ghbvf/gocell#1278,已交付 httpOnly refresh cookie __Host-gocell_rt)完成前端接线。

  • useAuthStore.refresh() 改为 cookie 优先:去掉 if (!_refreshToken.value) return null 早退——无内存 token(冷启动/刷新/新标签)也发 POST /sessions/refresh(空 body)靠 cookie 续期;有内存 token 时作 body 兜底(对接后端双通道)。加 withCredentials。
  • login() / logout() 加 withCredentials:分别收后端 Set-Cookie 与 cookie 清除(Max-Age=0)。
  • T105 挂载 bootstrap 已就位(main.ts await bootstrapSession() 再 app.mount()),本 PR 让它真正生效;同步 bootstrap.ts / main.ts 过时注释。
  • guards 无需改:await-before-mount 时序保证首个守卫在续期之后跑 → 冷启动不闪 /login。
  • @gocell/access README 同步。

涉及包

  • @gocell/access:stores/useAuthStore.ts(核心)+ README
  • apps/web:bootstrap.ts / main.ts 注释 + bootstrap.spec.ts + e2e/auth.spec.ts

是否触及 packages/contracts/

否。 请求体未类型化(http.post 的 data 为 any),{} 可正常编译,无需改契约。

⚠️ 本地 pnpm codegen(对本地 ../gocell)会暴露与本 issue 无关的广泛契约漂移(多租户 tenantId 上 audit/role——会破坏 role assign/revoke 消费方——以及 device-identity/compliance/policy 新 cell)。这些应由独立的契约同步 PR 协同消费方处理,不混入 #27。已另开 issue 跟踪(见下)。

Test plan

  • pnpm -F @gocell/access typecheck
  • pnpm -F @gocell/access test --run(292)
  • pnpm -w lint / pnpm -w typecheck
  • pnpm -w test --run(1755)
  • pnpm -F @gocell/web build
  • npx playwright test e2e/auth.spec.ts(6,含 2 个冷启动冒烟:重载经 cookie 续期停留受保护页 / 无 cookie 落 /login)

验收对照

登录后整页重载 → 会话经 cookie 自动续期、停留受保护页(e2e 实测通过);无有效 cookie → 落 /login。token 全程不入 localStorage / sessionStorage(铁律单测守门)。

遗留(非本 PR 可做)

  • 跨源 CORS withCredentials + Access-Control-Allow-Credentials 联调:后端 CORS 中间件未实装(backend #1680)。本 PR 完成同源(dev proxy / prod gateway,实际部署模型)+ 前端接线;withCredentials 为跨源 forward-compat 预留。

Closes #27

- refresh() 改 cookie 优先:去早退,无内存 token 也发空 body 续期,内存 token 作 body 兜底
- login/refresh/logout 加 withCredentials,收发后端 __Host-gocell_rt cookie
- bootstrap.ts/main.ts 注释同步:bootstrapSession 经 cookie 主动续期,await-before-mount 不闪 /login
- access README 同步;单测覆盖 cookie 模式 + 冷启动;e2e 冷启动整页重载冒烟

依赖后端 BR-005 (ghbvf/gocell#1278,已交付)。契约未改(请求体未类型化,{} 可编译);
跨源 CORS 联调后端未实装(backend #1680)。

Refs #27

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ghbvf

ghbvf commented Jun 17, 2026

Copy link
Copy Markdown
Owner Author

契约漂移跟踪 issue:#66(多租户 tenantId / device-identity / policy 导出同步 + 修消费方)。本 PR 故意不纳入这些无关漂移。

六维度 review(2 reviewer,diff 216 行 → 2 档)Cx2 修复:
- refresh() 请求体显式标注 HttpAuthRefreshV1Request | Record<string,never>,
  契约重命名时编译期暴露(去回了对 @gocell/contracts 的引用)
- refresh() 加 timeout 兜底:bootstrapSession await 在 app.mount() 前,
  无超时的 http 实例遇后端挂起会永久白屏;本 PR 让 mount 依赖网络故放大该既有风险
- bootstrap.ts JSDoc 注明 refresh() 永不 reject(.finally(mount) 依赖此不变量)

a11y 维度无 finding;withCredentials per-request 归属经确认合理(HTTP 单点未破)。
遗留(见 PR 评论):@gocell/request 全局 timeout、bootstrap 测试重置钩、e2e __Host- cookie 保真度。

Refs #27

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ghbvf

ghbvf commented Jun 17, 2026

Copy link
Copy Markdown
Owner Author

Review 汇总(六维度,2 reviewer,diff 216 行 → 200–600 档)

维度切分 A:Vue 模板 + TS + 设计/产品;B:包边界 + a11y + 性能。两位 reviewer 一致认定核心逻辑(withCredentials 接线、bootstrap 幂等单飞、refreshPath 排除递归、best-effort logout、cookie-first 分支)正确无 P0 阻断。a11y 维度无 finding;withCredentials per-request 归属经确认合理(HTTP 单点未破)。

已修(本 PR 085165e)

Cx Finding 修复
Cx2 refresh() 请求体未引用契约类型,{} 分支逃逸约束 body 显式标注 HttpAuthRefreshV1Request | Record<string, never>,schema 重命名编译期暴露
Cx2 app.mount() await refresh 但 http 实例无 timeout → 后端挂起永久白屏(本 PR 让 mount 依赖网络,放大既有风险) refresh() 加 timeout: 10s 兜底
Cx3 .finally(mount) 依赖 refresh 永不 reject 的隐式不变量 bootstrapSession JSDoc 注明

遗留(不在本 PR,已记录)

Cx Finding 处置
Cx2/Cx3 @gocell/request http 实例无全局默认 timeout(既有问题,全请求面) 建议另开 issue(本 PR 已就 refresh 路径兜底)
Cx3 bootstrapSession 无 _resetBootstrapForTesting 钩(测试靠 vi.resetModules(),现状可用) 暂不加,避免 YAGNI 导出
Cx3 e2e 用 plain gocell_rt 而非真实 __Host-gocell_rt(localhost http 无法设 __Host-/Secure),保真度有限 测试注释已说明;真·保真需 HTTPS dev proxy
Cx3 e2e sessionData() 返回 Record<string,unknown> 未对齐契约 已评估后不改:stub 故意不强约束于契约;且 e2e 现无任何 @gocell/* import,为此引入跨包 import 性价比低
OUT_OF_SCOPE bootstrap.spec 内联类型 / useAuthStore.spec mock 双重强转 既有测试脚手架,非本 PR 引入,不顺手重构

@ghbvf

ghbvf commented Jun 17, 2026

Copy link
Copy Markdown
Owner Author

CI 说明

检查 结果
typecheck / lint / test / build ✅ pass
Playwright smoke(含本 PR 新增 2 个冷启动冒烟) ✅ pass
codegen-readonly ❌ 既有后端契约漂移,非本 PR 引入
cell-manifest-readonly ❌ 同上

两个 readonly 检查的红与本 PR 无关:本分支 packages/contracts/ 与 develop 字节一致(未改)。失败 diff 与后端 ghbvf/n 近期推进的多租户/device cell 改动完全吻合(详见 #66,已升 p1)。develop 上次 CI 绿是因后端在那之后才推进——任何新 PR 现都会撞红这两项。验证 #27 的两个检查(test/build + Playwright)均绿。

…tart-cookie

# Conflicts:
#	packages/access/src/stores/useAuthStore.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(access): 冷启动 httpOnly cookie 静默续期接线(依赖 BR-005)

1 participant