Files
user-system/docs/sprints/SPRINT_13_COMPLETION_REPORT.md
long-agent 5b6bd93179 refactor: 整理项目根目录结构
整理内容:
- 删除 60+ 临时测试输出文件 (*.txt)
- 移动二进制文件到 bin/ 目录
- 移动 Shell 脚本到 scripts/ 目录
  - scripts/dev/: check_gitea.sh, check_sub2api.sh, run_tests.sh
  - scripts/deploy/: deploy_*.sh, simple_deploy.sh
  - scripts/ops/: fix_nginx.sh, fix_ssl.sh, install_docker.sh
  - scripts/test/: test_*.sh, test_*.bat
- 移动批处理文件到 scripts/
- 移动 Python 脚本到 tools/
- 清理临时日志文件

保留根目录必要文件:
- go.mod, go.sum, go.work
- Makefile, docker-compose.yml
- .env.example, .gitignore
- README.md, AGENTS.md, DEPLOY_GUIDE.md

验证: go build ./... && go test ./... 通过
2026-04-07 18:10:36 +08:00

183 lines
5.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 生成稳定值)