整理内容: - 删除 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 ./... 通过
5.9 KiB
5.9 KiB
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
问题: 短信验证码比较使用普通字符串 !=,存在时序攻击窗口
修复:
// 修复前
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重置共同调用)不检查密码历史,不写入历史记录
修复:
PasswordResetService新增passwordHistoryRepo字段- 新增
WithPasswordHistoryRepo()链式方法(便于注入) doResetPassword现在:- 检查新密码是否与最近5次密码重复
- 重置成功后异步写入密码历史记录,并清理超限旧记录
注入点: cmd/server/main.go
passwordResetService := service.NewPasswordResetService(userRepo, cacheManager, passwordResetConfig).
WithPasswordHistoryRepo(passwordHistoryRepo)
✅ GAP-05: AnomalyDetector — 确认已接线(无需修改)
调研结论:
cmd/server/main.go第 111-112 行已初始化并注入 ✅
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 — 补齐密码登录设备字段
// 修复前: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 — 导出设备注册公共方法
// 新增公共方法,供 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 |
下一步建议
- Sprint 14: 补齐邮箱验证码登录真实 handler + 前端 device_id 稳定化方案
- Sprint 14: 清理
SlidingWindowLimiter死代码(R6-02 建议项) - 前端联调: 在密码登录接口中传递真实的
device_id(可用fingerprint.js生成稳定值)