会话与访问控制
单一会话来源
会话查询在 apps/app/lib/queries/session.ts。页面组件使用 useSessionQuery(),路由守卫使用 sessionQueryOptions(),不要再用独立的本地存储或另一份认证会话状态。
查询结果同时含 user 和 session 才算有效。未登录返回空值,不应当作服务器故障;网络失败则不能直接当作未登录。
私有路由
apps/app/routes/(app)/route.tsx 在进入页面前读取会话,未登录跳转 /login。登录回跳地址先通过安全校验,仅允许站内地址。
这是界面访问控制。真正的安全边界在服务端:私有 tRPC 操作使用 protectedProcedure,组织、用户数据与账单再校验归属和角色。
登录与退出
登录、注册或活动组织改变后,调用 revalidateSession() 删除旧查询并让路由重新检查。退出时先请求服务端结束会话;请求失败显示错误,不能只把界面改成退出。
服务端退出成功后清空会话查询并整页跳转 /login,同时丢弃内存中的用户数据和界面状态,避免下一个账号看到旧缓存。
Cookie 与域名
APP_ORIGIN 应和访问网站的协议、域名、端口完全一致。生产使用 HTTPS;本地使用统一入口 http://localhost:4400。改变站点 ID 会改变认证 cookie 前缀,需要重新登录。
认证提示 cookie 只是历史路由提示,不证明会话有效;当前首页始终公开,不能据此判断登录状态。
排查清单
- 登录成功后检查
/api/auth/get-session是否返回用户与会话。 - 检查请求是否带上对应域名 cookie,是否混用 localhost 与 IP 地址。
- 检查会话查询是否在认证成功后刷新,网络错误是否被错误地当作未登录。
- 退出后直接请求私有接口应被拒绝。
- 活动组织成员被移除后,旧会话不能继续读取组织账单。
会话需要实时数据库连接,不能使用 Hyperdrive 的旧查询结果作为授权依据。