1 changed files with 258 additions and 0 deletions
@ -0,0 +1,258 @@ |
|||
# 登录、注册与用户信息 — 设计规格 |
|||
|
|||
**状态**:已定稿(2026-04-12) |
|||
**范围**:第一版邮箱密码 + Redis Session;未验证邮箱分级门禁;忘记密码闭环(无生产邮件);最小 Nuxt 页面;为 OAuth 预留数据与流程边界。 |
|||
|
|||
--- |
|||
|
|||
## 1. 目标与非目标 |
|||
|
|||
### 1.1 目标(第一版交付) |
|||
|
|||
- **注册**:邮箱 + 密码;持久化密码哈希;可创建邮箱验证类 challenge(与是否发信解耦)。 |
|||
- **登录 / 登出**:校验密码后创建 **Redis Session**,通过 **HttpOnly Cookie** 下发 opaque session id;登出删除会话并清 Cookie;登录成功 **轮换 session id**。 |
|||
- **当前用户**:从 Cookie → Redis → `user_id` 解析会话上下文。 |
|||
- **用户信息**:`GET` / `PATCH` 个人资料;**第一版不允许修改 `email`**;可更新字段如 `name`、`age`(与现有 `users_table` 对齐)。 |
|||
- **未验证账号**:不阻止登录;对指定操作通过 **集中配置** 要求 `email_verified_at`;新增能力时优先改配置而非复制校验逻辑。 |
|||
- **忘记密码**:`forgot-password` 与 `reset-password` **API 闭环**;无生产邮件时 token 通过开发日志或运维/DB 获取;对外响应防枚举。 |
|||
- **OAuth**:不在第一版实现;数据与流程边界见第 8 节。 |
|||
|
|||
### 1.2 非目标(第一版不做) |
|||
|
|||
- 第三方 OAuth 回调与 UI。 |
|||
- 2FA、设备管理、复杂风控。 |
|||
- 生产级「发验证邮件 / 发重置邮件」投递(通过 `Mailer` 抽象预留,第一版 `NoopMailer`)。 |
|||
- 完整运营后台;管理员手工重置仅作为运维流程文档化。 |
|||
|
|||
--- |
|||
|
|||
## 2. 数据模型与迁移 |
|||
|
|||
### 2.1 `users_table` 扩展 |
|||
|
|||
在现有表上 **增量迁移**(保持表名 `users_table` 与 Drizzle 映射,减少改名风险): |
|||
|
|||
| 列 | 说明 | |
|||
|----|------| |
|||
| `password_hash` | 非空(已有行需迁移策略:开发环境可清空后重建;生产需单独评估) | |
|||
| `email_verified_at` | `timestamptz`,可空 | |
|||
| `created_at` / `updated_at` | 建议新增,便于审计 | |
|||
|
|||
保留:`id`、`name`、`age`、`email`(唯一)。 |
|||
|
|||
**邮箱变更**:第一版 **禁止** PATCH 修改 `email`。 |
|||
|
|||
### 2.2 `auth_challenges` |
|||
|
|||
单表承载多种「挑战」: |
|||
|
|||
| 列 | 说明 | |
|||
|----|------| |
|||
| `id` | 主键 | |
|||
| `user_id` | 外键 → `users_table` | |
|||
| `type` | 枚举:`email_verify` \| `password_reset` | |
|||
| `token_hash` | 仅存储哈希,不存明文 token | |
|||
| `expires_at` | 过期时间 | |
|||
| `consumed_at` | 可空;消费后置位 | |
|||
| 可选 | `created_at`、`created_ip` | |
|||
|
|||
索引:按 `token_hash` 查询(实现时注意与过期清理策略配合)。 |
|||
|
|||
### 2.3 Session |
|||
|
|||
- **仅存 Redis**,不建 Postgres session 表。 |
|||
- Key 建议:`sess:{sessionId}`;Value JSON 至少含 `userId`,可选 `createdAt` 等。 |
|||
- TTL 与 Cookie `Max-Age` **一致**;续期策略在实现计划中写死默认(如固定过期或滑动窗口)。 |
|||
|
|||
### 2.4 `linked_accounts`(OAuth 预留) |
|||
|
|||
第二版可落库,第一版可在迁移中 **创建空表** 或 **仅文档约定**(二选一在实现计划中固定): |
|||
|
|||
- `user_id`、`provider`(如 `github`)、`provider_user_id` |
|||
- 唯一约束:`(provider, provider_user_id)` |
|||
|
|||
OAuth 绑定不写入 `users` 宽表。 |
|||
|
|||
### 2.5 迁移注意 |
|||
|
|||
- 使用 Drizzle 生成并版本化迁移。 |
|||
- 示例接口 `server/api/hello` 若返回用户列表,须在实现阶段改为 **鉴权后可用** 或 **删除**,禁止公开泄露。 |
|||
|
|||
--- |
|||
|
|||
## 3. Cookie、Redis 与安全 |
|||
|
|||
### 3.1 Cookie |
|||
|
|||
- **HttpOnly**:必选。 |
|||
- **Secure**:`NODE_ENV=production` 且 HTTPS 时必选;本地 HTTP 可关闭。 |
|||
- **SameSite**:默认 **`Lax`**。 |
|||
- **Path**:`/`;`Domain` 由部署环境可选配置。 |
|||
- Cookie 名:实现中用常量(如 `SESSION_COOKIE_NAME`),全仓统一。 |
|||
|
|||
### 3.2 Redis |
|||
|
|||
- 环境变量 **`REDIS_URL`**。 |
|||
- Nitro 进程内 **单例连接**(插件或惰性初始化),禁止每请求新建连接。 |
|||
|
|||
### 3.3 密码与 token |
|||
|
|||
- 密码哈希:**argon2id 或 bcrypt 二选一**,全仓单一封装;实现计划选定并锁定依赖。 |
|||
- 挑战 token:生成随机明文 → 仅存 **`token_hash`**;校验时使用恒定时间比较。 |
|||
|
|||
### 3.4 滥用防护 |
|||
|
|||
- 对登录、注册等接口做 **限流**(建议 Redis 计数器 + 时间窗口)。 |
|||
- `forgot-password`:**始终 HTTP 200** + 统一文案,不泄露邮箱是否注册。 |
|||
|
|||
### 3.5 CSRF |
|||
|
|||
- 第一版假设 **同站** Nuxt 调用 API,`SameSite=Lax`。 |
|||
- 若未来开放跨站写操作,须引入 **CSRF token** 或等价机制(单独规格)。 |
|||
|
|||
--- |
|||
|
|||
## 4. API 约定 |
|||
|
|||
### 4.1 路由一览 |
|||
|
|||
| 方法与路径 | 行为 | |
|||
|------------|------| |
|||
| `POST /api/auth/register` | 注册;**建议成功后直接建立会话**(与登录一致) | |
|||
| `POST /api/auth/login` | 登录;轮换 session;写 Cookie | |
|||
| `POST /api/auth/logout` | 登出;幂等 | |
|||
| `POST /api/auth/forgot-password` | body:`{ email }`;统一 200 响应 | |
|||
| `POST /api/auth/reset-password` | body:`{ token, new_password }`;成功后消费 challenge、更新 `password_hash`、**吊销该用户所有 Redis session** | |
|||
| `POST /api/auth/verify-email` | body:`{ token }`;成功则写 `email_verified_at`、消费 challenge | |
|||
| `GET /api/me` | 当前用户摘要;未登录 **401** | |
|||
| `PATCH /api/me` | 更新资料;未登录 **401**;若配置要求已验证而未满足 **403** | |
|||
|
|||
全仓路径命名保持一致;若调整仅允许整仓重命名并更新本文档。 |
|||
|
|||
### 4.2 错误响应 |
|||
|
|||
统一 JSON,例如: |
|||
|
|||
```json |
|||
{ |
|||
"error": { |
|||
"code": "EMAIL_IN_USE", |
|||
"message": "该邮箱已注册" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
建议状态码: |
|||
|
|||
- **400**:校验失败 |
|||
- **401**:未登录或 session 无效 |
|||
- **403**:已登录但不满足策略(如未验证邮箱) |
|||
- **409**:资源冲突(邮箱已存在) |
|||
- **429**:限流 |
|||
- **500**:不暴露内部细节;服务端结构化日志记录 |
|||
|
|||
响应体 **不得** 包含 `password_hash` 或明文 session id。 |
|||
|
|||
--- |
|||
|
|||
## 5. 「未验证」可配置门禁 |
|||
|
|||
### 5.1 原则 |
|||
|
|||
- **`requireUser`**:解析会话,无则 401。 |
|||
- **`requireVerifiedFor(handlerId)`** 或等价:根据 **集中配置** 判断是否要求 `email_verified_at` 非空。 |
|||
- **默认策略**:未在配置中声明的操作 **仅需登录**(避免默认过严导致全站不可用);敏感写操作 **显式** 声明需验证。 |
|||
|
|||
### 5.2 配置形态 |
|||
|
|||
- 使用「路由/能力 id → 是否需要已验证」的映射(对象、模块导出或 JSON)。 |
|||
- 第一版至少示例:**`PATCH /api/me` 需已验证**(可配置关闭以联调);**`GET /api/me` 仅需登录**。 |
|||
|
|||
### 5.3 前端 |
|||
|
|||
- **401**:跳转 `/login`,可带 `redirect`。 |
|||
- **403**(未验证):在 `/me` 使用提示条或轻量引导;不强制整页拦截只读,除非产品后续变更。 |
|||
|
|||
--- |
|||
|
|||
## 6. 邮件与占位行为 |
|||
|
|||
### 6.1 `Mailer` 抽象 |
|||
|
|||
- `sendVerificationEmail`、`sendPasswordResetEmail`(签名随实现细化)。 |
|||
- 第一版:**`NoopMailer`**,不发起网络投递。 |
|||
|
|||
### 6.2 开发调试 |
|||
|
|||
- 环境变量 **`AUTH_DEBUG_LOG_TOKENS=true`** 时,将验证/重置用 **明文 token 写入结构化日志一次**;**生产默认关闭**。 |
|||
|
|||
### 6.3 无邮件生产 |
|||
|
|||
- `reset-password` / `verify-email` 仍可用;token 获取走 **运维/DB 应急流程**(文档化,非产品功能)。 |
|||
|
|||
--- |
|||
|
|||
## 7. 最小 Nuxt 前端 |
|||
|
|||
- 页面:`/login`、`/register`、`/me`(资料 + 登出);登出可在 `/me` 上以按钮完成。 |
|||
- 路由中间件:`auth`(保护 `/me`);可选 `guest`(已登录访问登录/注册页时重定向 `/me`)。 |
|||
- 数据请求:浏览器与 SSR 须保证 **携带 Cookie**(`credentials: 'include'` 或同源默认行为以实现时验证为准)。 |
|||
- 客户端可做基础格式校验;**服务端校验为权威**。 |
|||
|
|||
--- |
|||
|
|||
## 8. OAuth 第二阶段(边界) |
|||
|
|||
- 登录成功仍使用 **同一套 Redis Session + Cookie**。 |
|||
- 用户主体在 **`users_table`**;外部身份在 **`linked_accounts`**。 |
|||
- **禁止** OAuth 邮箱与已有账号 **静默合并**;须显式「连接账号」或拒绝策略(实现计划细化)。 |
|||
- 回调路由占位:`/api/auth/oauth/{provider}/start`、`/api/auth/oauth/{provider}/callback`(路径以实现计划为准)。 |
|||
- `email_verified_at` 是否因 OAuth 声明而自动写入:第二阶段按 provider 策略在实现计划中规定。 |
|||
|
|||
--- |
|||
|
|||
## 9. 测试、环境变量与上线检查 |
|||
|
|||
### 9.1 测试 |
|||
|
|||
- **单元**:哈希、token 校验、门禁配置解析。 |
|||
- **集成**:Postgres + Redis;覆盖注册→登录→me、未验证 403、验证后通过、登出 401、重置密码吊销会话等(具体用例实现计划列出)。 |
|||
- 可复用/扩展 `scripts/migrate-test.sh` 等现有脚本启动测试库。 |
|||
|
|||
### 9.2 环境变量(最小集) |
|||
|
|||
| 变量 | 说明 | |
|||
|------|------| |
|||
| `DATABASE_URL` | 已有 | |
|||
| `REDIS_URL` | 新增 | |
|||
| `NODE_ENV` | `production` 行为见上文 | |
|||
| `AUTH_DEBUG_LOG_TOKENS` | 可选;开发用 | |
|||
| `SESSION_TTL_SECONDS` | 可选;缺省用代码默认 | |
|||
|
|||
部署可选:`COOKIE_DOMAIN`、限流相关前缀变量(实现计划定义)。 |
|||
|
|||
### 9.3 上线检查单 |
|||
|
|||
- 迁移已执行;Redis 可用;HTTPS + `Secure` Cookie。 |
|||
- 关闭 `AUTH_DEBUG_LOG_TOKENS`;限流与公开 API 审查完成。 |
|||
- 日志不含密码与生产 session 明文。 |
|||
|
|||
### 9.4 日志 |
|||
|
|||
- 复用项目 logger;认证失败、限流、Redis 错误结构化记录。 |
|||
|
|||
--- |
|||
|
|||
## 10. 与现有代码库的关系 |
|||
|
|||
- 栈:**Nuxt 4、Nitro、Bun、Drizzle、Postgres**;Session **Redis**。 |
|||
- 现有 `users_table` 与 `drizzle-pkg` 迁移流程延续;新表纳入同一包或约定目录。 |
|||
- 本文档为实现的 **唯一需求来源**之一;冲突以本文档与后续已批准的变更记录为准。 |
|||
|
|||
--- |
|||
|
|||
## 11. 修订记录 |
|||
|
|||
| 日期 | 说明 | |
|||
|------|------| |
|||
| 2026-04-12 | 初版定稿(brainstorming 各节确认合并) | |
|||
Loading…
Reference in new issue