Files
EvoScientist-WebUI/docs/superpowers/specs/2026-08-07-remember-me-design.md
T
2026-08-07 17:08:01 +08:00

3.7 KiB

记住我(30 天会话)设计

日期:2026-08-07 状态:已批准

背景

WebUI 登录当前使用无状态 HMAC 签名会话 Cookie(evoscientist_webui_session),有效期由 WEBUI_AUTH_SESSION_TTL_HOURS 控制(默认 12 小时)。用户希望登录页提供"记住我"选项,勾选后登录状态保持 30 天。

需求

  • 登录页新增"Remember me for 30 days"复选框,默认不勾选。
  • 勾选:会话有效 30 天,关闭浏览器再打开无需重新登录。
  • 未勾选:会话级 Cookie(浏览器关闭即失效),服务端 expiresAt 保留 12 小时兜底。
  • 认证未启用(WEBUI_AUTH_ENABLED != "true")时行为完全不变。
  • 旧会话 Cookie 无需迁移,自然过期。

方案

扩展现有签名会话 Cookie,按勾选状态使用不同 TTL。不引入新存储、不新增 Cookie。

1. 配置层 — src/lib/auth.ts

新增:

const DEFAULT_REMEMBER_TTL_DAYS = 30;

export function rememberTtlSeconds(): number {
  const days = Number(process.env.WEBUI_AUTH_REMEMBER_TTL_DAYS);
  if (!Number.isFinite(days) || days <= 0 || days > 90) {
    return DEFAULT_REMEMBER_TTL_DAYS * 24 * 60 * 60;
  }
  return Math.floor(days * 24 * 60 * 60);
}

与现有 sessionTtlSeconds() 模式一致:环境变量 WEBUI_AUTH_REMEMBER_TTL_DAYS,默认 30,上限 90,非法值回退默认。

2. 会话签发 — src/lib/server/auth.ts

  • createSession(username, role, ttlSeconds: number):增加 TTL 参数,用于计算 payload 的 expiresAt(由调用方传入 rememberTtlSeconds() 或 sessionTtlSeconds())。
  • authCookieOptions(ttlSeconds: number | null):参数为 null 时不设置 maxAge(会话级 Cookie);否则 maxAge 取该值。其余选项(httpOnly、sameSite=lax、secure、path)不变。
  • SessionPayload 结构不变;decodeSessionPayload 不变。
  • proxy.ts 中间件校验逻辑不变(已基于 payload 的 expiresAt 判断,与 Cookie maxAge 独立)。

3. 登录路由 — src/app/api/auth/login/route.ts

  • 请求体新增可选字段 rememberMe,仅严格 true 视为勾选,其余(缺失、false、非布尔)视为未勾选。
  • 勾选:createSession(user.username, user.role, rememberTtlSeconds()) + authCookieOptions(rememberTtlSeconds())。
  • 未勾选:createSession(user.username, user.role, sessionTtlSeconds()) + authCookieOptions(null)(会话级 Cookie,expiresAt 为 12 小时兜底)。
  • 其余校验(origin、用户名/密码长度、错误响应)不变。

4. 登录页 — src/app/login/page.tsx

  • 密码框下方新增复选框,文案固定为 "Remember me for 30 days"(写死,不从配置读取)。
  • 状态 rememberMe,默认 false,提交时加入请求体。
  • 复选框在 submitting 时禁用。
  • 样式沿用现有 Tailwind 类,与表单其余部分一致。

5. 错误处理与边界

  • 认证未启用:登录路由已返回 404,不涉及本功能。
  • 勾选但环境变量非法:回退 30 天,不报错。
  • 登出逻辑不变(清除 Cookie)。
  • 修改密码轮换密钥后所有会话(含 30 天会话)失效,行为与现状一致。

测试

  1. src/lib/auth 单测(新增或扩展):rememberTtlSeconds() 的默认值、合法值、非法值(非数字、负数)、超上限(>90 天)回退。
  2. 登录路由测试:
    • rememberMe: true → Set-Cookie 含 Max-Age ≈ 30 天,payload expiresAt ≈ 30 天后。
    • rememberMe: false 或缺失 → Set-Cookie 不含 Max-Age,payload expiresAt ≈ 12 小时后。
    • rememberMe 为非布尔值 → 按未勾选处理。
  3. 手动验证:勾选登录后重启浏览器仍保持登录;未勾选关闭浏览器后需重新登录。

非目标(YAGNI)

  • 不做 remember token 轮换 / 服务端可撤销的 refresh token 存储。
  • 不做设备管理、踢出会话界面。
  • 不做"30 天内活跃自动续期"。