docs(webui): remember-me (30-day session) design spec
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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 天内活跃自动续期"。
|
||||
Reference in New Issue
Block a user