403 lines
12 KiB
Markdown
403 lines
12 KiB
Markdown
|
|
# Auth UX Enhancement Implementation Plan
|
|||
|
|
|
|||
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|||
|
|
|
|||
|
|
**Goal:** 以最小风险完成 Auth UX 优化(忘记密码、注册提示、邀请码、用户搜索修复),并确保与现有 Supabase SDK 能力完全一致。
|
|||
|
|
|
|||
|
|
**Architecture:** 对客户端暴露两步重置流程:`password-reset` 发码 + `password-reset/confirm` 确认改密。`confirm` 在后端内部串行执行 `verify_otp(type="recovery")` 与 `update_user(password=...)`,既符合 SDK 限制又保持交互简洁。用户查询从 `GET /users/{username}` 迁移到 `POST /users/search`,并同步前端数据层,避免断链。全流程按 TDD 执行并同步路由文档。
|
|||
|
|
|
|||
|
|
**Tech Stack:** FastAPI, Pydantic, SQLAlchemy, Supabase Auth, Flutter, Dio, Bloc/Cubit
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Phase 0: 基线与保护
|
|||
|
|
|
|||
|
|
### Task 1: 建立基线测试(RED 前准备)
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `backend/tests/integration/test_auth_routes.py`
|
|||
|
|
- Modify: `backend/tests/integration/test_users_routes.py`
|
|||
|
|
|
|||
|
|
**Step 1: 新增失败测试占位(不改实现)**
|
|||
|
|
|
|||
|
|
新增并期望失败的测试:
|
|||
|
|
- `test_password_reset_request_returns_204`
|
|||
|
|
- `test_password_reset_confirm_returns_204`
|
|||
|
|
- `test_search_users_returns_list`
|
|||
|
|
|
|||
|
|
**Step 2: 运行后端测试确认 RED**
|
|||
|
|
|
|||
|
|
Run: `uv run pytest backend/tests/integration/test_auth_routes.py backend/tests/integration/test_users_routes.py -v`
|
|||
|
|
|
|||
|
|
Expected: 新增用例失败(404/AttributeError/未实现)。
|
|||
|
|
|
|||
|
|
**Step 3: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add backend/tests/integration/test_auth_routes.py backend/tests/integration/test_users_routes.py
|
|||
|
|
git commit -m "test: add failing tests for auth ux enhancement"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Phase 1: 后端密码重置(对外两步)
|
|||
|
|
|
|||
|
|
### Task 2: 完成密码重置请求接口(发码)
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `backend/src/v1/auth/schemas.py`
|
|||
|
|
- Modify: `backend/src/v1/auth/service.py`
|
|||
|
|
- Modify: `backend/src/v1/auth/gateway.py`
|
|||
|
|
- Modify: `backend/src/v1/auth/router.py`
|
|||
|
|
- Test: `backend/tests/integration/test_auth_routes.py`
|
|||
|
|
|
|||
|
|
**Step 1: 写失败测试(若 Task 1 未覆盖细节)**
|
|||
|
|
|
|||
|
|
确保 `POST /api/v1/auth/password-reset`:
|
|||
|
|
- 合法邮箱返回 `204`
|
|||
|
|
- 非法参数返回 `422`
|
|||
|
|
- 触发限流返回 `429`
|
|||
|
|
|
|||
|
|
**Step 2: 跑单测确认失败**
|
|||
|
|
|
|||
|
|
Run: `uv run pytest backend/tests/integration/test_auth_routes.py::test_password_reset_request_returns_204 -v`
|
|||
|
|
|
|||
|
|
Expected: FAIL。
|
|||
|
|
|
|||
|
|
**Step 3: 最小实现(GREEN)**
|
|||
|
|
|
|||
|
|
- `schemas.py`: 复用已有 `PasswordResetRequest`,不要重复定义同名模型。
|
|||
|
|
- `service.py`: 增加 `request_password_reset(...)`。
|
|||
|
|
- `gateway.py`: 调用 `self._client.auth.reset_password_email(email, options)`。
|
|||
|
|
- `router.py`: 新增 `POST /auth/password-reset`,返回 `204`,保留限流。
|
|||
|
|
|
|||
|
|
**Step 4: 跑测试确认通过**
|
|||
|
|
|
|||
|
|
Run: `uv run pytest backend/tests/integration/test_auth_routes.py::test_password_reset_request_returns_204 -v`
|
|||
|
|
|
|||
|
|
Expected: PASS。
|
|||
|
|
|
|||
|
|
**Step 5: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add 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/tests/integration/test_auth_routes.py
|
|||
|
|
git commit -m "feat(auth): add password reset request endpoint"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Task 3: 完成密码重置确认接口(验码 + 改密)
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `backend/src/v1/auth/schemas.py`
|
|||
|
|
- Modify: `backend/src/v1/auth/service.py`
|
|||
|
|
- Modify: `backend/src/v1/auth/gateway.py`
|
|||
|
|
- Modify: `backend/src/v1/auth/router.py`
|
|||
|
|
- Test: `backend/tests/integration/test_auth_routes.py`
|
|||
|
|
|
|||
|
|
**Step 1: 写失败测试**
|
|||
|
|
|
|||
|
|
测试 `POST /api/v1/auth/password-reset/confirm`:
|
|||
|
|
- 正确 `email + token + new_password` 返回 `204`
|
|||
|
|
- 错误验证码返回 `401`
|
|||
|
|
- 弱密码/参数错误返回 `422`
|
|||
|
|
|
|||
|
|
**Step 2: 跑测试确认失败**
|
|||
|
|
|
|||
|
|
Run: `uv run pytest backend/tests/integration/test_auth_routes.py::test_password_reset_confirm_returns_204 -v`
|
|||
|
|
|
|||
|
|
Expected: FAIL。
|
|||
|
|
|
|||
|
|
**Step 3: 最小实现**
|
|||
|
|
|
|||
|
|
- `schemas.py`: 新增 `PasswordResetConfirmRequest(email, token, new_password)`。
|
|||
|
|
- `service.py`: 增加 `confirm_password_reset(...)`。
|
|||
|
|
- `gateway.py`: 在单个方法内先调用 `verify_otp({"type":"recovery", ...})`,再在该会话上下文调用 `update_user({"password": new_password})`。
|
|||
|
|
- `router.py`: 新增 `POST /auth/password-reset/confirm`,返回 `204`。
|
|||
|
|
|
|||
|
|
**Step 4: 跑测试确认通过**
|
|||
|
|
|
|||
|
|
Run: `uv run pytest backend/tests/integration/test_auth_routes.py::test_password_reset_confirm_returns_204 -v`
|
|||
|
|
|
|||
|
|
Expected: PASS。
|
|||
|
|
|
|||
|
|
**Step 5: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add 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/tests/integration/test_auth_routes.py
|
|||
|
|
git commit -m "feat(auth): add password reset confirm endpoint"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Phase 2: 后端用户搜索替代用户名路由
|
|||
|
|
|
|||
|
|
### Task 4: 新增 POST /users/search 并移除旧路由
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `backend/src/v1/users/schemas.py`
|
|||
|
|
- Modify: `backend/src/v1/users/repository.py`
|
|||
|
|
- Modify: `backend/src/v1/users/service.py`
|
|||
|
|
- Modify: `backend/src/v1/users/router.py`
|
|||
|
|
- Test: `backend/tests/integration/test_users_routes.py`
|
|||
|
|
|
|||
|
|
**Step 1: 写失败测试**
|
|||
|
|
|
|||
|
|
新增测试:
|
|||
|
|
- `POST /api/v1/users/search` 成功返回列表
|
|||
|
|
- `query` 为空返回 `422`
|
|||
|
|
- 删除后 `GET /api/v1/users/{username}` 返回 `404`
|
|||
|
|
|
|||
|
|
**Step 2: 跑测试确认失败**
|
|||
|
|
|
|||
|
|
Run: `uv run pytest backend/tests/integration/test_users_routes.py -v`
|
|||
|
|
|
|||
|
|
Expected: FAIL。
|
|||
|
|
|
|||
|
|
**Step 3: 最小实现**
|
|||
|
|
|
|||
|
|
- `schemas.py`: 增加 `UserSearchRequest`, `UserSearchResult`。
|
|||
|
|
- `repository.py`: 新增 `search_users(query)`;username `ilike` + email 精确匹配,`limit 20`。
|
|||
|
|
- `service.py`: 增加 `search_users(...)` 并映射公开字段。
|
|||
|
|
- `router.py`: 增加 `POST /users/search`;删除 `GET /users/{username}`。
|
|||
|
|
|
|||
|
|
**Step 4: 跑测试确认通过**
|
|||
|
|
|
|||
|
|
Run: `uv run pytest backend/tests/integration/test_users_routes.py -v`
|
|||
|
|
|
|||
|
|
Expected: PASS。
|
|||
|
|
|
|||
|
|
**Step 5: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add 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_users_routes.py
|
|||
|
|
git commit -m "refactor(users): replace username endpoint with search"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Phase 3: 前端认证 UX
|
|||
|
|
|
|||
|
|
### Task 5: 数据层支持密码重置两步接口
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `apps/lib/features/auth/data/models/login_request.dart`
|
|||
|
|
- Modify: `apps/lib/features/auth/data/auth_api.dart`
|
|||
|
|
- Modify: `apps/lib/features/auth/data/auth_repository.dart`
|
|||
|
|
- Modify: `apps/lib/features/auth/data/auth_repository_impl.dart`
|
|||
|
|
- Test: `apps/test/features/auth/data/auth_repository_impl_test.dart`(如不存在则创建)
|
|||
|
|
|
|||
|
|
**Step 1: 写失败测试**
|
|||
|
|
|
|||
|
|
覆盖:
|
|||
|
|
- request -> confirm 的调用顺序
|
|||
|
|
- confirm 请求包含 `email + token + new_password`
|
|||
|
|
|
|||
|
|
**Step 2: 跑测试确认失败**
|
|||
|
|
|
|||
|
|
Run: `flutter test apps/test/features/auth/data/auth_repository_impl_test.dart`
|
|||
|
|
|
|||
|
|
Expected: FAIL。
|
|||
|
|
|
|||
|
|
**Step 3: 最小实现**
|
|||
|
|
|
|||
|
|
- 增加 `requestPasswordReset` / `confirmPasswordReset`。
|
|||
|
|
- 模型保持 snake_case JSON 键与后端一致。
|
|||
|
|
|
|||
|
|
**Step 4: 跑测试确认通过**
|
|||
|
|
|
|||
|
|
Run: `flutter test apps/test/features/auth/data/auth_repository_impl_test.dart`
|
|||
|
|
|
|||
|
|
Expected: PASS。
|
|||
|
|
|
|||
|
|
**Step 5: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add apps/lib/features/auth/data apps/test/features/auth/data/auth_repository_impl_test.dart
|
|||
|
|
git commit -m "feat(auth): add password reset data layer"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Task 6: 新增忘记密码页面与状态管理
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Create: `apps/lib/features/auth/ui/screens/forgot_password_screen.dart`
|
|||
|
|
- Create: `apps/lib/features/auth/ui/screens/reset_password_screen.dart`
|
|||
|
|
- Create: `apps/lib/features/auth/presentation/cubits/forgot_password_cubit.dart`
|
|||
|
|
- Create: `apps/lib/features/auth/presentation/cubits/reset_password_cubit.dart`
|
|||
|
|
- Modify: `apps/lib/features/auth/ui/screens/login_screen.dart`
|
|||
|
|
- Modify: `apps/lib/core/router/app_router.dart`
|
|||
|
|
- Test: `apps/test/features/auth/ui/forgot_password_screen_test.dart`(可新建)
|
|||
|
|
|
|||
|
|
**Step 1: 写失败测试**
|
|||
|
|
|
|||
|
|
至少覆盖:
|
|||
|
|
- 登录页点击“忘记密码”可跳转
|
|||
|
|
- 忘记密码页提交后进入验证码改密页
|
|||
|
|
|
|||
|
|
**Step 2: 跑测试确认失败**
|
|||
|
|
|
|||
|
|
Run: `flutter test apps/test/features/auth/ui/forgot_password_screen_test.dart`
|
|||
|
|
|
|||
|
|
Expected: FAIL。
|
|||
|
|
|
|||
|
|
**Step 3: 最小实现**
|
|||
|
|
|
|||
|
|
- 使用 `Toast` 呈现提交成功/失败反馈。
|
|||
|
|
- reset 页面提交时调用单个 confirm 接口完成验码与改密。
|
|||
|
|
- 成功后跳回登录并提示“密码已重置”。
|
|||
|
|
|
|||
|
|
**Step 4: 跑测试确认通过**
|
|||
|
|
|
|||
|
|
Run: `flutter test apps/test/features/auth/ui/forgot_password_screen_test.dart`
|
|||
|
|
|
|||
|
|
Expected: PASS。
|
|||
|
|
|
|||
|
|
**Step 5: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add apps/lib/features/auth apps/lib/core/router/app_router.dart apps/test/features/auth/ui/forgot_password_screen_test.dart
|
|||
|
|
git commit -m "feat(auth): add forgot password ui flow"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Task 7: 注册体验优化(提示 + 邀请码)
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `apps/lib/features/auth/ui/screens/register_verification_screen.dart`
|
|||
|
|
- Modify: `apps/lib/features/auth/ui/screens/register_screen.dart`
|
|||
|
|
- Modify: `apps/lib/features/auth/presentation/cubits/register_cubit.dart`
|
|||
|
|
- Modify: `apps/lib/features/auth/data/models/signup_request.dart`
|
|||
|
|
- Test: `apps/test/features/auth/ui/register_screen_test.dart`(如不存在则创建)
|
|||
|
|
|
|||
|
|
**Step 1: 写失败测试**
|
|||
|
|
|
|||
|
|
覆盖:
|
|||
|
|
- 验证码页首次进入显示提示 Toast
|
|||
|
|
- 注册页存在邀请码输入并为可选
|
|||
|
|
|
|||
|
|
**Step 2: 跑测试确认失败**
|
|||
|
|
|
|||
|
|
Run: `flutter test apps/test/features/auth/ui/register_screen_test.dart`
|
|||
|
|
|
|||
|
|
Expected: FAIL。
|
|||
|
|
|
|||
|
|
**Step 3: 最小实现**
|
|||
|
|
|
|||
|
|
- `signup_request` 可选字段 `invite_code`。
|
|||
|
|
- 页面展示邀请码输入框,不做必填校验。
|
|||
|
|
|
|||
|
|
**Step 4: 跑测试确认通过**
|
|||
|
|
|
|||
|
|
Run: `flutter test apps/test/features/auth/ui/register_screen_test.dart`
|
|||
|
|
|
|||
|
|
Expected: PASS。
|
|||
|
|
|
|||
|
|
**Step 5: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add apps/lib/features/auth/ui/screens/register_verification_screen.dart apps/lib/features/auth/ui/screens/register_screen.dart apps/lib/features/auth/presentation/cubits/register_cubit.dart apps/lib/features/auth/data/models/signup_request.dart apps/test/features/auth/ui/register_screen_test.dart
|
|||
|
|
git commit -m "feat(auth): improve verification hint and invite code input"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Phase 4: 前端 users 数据层迁移
|
|||
|
|
|
|||
|
|
### Task 8: 将 getByUsername 迁移为 searchUsers
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `apps/lib/features/users/data/users_api.dart`
|
|||
|
|
- Modify: `apps/lib/features/users/data/users_repository.dart`
|
|||
|
|
- Modify: `apps/lib/features/users/data/users_repository_impl.dart`
|
|||
|
|
- Test: `apps/test/features/users/data/users_repository_test.dart`(如不存在则创建)
|
|||
|
|
|
|||
|
|
**Step 1: 写失败测试**
|
|||
|
|
|
|||
|
|
覆盖:
|
|||
|
|
- `searchUsers(query)` 发送 `POST /api/v1/users/search`
|
|||
|
|
- 返回列表模型映射正确
|
|||
|
|
|
|||
|
|
**Step 2: 跑测试确认失败**
|
|||
|
|
|
|||
|
|
Run: `flutter test apps/test/features/users/data/users_repository_test.dart`
|
|||
|
|
|
|||
|
|
Expected: FAIL。
|
|||
|
|
|
|||
|
|
**Step 3: 最小实现**
|
|||
|
|
|
|||
|
|
- 删除 `getByUsername`。
|
|||
|
|
- 新增 `searchUsers(String query)`。
|
|||
|
|
|
|||
|
|
**Step 4: 跑测试确认通过**
|
|||
|
|
|
|||
|
|
Run: `flutter test apps/test/features/users/data/users_repository_test.dart`
|
|||
|
|
|
|||
|
|
Expected: PASS。
|
|||
|
|
|
|||
|
|
**Step 5: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add apps/lib/features/users/data apps/test/features/users/data/users_repository_test.dart
|
|||
|
|
git commit -m "refactor(users): migrate client to search endpoint"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Phase 5: 文档与全量验证
|
|||
|
|
|
|||
|
|
### Task 9: 更新 API 文档并完成全量检查
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `docs/runtime/runtime-route.md`
|
|||
|
|
|
|||
|
|
**Step 1: 更新路由文档**
|
|||
|
|
|
|||
|
|
- 新增:
|
|||
|
|
- `POST /auth/password-reset`
|
|||
|
|
- `POST /auth/password-reset/confirm`
|
|||
|
|
- `POST /users/search`
|
|||
|
|
- 删除:
|
|||
|
|
- `GET /users/{username}`
|
|||
|
|
|
|||
|
|
文档需包含:请求/响应 schema、状态码、错误格式(RFC 7807)。
|
|||
|
|
|
|||
|
|
**Step 2: 跑后端验证**
|
|||
|
|
|
|||
|
|
Run: `uv run pytest backend/tests -v && uv run basedpyright backend/src`
|
|||
|
|
|
|||
|
|
Expected: 全通过。
|
|||
|
|
|
|||
|
|
**Step 3: 跑前端验证**
|
|||
|
|
|
|||
|
|
Run: `flutter analyze apps/lib && flutter test`
|
|||
|
|
|
|||
|
|
Expected: 全通过。
|
|||
|
|
|
|||
|
|
**Step 4: 手动验收**
|
|||
|
|
|
|||
|
|
- 忘记密码完整链路(发码/确认改密/登录)
|
|||
|
|
- 注册页邀请码与验证码提示
|
|||
|
|
- `POST /users/search` 返回结果正确
|
|||
|
|
|
|||
|
|
**Step 5: Commit**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add docs/runtime/runtime-route.md
|
|||
|
|
git commit -m "docs: sync runtime routes for auth ux enhancement"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 验收清单
|
|||
|
|
|
|||
|
|
- [ ] 所有新增测试先失败后通过(有 RED/GREEN 记录)
|
|||
|
|
- [ ] 后端密码重置两步接口可用(confirm 内部完成验码 + 改密)
|
|||
|
|
- [ ] 前端忘记密码流程可用
|
|||
|
|
- [ ] 邀请码输入为选填且不破坏现有注册
|
|||
|
|
- [ ] `GET /users/{username}` 全链路移除
|
|||
|
|
- [ ] `POST /users/search` 前后端一致
|
|||
|
|
- [ ] `docs/runtime/runtime-route.md` 已同步
|