Files
social-app/docs/plans/2026-02-26-auth-ux-enhancement-design.md
T
qzl 76853452f6 chore: commit remaining workspace updates
include AGENTS guidance updates, plan doc replacements, and utility script changes left in working tree
2026-02-26 17:59:30 +08:00

5.6 KiB
Raw Blame History

Auth UX Enhancement Design

日期: 2026-02-26 状态: 可实施(修订版)

目标

本次改动聚焦 4 个问题:

  1. 注册验证码页增加首次提示,降低用户困惑。
  2. 增加忘记密码流程(验证码模式)。
  3. 注册页增加邀请码输入(前端收集,后端暂不消费)。
  4. 修复用户名非唯一导致的用户查询问题,改为搜索接口。

非目标

  • 不实现邀请码校验/入库。
  • 不改动 Supabase 邮件模板基础设施(当前 self-hosted 已配置 recovery 模板 URL)。
  • 不在本次引入新的认证机制(仅沿用 Supabase OTP + session)。

1. 忘记密码的可落地后端方案(对外两步)

1.1 约束说明(关键)

当前 Python SDK 的 verify_otp 参数模型不支持在验码时直接携带 new_password。因此不能走“单接口验码并改密”的实现。

1.2 可执行流程

对客户端暴露两步流程,第二步在后端内部执行两段动作:

  1. POST /auth/password-reset:调用 Supabase reset_password_email 发送 recovery 验证码。
  2. POST /auth/password-reset/confirm:接收 email + token + new_password,后端内部先调用 verify_otp(type="recovery"),再基于该会话调用 update_user(password=...)

这样既匹配 SDK 能力(verify_otp 不支持直接带 new_password),又保持前端体验为两步。

1.3 API 设计

POST /auth/password-reset

发送重置验证码。

Request

{
  "email": "string(email)",
  "redirect_to": "string(optional)"
}

Response: 204 No Content

Errors:

  • 422 参数错误
  • 429 频率受限

POST /auth/password-reset/confirm

验证 recovery 验证码并完成改密。

Request

{
  "email": "string(email)",
  "token": "string(6 digits)",
  "new_password": "string(min 6)"
}

Response: 204 No Content

Errors:

  • 401 验证码无效或过期
  • 422 参数错误
  • 429 频率受限

1.4 安全边界

  • 用户档案更新走 users 域(/users/me -> UserService -> Profile),仅允许公开资料字段。
  • 密码修改走 auth 域(Supabase Auth),不复用 users service/repository。
  • POST /auth/password-reset/confirm 必须在同一请求内完成“验码 + 改密”,禁止单独暴露“仅改密”接口。
  • 即使伪造 /users/me 请求,也无法触发密码修改路径。

2. 前端忘记密码流程

流程:

登录页 -> 忘记密码页(输入邮箱) -> 验证码+新密码页 -> 返回登录并用新密码登录

关键点:

  • 第二步页面一次提交 email + token + new_password/auth/password-reset/confirm
  • 所有用户反馈统一使用 Toast(遵循 apps/AGENTS.md)。
  • 错误提示优先展示后端 detail

3. 注册 UX 优化

3.1 验证码发送提示

register_verification_screen.dart 首次进入页面显示:

验证码已发送,如未收到请检查垃圾邮件或确认邮箱已注册

3.2 邀请码输入

在注册页新增可选字段:

  • Label: 邀请码(选填)
  • Hint: 请输入邀请码

前端请求体可携带 invite_code,后端忽略该字段,不返回错误。


4. 用户搜索 Bug 修复

4.1 问题

GET /users/{username} 隐含“用户名唯一”假设,实际不成立。

4.2 方案

后端删除 GET /users/{username},改为 POST /users/search

Request

{
  "query": "string(1-100)"
}

Response

[
  {
    "id": "string",
    "username": "string",
    "avatar_url": "string|null",
    "bio": "string|null"
  }
]

查询策略:

  • username: 模糊匹配(ilike
  • email: 精确匹配
  • 最多返回 20 条
  • 返回公开字段,不返回 email

4.3 前端联动

必须同步迁移 apps/lib/features/users/data/*getByUsername -> searchUsers),否则删除后端旧路由后前端会直接 404。


5. 主要改动文件

后端

  • backend/src/v1/auth/schemas.py
  • backend/src/v1/auth/service.py
  • backend/src/v1/auth/gateway.py
  • backend/src/v1/auth/router.py
  • backend/src/v1/users/schemas.py
  • backend/src/v1/users/repository.py
  • backend/src/v1/users/service.py
  • backend/src/v1/users/router.py
  • backend/tests/integration/test_auth_routes.py
  • backend/tests/integration/test_users_routes.py

前端

  • apps/lib/features/auth/ui/screens/login_screen.dart
  • apps/lib/features/auth/ui/screens/register_screen.dart
  • apps/lib/features/auth/ui/screens/register_verification_screen.dart
  • apps/lib/features/auth/ui/screens/forgot_password_screen.dart(新增)
  • apps/lib/features/auth/ui/screens/reset_password_screen.dart(新增)
  • apps/lib/features/auth/presentation/cubits/forgot_password_cubit.dart(新增)
  • apps/lib/features/auth/presentation/cubits/reset_password_cubit.dart(新增)
  • apps/lib/features/auth/data/auth_api.dart
  • apps/lib/features/auth/data/auth_repository.dart
  • apps/lib/features/auth/data/auth_repository_impl.dart
  • apps/lib/features/users/data/users_api.dart
  • apps/lib/features/users/data/users_repository.dart
  • apps/lib/features/users/data/users_repository_impl.dart
  • apps/lib/core/router/app_router.dart

文档

  • docs/runtime/runtime-route.md(按 AGENTS 规则必须同步)

6. 验收标准

  • 注册验证码页首次进入显示提示。
  • 登录页出现“忘记密码”入口。
  • 忘记密码流程可完整走通(发码、确认改密、重新登录)。
  • 注册页可输入邀请码且不影响注册。
  • GET /users/{username} 被移除。
  • POST /users/search 可用且返回不含 email。
  • 后端与前端相关测试通过,文档已同步。