Skip to content

认证端点 API

包含两类端点(频道权限按页面入口最低门槛取 public,便于未登录用户查看 OTP 接口; 具体子端点权限以下面标注为准):

  • 公开:OTP 验证码代理转发(带频率限制和审计记录)
  • 登录用户:注册兜底建团(issue #523)

基础路径

/auth

认证方式

  • /auth/send-otp公开,无需 JWT 认证,由后端统一做频率限制后代理转发到 Supabase Auth
  • /auth/initialize登录用户,需 Bearer JWT;单独挂 jwtAuth 中间件,不影响 send-otp 公开访问

限流说明

  • 全局限流:继承全局 CORS 中间件
  • Auth 专用限流:额外的频率限制(由 site_settings 动态配置)
  • IP 维度限流:数据库级每小时限制
  • 邮箱维度限流:原子化 advisory lock 限流(消除 TOCTOU 竞态)

端点列表

POST /auth/send-otp

发送 OTP 验证码邮件。支持首次发送和重发(通过 resend_token)。

请求体

参数类型必填说明
emailstring目标邮箱地址
resend_tokenstring上次返回的一次性重发令牌,用于绕过冷却期重发
langstring语言偏好(如 zh-CN / en-US),透传到 Supabase Auth user metadata,供 DB handle_new_user trigger 自动建首个团队时选择团队名(zh* → "我的团队",其余 → "My Team")

成功响应 200

json
{
  "success": true,
  "message": "验证码已发送",
  "resend_token": "uuid-token",
  "token_expires_at": "2026-01-01T00:05:00Z",
  "retry_after": 60,
  "next_allowed_at": "2026-01-01T00:01:00Z"
}
字段类型说明
resend_tokenstring新的一次性重发令牌(用于下次重发)
token_expires_atstring令牌过期时间(ISO 8601)
retry_afternumber距下次可发送的秒数
next_allowed_atstring下次可发送的 ISO 时间

错误响应

状态码error 值说明
400invalid_body请求体格式无效
400invalid_email邮箱格式无效
400token_email_mismatch重发令牌与邮箱不匹配
429rate_limited请求过于频繁,返回 retry_afternext_allowed_at
500internal_error服务暂时不可用
500send_failed验证码发送失败

限流响应 429

json
{
  "success": false,
  "error": "rate_limited",
  "message": "请求过于频繁,请稍后再试",
  "retry_after": 60,
  "next_allowed_at": "2026-01-01T00:01:00Z"
}

POST /auth/initialize

权限:登录用户(需 Bearer JWT)

兜底接口:确保当前登录用户拥有一个 active team。通常情况下注册时 DB handle_new_user trigger 已自动建好首个团队,本接口仅在以下场景生效:

  • DB trigger 创建失败(异常被 trigger 内部 WARN 吞掉,profile 已落地但 team 缺失)
  • 老用户首次登录(trigger 上线前注册的账号)
  • 团队被误删后用户访问 dashboard

调用 DB helper public.create_default_team_for_user(p_user_id, p_lang)。函数本身幂等:

  • 用户已有 active team 直接返回
  • 用户是某 team 的 owner → UPSERT 自己的 admin/accepted membership
  • 用户仅是某 team 的 active member(不是 owner) → 仅返回 team_id,不改 membership role(防止误提升)

请求体(可选)

json
{ "lang": "zh-CN" }
字段类型必填说明
langstring语言偏好,优先级最高

语言来源链(自上而下,首个非空生效):

  1. body.lang
  2. Accept-Language header 首段
  3. serverMode 兜底(selfhosted → zh-CN,cloud → en-US

成功响应 200

json
{
  "success": true,
  "data": {
    "team_id": "uuid",
    "slug": "team-a1b2c3d4e5",
    "name": "我的团队"
  }
}

错误响应

状态码error说明
401unauthorized缺少或无效的 Authorization header
500internal_errorRPC 调用失败或 team 查询失败

幂等性:连续调用对同一用户返回同一个 team_id;DB 层使用 pg_advisory_xact_lock 串行化同一用户的并发调用,避免双插。


安全机制

  1. IP 获取:优先使用 x-real-ip(反向代理设置),fallback 到 x-forwarded-for 首个 IP
  2. 日志脱敏:邮箱和 IP 在日志中自动脱敏(SHA-256 hash)
  3. 多层限流:内存限流中间件 + IP 数据库限流 + 邮箱原子限流
  4. 发送失败回滚:Supabase OTP 发送失败时自动回滚占位记录,避免冷却期误拦截
  5. 请求体校验:所有端点拒绝/归一化非对象 body(防止 JSON null 触发 TypeError)
  6. /auth/initialize 权限边界:DB helper SECURITY DEFINER + 仅 service_role GRANT,不暴露给前端直调;前端必须通过本路由的 jwtAuth + 后端 service-role 通道

AI Workflow Editor