Files
user-system/docs/sprints/SPRINT_13_COMPLETION_REPORT.md

183 lines
5.9 KiB
Markdown
Raw Normal View History

# Sprint 13 完成报告
**执行日期**: 2026-04-02
**Sprint 目标**: 处理 P2 设计断链问题,补齐 GAP 关键链路
**状态**: ✅ 全部核心任务完成
---
## 执行摘要
Sprint 13 聚焦于 PRD_GAP_DESIGN_PLAN.md 中识别的关键设计断链问题。本轮修复覆盖安全漏洞、密码历史链路完整性、设备信任链路三大方向。
---
## 任务完成情况
### ✅ GAP-01: 角色继承 — 确认已完整实现(无需修改)
**调研结论**
- `internal/service/role.go`:循环检测 `checkCircularInheritance` ✅ + 深度限制 `checkInheritanceDepth`5层
- `internal/api/middleware/auth.go``loadUserRolesAndPerms` 中收集祖先角色ID并汇总权限 ✅
- **此 GAP 已关闭**,无需额外修复
---
### ✅ GAP-02: SMS 密码重置验证码时序泄漏修复
**文件**: `internal/service/password_reset.go`
**问题**: 短信验证码比较使用普通字符串 `!=`,存在时序攻击窗口
**修复**:
```go
// 修复前
if !ok || code != req.Code {
return errors.New("验证码不正确")
}
// 修复后
if !ok || subtle.ConstantTimeCompare([]byte(code), []byte(req.Code)) != 1 {
return errors.New("验证码不正确")
}
```
**影响**: 防止通过响应时间差枚举有效验证码
---
### ✅ 密码历史记录: doResetPassword 补写历史
**文件**: `internal/service/password_reset.go`
**问题**: `doResetPassword`被邮件重置和SMS重置共同调用不检查密码历史不写入历史记录
**修复**:
1. `PasswordResetService` 新增 `passwordHistoryRepo` 字段
2. 新增 `WithPasswordHistoryRepo()` 链式方法(便于注入)
3. `doResetPassword` 现在:
- 检查新密码是否与最近5次密码重复
- 重置成功后异步写入密码历史记录,并清理超限旧记录
**注入点**: `cmd/server/main.go`
```go
passwordResetService := service.NewPasswordResetService(userRepo, cacheManager, passwordResetConfig).
WithPasswordHistoryRepo(passwordHistoryRepo)
```
---
### ✅ GAP-05: AnomalyDetector — 确认已接线(无需修改)
**调研结论**:
- `cmd/server/main.go` 第 111-112 行已初始化并注入 ✅
```go
anomalyDetector := security.NewAnomalyDetector(security.DefaultAnomalyConfig, ipFilter)
authService.SetAnomalyDetector(anomalyDetector)
```
- **此 GAP 已关闭**
---
### ✅ GAP-03: 设备信任链路 — 补齐设备 ID 传递
**问题分析**
设备信任链路存在以下断点:
| 断点 | 描述 |
|------|------|
| `auth_handler.go::Login` | handler 未接收 `device_id` 等字段,无法传入 `LoginRequest` |
| `sms_handler.go::LoginByCode` | 完全是 stub不调用真实 `AuthService.LoginByCode` |
| `LoginByEmailCode` | auth_handler 中的 stub未连接 auth_email.go 的实现 |
**修复内容**:
#### 1. `internal/api/handler/auth_handler.go` — 补齐密码登录设备字段
```go
// 修复前Login 不接收 device 字段
var req struct {
Account string `json:"account"`
Password string `json:"password"`
// ❌ 缺少 DeviceID, DeviceName, DeviceBrowser, DeviceOS
}
// 修复后:完整接收设备信息
var req struct {
Account string `json:"account"`
Password string `json:"password"`
DeviceID string `json:"device_id"` // ✅ 新增
DeviceName string `json:"device_name"` // ✅ 新增
DeviceBrowser string `json:"device_browser"` // ✅ 新增
DeviceOS string `json:"device_os"` // ✅ 新增
}
```
#### 2. `internal/api/handler/sms_handler.go` — 重写为真实实现
-`SMSHandler` 所有方法均为 stub
- 新增 `NewSMSHandlerWithService(authService, smsCodeService)` 构造函数
- `LoginByCode` 现在调用 `authService.LoginByCode()`,并在成功后异步调用 `BestEffortRegisterDevicePublic()` 注册设备
#### 3. `internal/service/auth.go` — 导出设备注册公共方法
```go
// 新增公共方法,供 SMS/邮箱验证码等非密码登录路径使用
func (s *AuthService) BestEffortRegisterDevicePublic(ctx context.Context, userID int64, req *LoginRequest) {
s.bestEffortRegisterDevice(ctx, userID, req)
}
```
---
## 验证结果
| 验证项 | 结果 |
|--------|------|
| `go build ./...` | ✅ 通过 |
| `go vet ./...` | ✅ 通过 |
| `go test ./... -count=1` | ✅ 全部通过(含 e2e、integration、security 等) |
| Lint受改文件 | ✅ 无错误 |
---
## 修改的文件清单
| 文件 | 类型 | 修改描述 |
|------|------|----------|
| `internal/service/password_reset.go` | 修改 | 添加 subtle 比较 + 密码历史检查/记录 + WithPasswordHistoryRepo |
| `internal/api/handler/auth_handler.go` | 修改 | Login 补齐 device 字段接收与传递 |
| `internal/api/handler/sms_handler.go` | 重写 | 从 stub 改为真实实现,支持设备注册 |
| `internal/service/auth.go` | 修改 | 导出 BestEffortRegisterDevicePublic |
| `cmd/server/main.go` | 修改 | 注入 passwordHistoryRepo 到 passwordResetService |
---
## 关闭的 GAP 项
| GAP | 描述 | 状态 |
|-----|------|------|
| GAP-01 | 角色继承 | ✅ 已实现Sprint 12 调研确认) |
| GAP-02 | SMS 密码重置 | ✅ 已完整修复(时序泄漏 + 密码历史) |
| GAP-05 | 异地/设备检测 | ✅ AnomalyDetector 已接线 |
| GAP-03 | 设备信任链路 | ✅ 主路径补齐(密码登录 + SMS登录 |
---
## 遗留项
| 项目 | 描述 | 优先级 |
|------|------|--------|
| 邮箱验证码登录 handler | `auth_handler.go::LoginByEmailCode` 仍是 stub | P2 |
| device_id 稳定性 | 前端 device_id 仍为随机生成,需稳定化 | P2 |
| GAP-04 (CAS/SAML SSO) | 明确推迟至 v2.0 | P3 |
| GAP-07 (SDK) | 明确推迟至 v2.0 | P3 |
---
## 下一步建议
1. **Sprint 14**: 补齐邮箱验证码登录真实 handler + 前端 device_id 稳定化方案
2. **Sprint 14**: 清理 `SlidingWindowLimiter` 死代码R6-02 建议项)
3. **前端联调**: 在密码登录接口中传递真实的 `device_id`(可用 `fingerprint.js` 生成稳定值)