docs(webui): remember-me (30-day session) design spec

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
m4
2026-08-07 17:08:01 +08:00
parent 6d2b2f4d80
commit 9c25b3a9e5
@@ -0,0 +1,81 @@
# 记住我(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`
新增:
```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 天内活跃自动续期"。