Files
user-system/docs/code-review/FULL_REVIEW_2026-05-30.md
2026-05-30 21:29:24 +08:00

561 lines
15 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.
# user-system 全面 Review 报告
**审查日期**2026-05-30
**审查范围**`/home/long/project/user-system`
**审查模式**:严格、系统、全面
**审查方式**:源码审阅 + 实际构建/测试/静态检查验证 + 第二轮契约一致性对账
**结论等级****B- / 有条件可运行,不可宣称“已全面收口”**
---
## 一、执行摘要
该项目不是不可用项目。后端、前端、测试主链路均可运行,说明系统已经具备较高完成度;但它距离“高可靠、可审计、严格闭环”的标准仍有明显差距,主要集中在以下五类问题:
1. **SSO/OAuth 协议正确性存在关键缺口**
2. **Swagger / 路由 / 文档之间存在系统性契约漂移**
3. **测试数量很多,但契约强度不足,且掩盖了真实路由/鉴权问题**
4. **质量门禁对外表述与实际状态不一致**
5. **缓存失效、参数校验、上传实现等边界质量仍不够严谨**
一句话结论:
> 当前项目可以诚实表述为“主体功能可运行、可测试,但仍存在高价值安全与契约治理缺口”;不能诚实表述为“严格闭环、全面审计通过”。
---
## 二、审查范围与方法
### 2.1 重点审查模块
- 启动与配置链路
- `cmd/server/main.go`
- `internal/server/server.go`
- `internal/config/config.go`
- 认证 / 授权 / 会话
- `internal/api/middleware/auth.go`
- `internal/service/auth.go`
- `internal/service/user_service.go`
- `internal/auth/sso.go`
- `internal/api/handler/sso_handler.go`
- 核心 Handler 与 API 暴露
- `internal/api/handler/user_handler.go`
- `internal/api/handler/export_handler.go`
- `internal/api/handler/avatar_handler.go`
- `internal/api/router/router.go`
- 仓储层
- `internal/repository/user.go`
- `internal/repository/operation_log.go`
- 前端契约与测试
- `frontend/admin/src/services/*`
- `frontend/admin/src/pages/admin/ImportExportPage/*`
- `internal/api/handler/*_test.go`
- `internal/e2e/*`
- 文档与 Swagger
- `docs/swagger.go`
- `docs/docs.go`
- `docs/API.md`
- `docs/archive/OAUTH_INTEGRATION.md`
### 2.2 第二轮差异化审查方法
除第一轮常规源码审阅外,第二轮增加了以下“不同方式”的 review
1. **路由注册 vs Swagger 注释逐项对账**
-`internal/api/router/router.go` 为真实路由基准
- 对照 `internal/api/handler/*.go` 中所有 `@Router` 注释
2. **协议路径 vs 鉴权模型对账**
- 重点检查 SSO `/authorize``/token``/introspect``/revoke``/userinfo`
- 核对它们是否被挂在了正确的 middleware / route group 下
3. **测试行为 vs 真实路由语义对账**
- 检查测试是否在错误的前提下仍“允许通过”
4. **文档路径 vs 前端调用路径对账**
- 对照 Swagger 注释、路由、前端 service、API 文档的四方一致性
第二轮发现了**新的系统性问题**,已补充到本报告和修复计划中。
---
## 三、实际执行的验证
以下命令已实际执行。
### 3.1 通过项
```bash
go test ./... -count=1
go build ./cmd/server
cd frontend/admin && env -u NODE_ENV npm run test:run
cd frontend/admin && env -u NODE_ENV npm run build
```
结果:
- `go test ./... -count=1`**通过**
- `go build ./cmd/server`**通过**
- 前端 `npm run test:run`**通过**
- `82 files / 525 tests`
- 前端 `npm run build`**通过**
### 3.2 失败项
```bash
go vet ./...
```
结果:**失败**
失败位置:
- `internal/api/handler/avatar_handler_test.go:204`
- `internal/api/handler/export_handler_test.go:174`
- `internal/api/handler/export_handler_test.go:202`
- `internal/api/handler/export_handler_test.go:229`
失败信息:
- `using resp before checking for errors`
这说明当前仓库不能继续对外宣称 `go vet` 已通过。
---
## 四、主要发现
---
## P0必须优先修复的问题
### P0-1Swagger 文档实际为空壳,当前不能算有效 API 文档
**证据**
`docs/swagger.go` 中:
```json
"paths": {}
```
同时 `internal/api/router/router.go` 公开暴露了:
- `/swagger/*any`
**影响**
- Swagger UI 可能可访问
- 但 API spec 本身没有有效路径
- “Swagger 已完成”是错误表述
**结论**:高优先级治理缺陷。
---
### P0-2Swagger 注释与真实路由存在系统性漂移,不是单点问题
第一轮只确认了导入导出接口漂移;第二轮确认:**这不是局部问题,而是全局契约漂移**。
**明确证据示例**
1. **导入导出接口**
- 注释:`/api/v1/exports/users``/api/v1/exports/template`
- 实际:`/api/v1/admin/users/export``/api/v1/admin/users/import``/api/v1/admin/users/import/template`
2. **刷新令牌接口**
- 注释:`/api/v1/auth/refresh-token`
- 实际:`/api/v1/auth/refresh`
3. **邮箱验证码登录接口**
- 注释:`/api/v1/auth/login-by-email-code`
- 实际:`/api/v1/auth/login/email-code`
4. **重发激活邮件接口**
- 注释:`/api/v1/auth/resend-activation-email`
- 实际:`/api/v1/auth/resend-activation`
5. **TOTP / 2FA 接口**
- 注释:`/api/v1/auth/totp/*`
- 实际:`/api/v1/auth/2fa/*`
-`SetupTOTP` 注释是 `POST`,实际路由是 `GET`
6. **Captcha 接口**
- 注释:`/api/v1/captcha/*`
- 实际:`/api/v1/auth/captcha*`
7. **密码重置接口**
- 注释:`/api/v1/auth/password/forgot``/reset`
- 实际:`/api/v1/auth/forgot-password``/reset-password``/forgot-password/phone`
8. **自定义字段接口**
- 注释:`/api/v1/fields/*`
- 实际:`/api/v1/custom-fields/*`
9. **日志接口**
- 注释:`/api/v1/users/me/login-logs``/operation-logs`
- 实际:`/api/v1/logs/login/me``/api/v1/logs/operation/me`
10. **管理员接口**
- 注释:`/api/v1/users/admins`
- 实际:`/api/v1/admin/admins`
11. **方法不一致**
- `AssignRoles` 注释为 `POST /api/v1/users/{id}/roles`,实际是 `PUT`
- `AssignPermissions` 注释为 `POST /api/v1/roles/{id}/permissions`,实际是 `PUT`
**影响**
- 当前 Swagger 注释整体**不可信**
- 不能基于其生成正确 SDK 或自动化客户端
- 文档、前端、后端、测试之间存在多套契约
- 即使把 Swagger 重新生成,也仍会生成错误契约,除非先修注释
**结论**:严重契约一致性问题。
---
### P0-3SSO 授权码没有绑定 redirect_uritoken 兑换阶段未校验 redirect_uri / code / client 三元绑定
**证据**
`internal/auth/sso.go``SSOSession` 结构体不包含 `RedirectURI` 字段。
`GenerateAuthorizationCode(clientID, redirectURI, scope, ...)` 虽接收 `redirectURI`,但没有保存到 session。
`internal/api/handler/sso_handler.go``Token` 流程中:
- 校验了 `grant_type`
- 校验了 `client_secret`
- 校验了 `code` 是否存在
- **未校验** `req.RedirectURI == session.RedirectURI`
- **未做严格的 code-client-redirect 三元绑定**
**影响**
- 授权码模式协议实现不完整
- 授权码被截获或混用时,服务端缺少关键约束
- 不满足高可靠安全要求
**结论**:严重安全问题。
---
### P0-4SSO implicit flow 仍被支持,并通过 URL fragment 返回 access token
**证据**
`internal/api/handler/sso_handler.go` 中,当 `response_type == "token"` 时:
```go
redirectURL := req.RedirectURI + "#access_token=" + token + "&expires_in=7200"
```
**影响**
- access token 暴露给前端地址片段
- 不适合高安全系统
- 与现代 OAuth 推荐实践不一致
**结论**:严重安全设计问题。
---
### P0-5SSO `/token`、`/introspect`、`/revoke`、`/userinfo` 被挂在错误的鉴权模型下,协议语义与访问控制同时出错
这是第二轮新增的关键发现。
**证据**
`internal/api/router/router.go` 中:
- SSO 整组被挂在:
- `protected := v1.Group("")`
- `protected.Use(r.authMiddleware.Required())`
- 然后:
- `sso := protected.Group("/sso")`
- `sso.POST("/token", r.ssoHandler.Token)`
- `sso.POST("/introspect", r.ssoHandler.Introspect)`
- `sso.POST("/revoke", r.ssoHandler.Revoke)`
- `sso.GET("/userinfo", r.ssoHandler.UserInfo)`
而对应 handler 语义是:
- `Token`:使用 `grant_type + code + client_id + client_secret` 兑换 token不依赖当前登录用户
- `Introspect`:只收 `token` / `client_id`
- `Revoke`:只收 `token`
- `UserInfo`:当前实现反而直接读 app auth middleware 注入的 `user_id` / `username`
**影响**
1. **OAuth 客户端无法按协议直接兑换授权码**
- 因为 `/token` 被错误地要求先通过平台 BearerAuth
2. **`/introspect``/revoke` 不是 client-auth 模型,而是 app-user-auth 模型**
- 任意已登录平台用户如果拿到 token 字符串,就可能执行 introspect / revoke
3. **`/userinfo` 返回的是平台 JWT 上下文中的用户,而不是 SSO access token 的 subject**
- 协议语义错误
4. **现有测试已经在掩盖这个问题**
- 测试里直接不带认证访问 `/api/v1/sso/token``/introspect``/revoke`
- 但断言允许 200/400/401 多种状态混过
**结论**:严重的协议与访问控制双重错误,必须优先修复。
---
## P1应尽快修复的问题
### P1-1测试大量使用“宽松状态码断言”无法守住真实接口契约
**证据**
`internal/api/handler/export_handler_test.go``internal/api/handler/sso_handler_test.go` 中大量断言允许:
- 200
- 302
- 400
- 401
- 403
- 500
中的多个同时通过。
**第二轮补充证据**
- `sso_handler_test.go` 中多处直接对 `/api/v1/sso/token``/introspect``/revoke` 发起**无认证请求**
- 但测试依旧允许 `401``400``200` 等多个互斥结果
- 这恰好掩盖了 `router.go` 中 SSO route group 被错误挂到 `protected` 下的问题
**影响**
- 测试数量多但行为约束弱
- 路由语义漂移、鉴权模型错误时测试仍可能全绿
- 会制造“测试全绿”的假象
**结论**:高优先级测试质量问题。
---
### P1-2`go vet ./...` 实际不通过,项目对外表述与真实状态不一致
**证据**
本次实际执行 `go vet ./...` 失败,失败点见第三节。
**影响**
- README 与状态文档中若继续宣称 `go vet PASS`,属于事实不符
- 静态分析未真正成为质量门禁
**结论**:高优先级工程质量问题。
---
### P1-3JWT secret 治理与项目自我标准不完全一致
**证据**
`cmd/server/main.go` 使用 `config.Load()`,不是 `LoadForBootstrap()`,这点是好的;但 `internal/config/config.go` 中对弱 JWT secret 仅见 `warn` 级处理证据,而未见 release 模式弱值硬失败证据。
仓库多份 review / 标准文档则明确要求:
- 生产环境通过环境变量注入 `JWT_SECRET`
- 缺失 / 弱值应 fatal
**影响**
- 代码行为与治理标准之间存在差距
- 高可靠环境下,弱密钥仅告警不足够
**结论**:重要安全治理问题。
---
### P1-4用户状态 / 权限缓存失效接口存在,但未见业务路径接入证据
**证据**
`internal/api/middleware/auth.go` 暴露了:
- `InvalidateUserStateCache(userID)`
- `InvalidateUserPermCache(userID)`
但在 service / handler / server 调用链中未找到这些失效方法的业务接入证据。
同时缓存 TTL 为:
- 用户状态5s
- 权限缓存5min
**影响**
- 密码修改、状态修改、角色修改、权限调整后可能短时继续沿用旧授权结果
- 在高敏感场景中不够严格
**结论**:重要一致性问题。
---
### P1-5归档文档中存在拟真 OAuth secret 示例,文档边界不干净
**证据**
`docs/archive/OAUTH_INTEGRATION.md` 中存在:
```yaml
client_secret: "GOCSPX-abcdef123456"
```
**影响**
- 容易被误判为真实 secret
- 不符合敏感信息示例占位规范
**结论**:文档安全卫生问题。
---
## P2建议优化的问题
### P2-1`strconvAtoi` 非法输入返回 `(0, nil)`,会吞掉参数错误
**证据**
`internal/api/handler/export_handler.go` 中:
```go
if c < '0' || c > '9' {
return 0, nil
}
```
这会把非法 `status=abc` 静默转换成 `0`
**影响**
- 参数错误被吞掉
- 查询语义可能被扭曲
**结论**:中优先级正确性问题。
---
### P2-2头像上传一次性读入整个文件不必要
**证据**
`internal/api/handler/avatar_handler.go`
```go
data := make([]byte, file.Size)
src.Read(data)
os.WriteFile(dstPath, data, 0o644)
```
**影响**
- 不必要的整块内存分配
- 虽当前 5MB 限制可控,但实现不够稳健
**结论**:中优先级实现质量问题。
---
### P2-3头像上传成功响应使用匿名 `gin.H`,接口 schema 易漂移
**证据**
`internal/api/handler/avatar_handler.go` 返回:
```go
"data": gin.H{
"avatar_url": avatarURL,
"thumbnail": avatarURL,
}
```
但注释中宣称的是 `AvatarResponse`
**影响**
- 文档与实现松耦合
- 前端类型契约不稳
**结论**:中优先级可维护性问题。
---
## 五、值得保留的正面设计
### 5.1 头像上传做了扩展名 + Magic Bytes 双校验
位置:`internal/api/handler/avatar_handler.go`
这是正确的防伪装上传设计。
### 5.2 LIKE 搜索做了特殊字符转义
位置:
- `internal/repository/user.go`
- `internal/repository/operation_log.go`
说明对模式匹配误用和干扰有明确防御意识。
### 5.3 权限查询做了合并查询 + 缓存
位置:`internal/api/middleware/auth.go`
方向正确,说明系统已考虑权限查询成本。
### 5.4 密码修改事务中避免重复 Argon2id 计算
位置:`internal/service/user_service.go`
这体现了不错的成本意识与事务处理意识。
### 5.5 前端对原生弹窗做了 guard
位置:`frontend/admin/src/app/bootstrap/installWindowGuards.ts`
与仓库“禁止原生 alert/confirm/prompt/open”的规则一致。
---
## 六、测试体系评估
### 6.1 测试“很多”,但不等于“严格”
当前问题不是缺测试,而是:
- 测试覆盖面不算窄
- 但很多 handler 测试不对行为做强约束
- 真实接口契约未被有效锁定
### 6.2 E2E 有价值,但仍偏“可访问性验证”
`internal/e2e/e2e_advanced_test.go` 已对真实 admin 导出路由做访问限制验证,这是正面项;但协议严谨性、返回结构一致性、错误语义边界仍缺少强验证。
### 6.3 第二轮确认:测试还在掩盖路由/鉴权模型错误
SSO 相关测试已经直接暴露出一个事实:
- 被测接口在路由层要求平台 BearerAuth
- 测试却在无认证前提下继续跑
- 断言又接受 200/400/401 多种结果
这类测试不是“有弹性”,而是**无法担任回归保护**。
### 6.4 `go vet` 尚未纳入真实闭环
当前最直接证据就是:`go vet ./...` 失败,而项目文档却可能继续声称通过。
---
## 七、最终结论
该项目:
- **可以运行**
- **可以构建**
- **大部分测试可以通过**
- **但仍不能宣称“严格闭环、全面收口、可全面审计通过”**
最关键的阻塞点不是“功能没做完”,而是:
1. **SSO/OAuth 协议与路由鉴权模型不够严谨**
2. **Swagger / 路由 / 文档契约漂移是系统性的,不是局部的**
3. **测试绿但不够硬,且会掩盖真实问题**
4. **静态检查门禁未真正闭环**
建议下一步按修复计划先处理 P0再收紧测试与门禁最后同步更新状态文档与对外表述。