### 问题 后端认证模块已在 PR #75 落地,鉴权中间件的白名单只放行 `/auth/*` 与文档、健康检查,其余接口一律要求有效的 access token。前端目前没有任何会话概念:没有模块持有令牌,`shared/api` 的 `registerApiAccessTokenProvider` 至今无人注册,因此所有需要鉴权的请求都会被拒。 PR #147 提供了一份完整的会话层实现,但作者没有时间继续推进,本 issue 接手该实现并补齐评审指出的缺口。 ### 目标 提供一个持有登录会话的模块,向上给界面暴露状态与操作,向下给 `shared/api` 提供 access token。本 issue 不含任何界面。 ### 范围 包含: - `entities/user`:用户实体与八个端点的接口契约,字段映射后端 `UserOut` 与 `TokenResponse` - `features/auth-session`:会话 Provider,三态为启动中、访客、已登录 - access token 只存内存,refresh token 持久化到 localStorage,存储不可用时降级为仅内存会话 - 启动时用持久化的 refresh token 恢复会话,失败转访客 - access token 过期前静默续期,并发续期请求去重 - 注册 access token provider,模块卸载时撤销 不包含: - 任何界面、路由与守卫的挂载(分别属于本模块之后的三条 issue) - 组合根中认证以外的依赖装配,仍归 #103 - `entities/project` 的任何改动。`ProjectPageQuery.ownerId` 这处遗留由 PR #142 一并清理,本 issue 不碰该模块 ### 实现约束 - 起点是 PR #147 的实现,可直接复用,PR 正文需注明来源与作者。 - 后端的 401 与 429 都以 HTTP 200 返回,业务码在响应体的 `code` 字段里,见 `middleware/auth.py` 的 `_biz_error`。判断登录失效必须依据 `ApiError.code === 401`,依据 HTTP 状态码会导致登录态永不过期。 - 必须提供对 `code=401` 的响应式处理,不能只依赖定时器。标签页被挂起时定时器不会准时触发,醒来后令牌已过期。收到 401 时先尝试续期并重放请求,续期失败再转访客。 - `/auth/refresh` 会轮换 refresh token 并返回新的一份。必须监听 `storage` 事件在标签页之间同步登录、登出与续期结果,否则两个标签页同时打开时会互相使对方的 refresh token 失效。 - `resolveAuthMode`、`AuthModeProvider`、`createLocalUserApis` 属于装配职责,边界归 #103。本模块保留本地开发适配器时,`AuthModeProvider` 这类只做转发的空壳要去掉,启用本地适配器时界面上需有明确标识,避免误认为登录已打通。 - 用户状态遇到 0 与 1 之外的取值时按正常处理并保留原值,不要让整个登录失败。 - 字段映射统一显式书写,不依赖驼峰与蛇形恰好同形。 ### 后端契约要点 - access token 有效期 15 分钟,refresh token 7 天。 - `/auth/logout` 在鉴权白名单内,请求体传 `refresh_token`。 - 登录返回的 `user` 与 `/auth/me` 返回的用户对象,JSON 键名与类型一致,可共用一套映射。 ### 验收标准 - 启动时若存在有效的 refresh token,恢复为已登录;无效或缺失则为访客。 - 任一请求返回 `code=401` 时触发续期并重放;续期失败后状态转访客且令牌被清除。 - 同一浏览器的两个标签页,其一登出后另一个同步转为访客;其一续期后另一个不会因 refresh token 失效而掉线。 - localStorage 不可用时仍可完成登录,仅退化为不跨刷新保持。 - 单元测试覆盖上述四条,以及并发续期只发一次请求。 ---
Activity
fix(auth-session): keep a refreshed session when the newer token fails
feat(auth): add the frontend auth session layer (#163)