You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

9.7 KiB

登录、注册与用户信息 — 设计规格

状态:已定稿(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;可更新字段如 nameage(与现有 users_table 对齐)。
  • 未验证账号:不阻止登录;对指定操作通过 集中配置 要求 email_verified_at;新增能力时优先改配置而非复制校验逻辑。
  • 忘记密码forgot-passwordreset-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 建议新增,便于审计

保留:idnameageemail(唯一)。

邮箱变更:第一版 禁止 PATCH 修改 email

2.2 auth_challenges

单表承载多种「挑战」:

说明
id 主键
user_id 外键 → users_table
type 枚举:email_verify | password_reset
token_hash 仅存储哈希,不存明文 token
expires_at 过期时间
consumed_at 可空;消费后置位
可选 created_atcreated_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_idprovider(如 github)、provider_user_id
  • 唯一约束:(provider, provider_user_id)

OAuth 绑定不写入 users 宽表。

2.5 迁移注意

  • 使用 Drizzle 生成并版本化迁移。
  • 示例接口 server/api/hello 若返回用户列表,须在实现阶段改为 鉴权后可用删除,禁止公开泄露。

  • HttpOnly:必选。
  • SecureNODE_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,例如:

{
  "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 抽象

  • sendVerificationEmailsendPasswordResetEmail(签名随实现细化)。
  • 第一版: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 须保证 携带 Cookiecredentials: '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_tabledrizzle-pkg 迁移流程延续;新表纳入同一包或约定目录。
  • 本文档为实现的 唯一需求来源之一;冲突以本文档与后续已批准的变更记录为准。

11. 修订记录

日期 说明
2026-04-12 初版定稿(brainstorming 各节确认合并)