chore: commit remaining workspace updates

include AGENTS guidance updates, plan doc replacements, and utility script changes left in working tree
This commit is contained in:
qzl
2026-02-26 17:59:30 +08:00
parent f3d08a7fcf
commit 76853452f6
9 changed files with 996 additions and 2423 deletions
@@ -0,0 +1,204 @@
# 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
```json
{
"email": "string(email)",
"redirect_to": "string(optional)"
}
```
Response: `204 No Content`
Errors:
- `422` 参数错误
- `429` 频率受限
#### POST /auth/password-reset/confirm
验证 recovery 验证码并完成改密。
Request
```json
{
"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
```json
{
"query": "string(1-100)"
}
```
Response
```json
[
{
"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。
- [ ] 后端与前端相关测试通过,文档已同步。