519 lines
28 KiB
Markdown
519 lines
28 KiB
Markdown
# ZCLAW SaaS 功能审计报告 v7
|
||
|
||
> **ARCHIVED — 此报告已过时**
|
||
>
|
||
> 本报告是 2026-03-28 审计迭代的 v7 版本。最终结论已合并到 DEEP_AUDIT_REPORT.md。
|
||
>
|
||
> **请参考最新文档** → [README.md](./README.md)
|
||
|
||
> **审计日期**: 2026-03-28 (原文标注有误)
|
||
> **审计范围**: SaaS worktree(`.claude/worktrees/saas-backend/`)— Rust 后端 + Admin UI + 桌面端集成
|
||
> **审计方法**: 五步审计流程(文档对齐 → 数据流追踪 → Dead Code → 接口检查 → E2E 验证)+ 10 项通用清单 + 5 种差距模式 + 跨部门专家论证
|
||
> **前次审计**: v6(Tauri 端 78%,SaaS 端 18%)
|
||
|
||
---
|
||
|
||
## 一、执行摘要
|
||
|
||
| 指标 | 数值 |
|
||
|------|------|
|
||
| **SaaS 后端 API 端点** | 43 个(文档声明 44 个) |
|
||
| **文档-代码对齐率** | 97.7%(43/44,缺 `/api/health`) |
|
||
| **OpenAPI 文档覆盖** | 37/43(86%),6 个端点未注解 |
|
||
| **Admin UI 页面** | 13 个(含 login) |
|
||
| **Admin API 方法** | 30 个,全覆盖 |
|
||
| **桌面端 SaaS 方法** | 20 个,全覆盖 |
|
||
| **10 项审计清单通过率** | auth 90% / account 80% / model_config 100% / relay 90% / migration 60% |
|
||
| **五种差距模式发现** | 16 个问题 |
|
||
| **Dead Code** | 0 个 `#[allow(dead_code)]`/TODO/FIXME + 3 个配置字段 + 3 个遗留函数 |
|
||
| **安全漏洞** | 1 个 CRITICAL + 2 个 HIGH + 1 个 LOW |
|
||
| **审计日志缺失** | migration 模块全部缺失(5 个变更端点) |
|
||
| **测试总数** | 86 个单元测试(10 个模块有测试,9 个文件零测试) |
|
||
| **整体 SaaS 完成度** | **~88%**(V6 审计评估 18% 严重低估,修正为 88%)
|
||
|
||
### 与 V6 审计关键差异
|
||
|
||
V6 报告评估 SaaS 完成度为 18%,这是**基于错误假设**(将 Tauri 端 AI 能力缺失计入 SaaS 差距)。本次审计确认 SaaS 的设计定位(系统管理:账号/权限/relay),在定位范围内完成度为 **~88%**。
|
||
|
||
---
|
||
|
||
## 二、功能清单与真实完成度
|
||
|
||
### 2.1 SaaS 功能模块总览
|
||
|
||
| # | 功能模块 | 设计意图 | 文档声称 | 真实完成度 | 差距说明 |
|
||
|---|---------|---------|---------|-----------|---------|
|
||
| 1 | 用户认证 | 多模式登录、JWT 生命周期、TOTP 2FA | 100% | **95%** | `/api/health` 未实现;`auth.refresh` 无审计日志 |
|
||
| 2 | 账号管理 | CRUD + 角色限制 + 设备管理 | 100% | **90%** | `AccountPublicPaginatedResponse` 死代码;dashboard 7 次串行查询 |
|
||
| 3 | API Token | 创建/撤销/权限管理 | 100% | **100%** | 无 |
|
||
| 4 | 模型配置 | Provider/Model/Key CRUD + 用量 | 95% | **90%** | `account_api_keys` 存了没用(Relay 未消费) |
|
||
| 5 | LLM Relay | OpenAI 兼容代理 + SSE + 重试 + SSRF | 95% | **90%** | `request_hash`/`priority`/`batch_window_ms` 存了没用;`chat_completions` OpenAPI 缺失 |
|
||
| 6 | 配置迁移 | diff/sync/seed + 日志 | 90% | **70%** | `sync_config` 缺权限检查(CRITICAL);`pull` 未实现;5 个端点缺审计日志;`created` 计数器永远为 0 |
|
||
| 7 | Admin UI | 13 页面仪表盘 | 95% | **85%** | 权限过滤 Bug(admin 角色缺 4 页面);分页未连接 API;无前端路由保护 |
|
||
| 8 | 桌面端集成 | 登录/设备/Relay/迁移向导 | 95% | **90%** | `computeConfigDiff`/`syncConfig` 定义但未调用;调用路径不一致 |
|
||
|
||
### 2.2 未实现功能(设计文档提及但代码中不存在)
|
||
|
||
| 功能 | 文档状态 | 代码状态 | 说明 |
|
||
|------|---------|---------|------|
|
||
| `/api/health` 健康检查 | 文档声明 | **未实现** | `main.rs` 无此路由 |
|
||
| `pull` 配置同步方向 | types.rs 注释 | **未实现** | service 只处理 push/merge |
|
||
| 订阅/计费系统 | roadmap 提及 | **未实现** | 无代码 |
|
||
| 多租户隔离 | 无 | **未实现** | 无代码 |
|
||
| 配额执行 | 无 | **未实现** | `max_queue_size`/`max_concurrent` 定义但未执行 |
|
||
|
||
---
|
||
|
||
## 三、API 端点覆盖率
|
||
|
||
### 3.1 端点清单(文档 vs 代码)
|
||
|
||
| 分类 | 端点 | 文档 | 代码 | OpenAPI | 测试 |
|
||
|------|------|------|------|---------|------|
|
||
| **公开** | `POST /api/v1/auth/register` | ✅ | ✅ | ✅ | ✅ |
|
||
| | `POST /api/v1/auth/login` | ✅ | ✅ | ✅ | ✅ |
|
||
| | `GET /api/health` | ✅ | **❌** | ❌ | ❌ |
|
||
| **认证** | `GET /api/v1/auth/me` | ✅ | ✅ | ✅ | — |
|
||
| | `POST /api/v1/auth/refresh` | ✅ | ✅ | ✅ | — |
|
||
| | `PUT /api/v1/auth/password` | ✅ | ✅ | ✅ | — |
|
||
| **TOTP** | `POST /api/v1/auth/totp/setup` | ✅ | ✅ | ✅ | — |
|
||
| | `POST /api/v1/auth/totp/verify` | ✅ | ✅ | ✅ | — |
|
||
| | `POST /api/v1/auth/totp/disable` | ✅ | ✅ | ✅ | — |
|
||
| **账号** | `GET /api/v1/accounts` | ✅ | ✅ | ✅ | — |
|
||
| | `GET /api/v1/accounts/{id}` | ✅ | ✅ | ✅ | — |
|
||
| | `PUT /api/v1/accounts/{id}` | ✅ | ✅ | ✅ | — |
|
||
| | `PATCH /api/v1/accounts/{id}/status` | ✅ | ✅ | ✅ | — |
|
||
| | `GET /api/v1/stats/dashboard` | ✅ | ✅ | ✅ | — |
|
||
| **Token** | `GET /api/v1/tokens` | ✅ | ✅ | ✅ | — |
|
||
| | `POST /api/v1/tokens` | ✅ | ✅ | ✅ | — |
|
||
| | `DELETE /api/v1/tokens/{id}` | ✅ | ✅ | ✅ | — |
|
||
| **设备** | `POST /api/v1/devices/register` | ✅ | ✅ | ✅ | — |
|
||
| | `POST /api/v1/devices/heartbeat` | ✅ | ✅ | ✅ | — |
|
||
| | `GET /api/v1/devices` | ✅ | ✅ | ✅ | — |
|
||
| **Provider** | `GET/POST /api/v1/providers` | ✅ | ✅ | ✅ | — |
|
||
| | `GET/PUT/DELETE /api/v1/providers/{id}` | ✅ | ✅ | ✅ | — |
|
||
| | `GET /api/v1/providers/{id}/models` | ✅ | ✅ | ✅ | — |
|
||
| **Model** | `GET/POST /api/v1/models` | ✅ | ✅ | ✅ | — |
|
||
| | `GET/PUT/DELETE /api/v1/models/{id}` | ✅ | ✅ | ✅ | — |
|
||
| **Key** | `GET/POST/DELETE /api/v1/keys` | ✅ | ✅ | ✅ | — |
|
||
| | `POST /api/v1/keys/{id}/rotate` | ✅ | ✅ | ✅ | — |
|
||
| **Relay** | `GET /api/v1/relay/models` | ✅ | ✅ | ✅ | — |
|
||
| | `POST /api/v1/relay/chat/completions` | ✅ | ✅ | **❌** | — |
|
||
| | `GET /api/v1/relay/tasks` | ✅ | ✅ | ✅ | — |
|
||
| | `GET /api/v1/relay/tasks/{id}` | ✅ | ✅ | ✅ | — |
|
||
| | `POST /api/v1/relay/tasks/{id}/retry` | ✅ | ✅ | ✅ | — |
|
||
| **Config** | `GET/POST /api/v1/config/items` | ✅ | ✅ | ✅ | — |
|
||
| | `GET/PUT/DELETE /api/v1/config/items/{id}` | ✅ | ✅ | ✅ | — |
|
||
| | `GET /api/v1/config/analysis` | ✅ | ✅ | ✅ | — |
|
||
| | `POST /api/v1/config/seed` | ✅ | ✅ | ✅ | — |
|
||
| | `POST /api/v1/config/sync` | ✅ | ✅ | ✅ | — |
|
||
| | `POST /api/v1/config/diff` | ✅ | ✅ | ✅ | — |
|
||
| | `GET /api/v1/config/sync-logs` | ✅ | ✅ | ✅ | — |
|
||
| **审计** | `GET /api/v1/logs/operations` | ✅ | ✅ | ✅ | — |
|
||
| | `GET /api/v1/usage` | ✅ | ✅ | ✅ | — |
|
||
|
||
**统计**: 43/44 端点已实现(97.7%),37/43 有 OpenAPI 文档(86%)
|
||
|
||
**缺失 OpenAPI 文档的 6 个端点**:
|
||
1. `POST /api/v1/auth/totp/setup` — TOTP 设置
|
||
2. `POST /api/v1/auth/totp/verify` — TOTP 验证
|
||
3. `POST /api/v1/auth/totp/disable` — TOTP 禁用
|
||
4. `GET /api/v1/logs/operations` — 操作日志
|
||
5. `GET /api/v1/stats/dashboard` — 仪表盘统计
|
||
6. `POST /api/v1/relay/chat/completions` — 聊天中转
|
||
|
||
---
|
||
|
||
## 四、审计矩阵
|
||
|
||
### 4.1 十项通用审计清单
|
||
|
||
| # | 检查项 | auth | account | model_config | relay | migration |
|
||
|---|--------|------|---------|-------------|-------|-----------|
|
||
| 1 | 代码存在性 | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||
| 2 | 调用链连通 | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||
| 3 | 配置传递 | ✅ | ✅ | ✅ | **⚠️** 3 字段未使用 | ✅ |
|
||
| 4 | 降级策略 | ✅ JWT 过期处理 | — | — | ✅ 上游超时重试 | — |
|
||
| 5 | 错误处理 | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||
| 6 | 权限执行 | ✅ | ✅ | ✅ | ✅ | **❌** sync_config |
|
||
| 7 | 日志覆盖 | **⚠️** refresh 缺失 | ✅ | ✅ | ✅ | **❌** 全部缺失 |
|
||
| 8 | 输入验证 | ✅ | ✅ | ✅ | ✅ | **⚠️** sync action |
|
||
| 9 | 测试覆盖 | ✅ 3 tests | ✅ 2 tests | ✅ 1 test | ✅ 30+ tests | ✅ 2 tests |
|
||
| 10 | 文档一致性 | **⚠️** /health 缺失 | ✅ | ✅ | **⚠️** OpenAPI 缺失 | ✅ |
|
||
|
||
### 4.2 E2E 数据流验证
|
||
|
||
| # | 流程 | 结果 | 关键发现 |
|
||
|---|------|------|---------|
|
||
| 1 | 用户注册 → JWT → 首次请求 | **PASS** | 全链路正常,Argon2 哈希正确 |
|
||
| 2 | TOTP 登录 → 受保护 API | **PASS** | verify_totp_code → get_role_permissions → create_token 正确 |
|
||
| 3 | Relay SSE 流式 + 用量记录 | **PASS** | SSRF 防护 + DNS Rebinding + SSE 用量提取正确 |
|
||
| 4 | 配置同步 push/merge | **WARNING** | push 路径正常但缺权限检查;`created` 计数永远为 0 |
|
||
| 5 | 设备注册 + 心跳 | **PASS** | UPSERT + heartbeat UPDATE 正确 |
|
||
|
||
---
|
||
|
||
## 五、差距分析(5 种模式)
|
||
|
||
### 5.1 Pattern 1: "写了没接"(3 项)
|
||
|
||
| # | 发现 | 位置 | 严重性 |
|
||
|---|------|------|--------|
|
||
| G1 | `hash_request()` 计算 SHA-256 哈希存入 `request_hash` 但从未被查询 | `relay/service.rs:273` | LOW |
|
||
| G2 | `pull` 同步方向在类型注释中声明但 service 未实现 | `migration/service.rs:306` | LOW |
|
||
| G3 | `cleanup_stale_entries()` 声明为 pub(crate) 但从未被调用(仅测试使用) | `middleware.rs:54` | LOW |
|
||
|
||
### 5.2 Pattern 2: "接了没传"(2 项)
|
||
|
||
| # | 发现 | 位置 | 严重性 |
|
||
|---|------|------|--------|
|
||
| G4 | `sync_config` 的 `created` 变量初始化为 0 但从未递增 | `migration/service.rs:263` | MEDIUM |
|
||
| G5 | `chat_completions` 的 `_headers` 参数提取但未使用 | `relay/handlers.rs:25` | LOW |
|
||
|
||
### 5.3 Pattern 3: "传了没存"(0 项)
|
||
|
||
无发现。所有 SQL 变更正确执行。
|
||
|
||
### 5.4 Pattern 4: "存了没用"(5 项)
|
||
|
||
| # | 发现 | 位置 | 严重性 |
|
||
|---|------|------|--------|
|
||
| G6 | `request_hash` 列存储但从未用于去重 | `relay_tasks.request_hash` | LOW |
|
||
| G7 | `priority` 列永远为 0,未用于排序 | `relay_tasks.priority` | LOW |
|
||
| G8 | `max_queue_size` 配置字段定义但未执行 | `config.rs:49` | LOW |
|
||
| G9 | `max_concurrent_per_provider` 配置字段定义但未执行 | `config.rs:51` | LOW |
|
||
| G10 | `batch_window_ms` 配置字段定义但未使用 | `config.rs:53` | LOW |
|
||
| **G11** | **`account_api_keys` 表完整 CRUD 但 Relay 只使用 provider 级 API Key** | `model_config/service.rs:285` vs `relay/handlers.rs:56` | **HIGH** |
|
||
|
||
### 5.5 Pattern 5: "双系统不同步"(3 项)
|
||
|
||
| # | 发现 | 位置 | 严重性 |
|
||
|---|------|------|--------|
|
||
| G12 | Config 模块写 `config_sync_log` 但不写统一 `operation_logs`,破坏审计追踪 | `migration/handlers.rs` | **MEDIUM** |
|
||
| G13 | Provider 级 API Key + 用户级 API Key 双系统,Relay 只消费前者 | `relay/handlers.rs:56` | **MEDIUM** |
|
||
| G14 | Admin UI 导航权限过滤逻辑与后端 RBAC 权限系统不同步 | `layout.tsx:107` vs `db.rs` seed roles | **HIGH** |
|
||
|
||
### 5.6 Pattern 1 补充: "写了没接"(Admin UI + Desktop)
|
||
|
||
| # | 发现 | 位置 | 严重性 |
|
||
|---|------|------|--------|
|
||
| G15 | saas-client `computeConfigDiff()`/`syncConfig()` 定义但桌面端从未调用 | `saas-client.ts` | LOW |
|
||
| G16 | Admin 5 个列表页面分页组件存在但未连接 API 分页参数 | `accounts/providers/models/api-keys/relay page.tsx` | MEDIUM |
|
||
|
||
---
|
||
|
||
## 六、安全问题
|
||
|
||
| # | 问题 | 严重性 | 位置 | 说明 |
|
||
|---|------|--------|------|------|
|
||
| **S1** | `sync_config` 无权限检查 | **CRITICAL** | `migration/handlers.rs:83-89` | 任何认证用户可推送配置值,应要求 `config:write` |
|
||
| **S2** | Config 变更端点无审计日志 | **HIGH** | `migration/handlers.rs` | create/update/delete/seed/sync 5 个端点未调用 `log_operation()` |
|
||
| **S3** | Admin UI 权限过滤逻辑错误 | **HIGH** | `layout.tsx:107` | 普通 admin 角色无法看到 4 个管理页面;隐藏导航但未保护路由 |
|
||
| S4 | 邮箱验证仅检查 `@` 和 `.` | LOW | `auth/handlers.rs:28` | 可接受简单验证,PostgreSQL 有 email 类型约束 |
|
||
|
||
---
|
||
|
||
## 七、跨部门专家论证
|
||
|
||
### 发现 S1: `sync_config` 缺权限检查 (CRITICAL)
|
||
|
||
| 专家视角 | 分析 |
|
||
|---------|------|
|
||
| **产品经理** | 影响中等。当前仅桌面端调用此端点,但公开后任何用户可修改系统配置。优先级 P0 |
|
||
| **架构师** | 实现疏忽。其他所有 mutation 端点都有 `check_permission`,唯独 `sync_config` 遗漏。修复方案简单:添加 `check_permission(ctx, "config:write")` |
|
||
| **安全工程师** | 权限边界被突破。一个普通用户(`user` 角色,权限仅 `["model:read","relay:use","config:read"]`)可以覆盖任何配置项。风险:配置投毒 |
|
||
| **UX 设计师** | 无 UI 影响。Admin 和桌面端的配置同步 UI 都不会因此出现问题,因为其他端点都有权限检查 |
|
||
|
||
**结论**: 真实漏洞,P0 修复。一行代码即可修复。
|
||
|
||
### 发现 G11: `account_api_keys` 未被 Relay 消费 (HIGH)
|
||
|
||
| 专家视角 | 分析 |
|
||
|---------|------|
|
||
| **产品经理** | 影响高。用户在桌面端创建的 per-provider API Key 永远不会被使用,Relay 始终使用管理员配置的 provider 级 Key。这导致多用户共享同一个 API Key,无法追踪个人用量到具体 Key |
|
||
| **架构师** | 设计不完整。`account_api_keys` 的 CRUD 已实现(创建/撤销/轮换),Admin UI 有专门页面,但消费端(Relay)从未接入。需要决策:(A) Relay 优先使用用户级 Key;(B) 移除 `account_api_keys` 功能 |
|
||
| **安全工程师** | 用户级 Key 隔离未生效。当前所有用户的 Relay 请求共享同一个 provider Key,无法在 Key 级别做权限隔离或用量追踪 |
|
||
| **UX 设计师** | Admin UI 的 "API Keys" 页面展示用户创建的 Key 和轮换状态,但实际从未使用。用户可能困惑于 "为什么创建了 Key 但用量全部归到 provider" |
|
||
|
||
**结论**: 真实设计缺陷,需要架构决策。建议方案 A(Relay 优先用户级 Key,回退 provider 级)。
|
||
|
||
### 发现 G12: Config 模块审计日志缺失 (MEDIUM)
|
||
|
||
| 专家视角 | 分析 |
|
||
|---------|------|
|
||
| **产品经理** | 影响低。Config 变更频率低,通常由管理员操作。但合规审计需要完整日志 |
|
||
| **架构师** | 不一致。auth/account/model_config 模块都有 `log_operation()`,唯独 migration 模块缺失。`sync_config` 写了 `config_sync_log` 但这是专用日志,不在统一的 `operation_logs` 表中 |
|
||
| **安全工程师** | 审计盲区。如果有人在凌晨 3 点覆盖了系统配置,`operation_logs` 表中不会有记录 |
|
||
| **UX 设计师** | 无影响。Admin 操作日志页面不会显示 Config 变更记录 |
|
||
|
||
**结论**: 真实遗漏。修复简单:在 5 个 mutation handler 中添加 `log_operation()` 调用。
|
||
|
||
### 发现 G4: `sync_config` `created` 计数永远为 0 (MEDIUM)
|
||
|
||
| 专家视角 | 分析 |
|
||
|---------|------|
|
||
| **产品经理** | 影响低。桌面端迁移向导显示 "updated: N, created: 0, skipped: M"。`created` 永远为 0 可能让用户困惑 |
|
||
| **架构师** | 实现疏忽。push 路径只处理已存在的 config_items(UPDATE),merge 路径也只处理已存在的。真正的新增场景(客户端发送 SaaS 不存在的 key)未被处理 |
|
||
| **安全工程师** | 无安全影响 |
|
||
| **UX 设计师** | 迁移向导的统计信息不准确。`created` 应该在 INSERT 新 config_item 时递增 |
|
||
|
||
**结论**: 真实 Bug。`sync_config` 应在 INSERT 时递增 `created` 计数器。
|
||
|
||
### 发现 S3: `/api/health` 端点未实现 (LOW)
|
||
|
||
| 专家视角 | 分析 |
|
||
|---------|------|
|
||
| **产品经理** | 影响低。文档声明了但桌面端 `healthCheck()` 调用时如果返回 404 会判断为不健康。实际影响:桌面端可能误判服务器不可达 |
|
||
| **架构师** | 遗漏。实现简单,返回 `{ status: "ok", version: "x.y.z" }` 即可 |
|
||
| **安全工程师** | 无安全影响(公开端点) |
|
||
| **UX 设计师** | 桌面端 SaaSStatus 组件依赖健康检查。缺失会导致状态显示异常 |
|
||
|
||
**结论**: 真实遗漏。简单修复。
|
||
|
||
### 发现 S3: Admin UI 权限过滤逻辑错误 (HIGH)
|
||
|
||
| 专家视角 | 分析 |
|
||
|---------|------|
|
||
| **产品经理** | 影响高。多管理员场景下(组织有多个管理员),普通 `admin` 角色用户登录后看不到账号管理、服务商、模型管理、中转任务 4 个核心页面。这严重限制了管理员协作能力。优先级 P1 |
|
||
| **架构师** | 实现疏忽。过滤条件 `account.role === 'super_admin' \|\| item.permission === 'admin:full'` 未检查用户是否实际拥有 `item.permission`。正确做法应检查 `account.permissions.includes(item.permission)`。此外,仅隐藏导航项未做前端路由守卫,用户直接输入 URL 可绕过。修复需 2 处:(A) 修正过滤逻辑;(B) 添加路由级权限检查 |
|
||
| **安全工程师** | 前端权限过滤失效。虽然后端 API 有权限检查(`check_permission`),但前端暴露了所有路由 URL,攻击者可枚举管理端点。更重要的是,**前端路由未保护**意味着用户可能看到一个有权限错误但功能正常的页面,体验混乱 |
|
||
| **UX 设计师** | 用户体验混乱。管理员登录后看到侧边栏只有 6/12 项,可能认为自己权限不足。如果直接输入 URL 访问被隐藏的页面,后端返回 403 错误但页面不会给出清晰提示 |
|
||
|
||
**结论**: 真实 Bug,P1 修复。修正过滤逻辑 + 添加路由守卫。
|
||
|
||
### 发现 G16: Admin 分页组件未连接 API (MEDIUM)
|
||
|
||
| 专家视角 | 分析 |
|
||
|---------|------|
|
||
| **产品经理** | 影响中等。当前数据量小(早期用户)时无感知。但一旦账号/模型/密钥数量超过默认 page_size,用户只能看到第一批数据,无法翻页查看更多。优先级 P2 |
|
||
| **架构师** | 分页组件存在于 5 个页面但全部未连接 API 调用。`PageNavigation` 组件维护 `currentPage`/`pageSize` 状态,但 `api.xxx.list()` 调用未传递这些参数。修复方案:将分页状态传递到 API 调用,监听页码变化触发重新加载 |
|
||
| **安全工程师** | 无安全影响 |
|
||
| **UX 设计师** | 分页按钮显示但点击无效,或者始终显示"第 1 页"。用户可能认为数据只有这么多,不知道还有更多未加载 |
|
||
|
||
**结论**: 真实 Bug,P2 修复。5 个页面统一处理。
|
||
|
||
---
|
||
|
||
## 八、Admin UI 审计
|
||
|
||
### 8.1 页面-API 对齐
|
||
|
||
| 页面 | API 方法调用 | 状态 |
|
||
|------|------------|------|
|
||
| Login | `api.auth.login()` | ✅ |
|
||
| Dashboard | `api.stats.dashboard()` | ✅ |
|
||
| Accounts | `api.accounts.list/get/update/updateStatus()` | ✅ |
|
||
| Providers | `api.providers.list/get/create/update/delete()` | ✅ |
|
||
| Models | `api.models.list/get/create/update/delete()` | ✅ |
|
||
| API Keys | `api.tokens.list/create/revoke()` | ✅ |
|
||
| Usage | `api.usage.get()` | ✅ |
|
||
| Relay | `api.relay.list/get/retry()` | ✅ |
|
||
| Config | `api.config.list/update()` | ✅ |
|
||
| Logs | `api.logs.list()` | ✅ |
|
||
| Devices | `api.devices.list/register/heartbeat()` | ✅ |
|
||
| Profile | `api.auth.changePassword()` | ✅ |
|
||
| Security | `api.auth.totpSetup/verify/disable()` | ✅ |
|
||
|
||
**对齐率**: 100%。Admin UI 的 30 个 API 方法全部有对应后端端点。
|
||
|
||
### 8.2 Admin API 方法 vs 后端端点交叉验证
|
||
|
||
所有 30 个 Admin API 方法的路径均与后端路由匹配,无多余或缺失。
|
||
|
||
### 8.3 Admin UI 权限过滤 Bug(HIGH)
|
||
|
||
**位置**: `admin/src/app/(dashboard)/layout.tsx:103-108`
|
||
|
||
**问题**: 侧边栏导航过滤逻辑存在逻辑错误:
|
||
|
||
```typescript
|
||
// 当前代码(有 Bug)
|
||
.filter((item) => {
|
||
if (!item.permission) return true
|
||
if (!account) return false
|
||
return account.role === 'super_admin' || item.permission === 'admin:full'
|
||
})
|
||
```
|
||
|
||
**影响**: `navItems` 中 `account:admin`、`model:admin`、`relay:admin` 三个权限标记对普通 `admin` 角色用户**完全无效**。逻辑等价于:
|
||
- `permission: null` → 所有角色可见(正确)
|
||
- `permission: 'admin:full'` → 所有角色可见(Bug,应仅 super_admin 可见)
|
||
- `permission: 'account:admin'` → **仅** super_admin 可见(Bug,admin 也应有此权限)
|
||
- `permission: 'model:admin'` → **仅** super_admin 可见(Bug)
|
||
- `permission: 'relay:admin'` → **仅** super_admin 可见(Bug)
|
||
|
||
**结果**: 普通 `admin` 角色用户只能看到仪表盘、API 密钥、用量统计、个人设置、安全设置、设备管理(6/12),**无法看到**账号管理、服务商、模型管理、中转任务(4/12)。
|
||
|
||
**缺失**: 仅隐藏导航项,**未做前端路由保护**。用户直接输入 `/accounts`、`/providers` 等路径仍可访问页面(后端权限检查正常,但前端体验不一致)。
|
||
|
||
**修复方案**: 改用权限检查函数验证用户是否拥有对应权限:
|
||
```typescript
|
||
.filter((item) => {
|
||
if (!item.permission) return true
|
||
if (!account) return false
|
||
return account.permissions?.includes(item.permission) ?? false
|
||
})
|
||
```
|
||
|
||
### 8.4 Admin UI 分页功能不可用(MEDIUM)
|
||
|
||
**影响页面**: accounts、providers、models、api-keys、relay(5 个列表页面)
|
||
|
||
**发现**: 5 个页面均包含分页组件(PageNavigation),后端 API 支持分页参数(`page`/`page_size`),但**前端调用 API 时未传递分页参数**,始终获取第一页全部数据。
|
||
|
||
**根因**: API 调用使用 `api.xxx.list()` 而非 `api.xxx.list({ page, pageSize })`,分页状态(`currentPage`/`pageSize`)存在于组件 state 中但未与 API 调用关联。
|
||
|
||
### 8.5 Admin UI 错误处理一致性
|
||
|
||
**发现**: 所有页面均有 `try/catch` 处理 API 错误,但存在不一致:
|
||
- 部分页面使用 `setError(err.message)` 显示具体错误(accounts、providers)
|
||
- 部分页面使用 `console.error` 但无用户可见反馈(models:113、api-keys:138)
|
||
- 无统一的全局错误 Toast/Banner 组件
|
||
|
||
**影响**: LOW。用户可能在操作失败时看不到任何反馈。
|
||
|
||
---
|
||
|
||
## 九、桌面端 SaaS 集成审计
|
||
|
||
### 9.1 saas-client.ts 端点覆盖
|
||
|
||
| 方法 | 后端端点 | 匹配 |
|
||
|------|---------|------|
|
||
| `login()` | `POST /api/v1/auth/login` | ✅ |
|
||
| `register()` | `POST /api/v1/auth/register` | ✅ |
|
||
| `me()` | `GET /api/v1/auth/me` | ✅ |
|
||
| `refreshToken()` | `POST /api/v1/auth/refresh` | ✅ |
|
||
| `changePassword()` | `PUT /api/v1/auth/password` | ✅ |
|
||
| `setupTotp()` | `POST /api/v1/auth/totp/setup` | ✅ |
|
||
| `verifyTotp()` | `POST /api/v1/auth/totp/verify` | ✅ |
|
||
| `disableTotp()` | `POST /api/v1/auth/totp/disable` | ✅ |
|
||
| `registerDevice()` | `POST /api/v1/devices/register` | ✅ |
|
||
| `deviceHeartbeat()` | `POST /api/v1/devices/heartbeat` | ✅ |
|
||
| `listDevices()` | `GET /api/v1/devices` | ✅ |
|
||
| `listModels()` | `GET /api/v1/relay/models` | ✅ |
|
||
| `listRelayTasks()` | `GET /api/v1/relay/tasks` | ✅ |
|
||
| `getRelayTask()` | `GET /api/v1/relay/tasks/{id}` | ✅ |
|
||
| `retryRelayTask()` | `POST /api/v1/relay/tasks/{id}/retry` | ✅ |
|
||
| `chatCompletion()` | `POST /api/v1/relay/chat/completions` | ✅ |
|
||
| `listConfig()` | `GET /api/v1/config/items` | ✅ |
|
||
| `computeConfigDiff()` | `POST /api/v1/config/diff` | ✅ |
|
||
| `syncConfig()` | `POST /api/v1/config/sync` | ✅ |
|
||
| `healthCheck()` | `GET /api/health` | **❌** 端点不存在 |
|
||
|
||
**对齐率**: 19/20(95%)。唯一不匹配是 `healthCheck()` 调用了不存在的 `/api/health`。
|
||
|
||
### 9.2 桌面端特性
|
||
|
||
- **JWT Token 自动刷新**: 80% 生命周期时主动刷新 + visibilitychange 监听
|
||
- **会话持久化**: Token 存 OS Keyring(secureStorage),URL/Account 存 localStorage
|
||
- **遗留函数清理**: `loadSaaSSession()`/`saveSaaSSession()`/`clearSaaSSession()` 3 个同步版本标记为 `@deprecated`
|
||
- **网络重试**: 指数退避 2 次重试(1s, 2s)
|
||
- **离线检测**: `isServerReachable()` 状态追踪
|
||
|
||
### 9.3 桌面端架构不一致(MEDIUM)
|
||
|
||
**发现**: saas-client.ts 中定义了 `computeConfigDiff()` 和 `syncConfig()` 两个方法,但**桌面端代码中无任何组件或 store 调用它们**。配置迁移向导(ConfigMigrationWizard)实现了自己的 diff/sync 逻辑,不经过 saas-client。
|
||
|
||
**影响**: MEDIUM。代码冗余 + 维护风险。如果 saas-client 的 API 签名与向导实现不一致,可能出现行为分歧。
|
||
|
||
### 9.4 桌面端调用路径不一致(LOW)
|
||
|
||
**发现**: 部分 SaaS 操作通过 `saasStore`(Zustand)编排,其他操作直接调用 `saasClient`。无统一规范约定何时使用哪条路径。
|
||
|
||
**影响**: LOW。不影响功能正确性,但增加了代码理解和维护成本。
|
||
|
||
---
|
||
|
||
## 十、测试覆盖评估
|
||
|
||
| 模块 | 测试数 | 覆盖内容 |
|
||
|------|--------|---------|
|
||
| error.rs | 4 | SaasError 变体、IntoResponse |
|
||
| config.rs | 4 | 配置加载、环境变量覆盖 |
|
||
| middleware.rs | 8 | 速率限制、窗口过期、admin 豁免 |
|
||
| csrf.rs | 6 | Origin 验证、路径剥离、dev 绕过 |
|
||
| crypto.rs | 10 | 加密/解密、篡改检测、错误密钥 |
|
||
| jwt | 3 | Token 创建/验证/过期 |
|
||
| password | 2 | 哈希/验证 |
|
||
| db.rs | 2 | Schema 初始化、表存在性 |
|
||
| relay/service.rs | 37 | 重试逻辑、Token 提取、SSRF 检测(**最佳覆盖**) |
|
||
| model_config/service.rs | 10 | Provider/Model/Key CRUD |
|
||
| **总计** | **86** | — |
|
||
|
||
**零测试的 9 个文件**: 5 个 handler 文件、`auth/totp.rs`、`account/service.rs`、`migration/service.rs`、`state.rs`
|
||
|
||
---
|
||
|
||
## 十一、修复建议(优先级排序)
|
||
|
||
### P0 — CRITICAL(安全漏洞)
|
||
|
||
| # | 问题 | 修复方案 | 工作量 |
|
||
|---|------|---------|--------|
|
||
| P0-1 | `sync_config` 无权限检查 | `migration/handlers.rs:83` 添加 `check_permission(ctx, "config:write")` | **1 行** |
|
||
|
||
### P1 — HIGH(功能缺陷)
|
||
|
||
| # | 问题 | 修复方案 | 工作量 |
|
||
|---|------|---------|--------|
|
||
| P1-1 | `account_api_keys` 未被 Relay 消费 | Relay handler 查找用户级 Key,回退到 provider 级 Key | 中 |
|
||
| P1-2 | Config 模块 5 个端点缺审计日志 | 添加 `log_operation()` 调用 | 小 |
|
||
| P1-3 | `sync_config` `created` 计数永远为 0 | INSERT 时递增 `created` | 小 |
|
||
| P1-4 | Admin UI 权限过滤逻辑错误 | `layout.tsx:107` 改用 `account.permissions.includes(item.permission)` | **5 行** |
|
||
| P1-5 | Admin UI 缺前端路由保护 | 添加 `AuthGuard` 组件检查页面级权限 | 中 |
|
||
|
||
### P2 — MEDIUM(代码质量)
|
||
|
||
| # | 问题 | 修复方案 | 工作量 |
|
||
|---|------|---------|--------|
|
||
| P2-1 | `/api/health` 端点未实现 | 添加健康检查路由 | 小 |
|
||
| P2-2 | `chat_completions` 缺 OpenAPI 文档 | `openapi.rs` 添加 `#[utoipa::path]` | 小 |
|
||
| P2-3 | 6 个端点缺 OpenAPI 注解 | `openapi.rs` 补充 `#[utoipa::path]` | 中 |
|
||
| P2-4 | `AccountPublicPaginatedResponse` 仅 OpenAPI 用 | 保留或统一到 PaginatedResponse | 小 |
|
||
| P2-5 | 3 个 Relay 配置字段未使用 | 移除或实现 `max_queue_size`/`max_concurrent`/`batch_window_ms` | 中 |
|
||
| P2-6 | `request_hash`/`priority` 列存了没用 | 移除或实现去重/优先级排序 | 中 |
|
||
| P2-7 | `pull` 同步方向未实现 | 实现或从类型注释中移除 | 中 |
|
||
| P2-8 | 3 个遗留同步函数未清理 | 移除 `loadSaaSSession`/`saveSaaSSession`/`clearSaaSSession` | 小 |
|
||
| P2-9 | dashboard_stats 7 次串行查询 | 合并为单次 SQL 查询 | 小 |
|
||
| P2-10 | Admin 5 个列表页面分页未连接 API | 将分页状态传递到 API 调用 | 小 |
|
||
| P2-11 | `computeConfigDiff`/`syncConfig` saas-client 定义但未使用 | 移除或统一迁移向导调用路径 | 小 |
|
||
| P2-12 | Admin UI catch 块无用户可见错误反馈 | 添加全局 Toast 组件 | 小 |
|
||
|
||
### P3 — LOW(测试补充)
|
||
|
||
| # | 问题 | 修复方案 | 工作量 |
|
||
|---|------|---------|--------|
|
||
| P3-1 | auth/account/model_config/migration 缺单元测试 | 添加 handler/service 层测试 | 大 |
|
||
|
||
---
|
||
|
||
## 十二、审计结论
|
||
|
||
### SaaS 后端健康度: **良好(~88%)**
|
||
|
||
**优势**:
|
||
- 功能-代码对齐率 97.7%(43/44 端点)
|
||
- 安全基础设施成熟(Argon2 + TOTP + RBAC + AES-256-GCM + CSRF + SSRF 防护)
|
||
- Admin UI 覆盖所有管理功能(13 页面)
|
||
- 桌面端集成完整(登录/设备/Relay/配置迁移)
|
||
- 测试覆盖 86 个单元测试
|
||
- 代码质量高:零 TODO/FIXME,错误处理一致
|
||
|
||
**待改进**:
|
||
- 1 个 CRITICAL 安全漏洞(`sync_config` 权限检查缺失)
|
||
- 2 个 HIGH 缺陷(用户级 API Key 未被 Relay 消费;Admin 权限过滤逻辑错误)
|
||
- 1 个 MEDIUM 功能 Bug(`created` 计数永远为 0)
|
||
- Config 模块审计日志完全缺失
|
||
- Admin UI 分页未连接 API(5 个页面)
|
||
- Admin UI 无前端路由保护
|
||
- 5 个配置字段定义但未执行
|
||
|
||
### 修正 V6 评估
|
||
|
||
V6 审计将 SaaS 完成度评估为 18%,原因是将 Tauri 端 AI 能力(Agent/Skill/Hand/Pipeline/Memory/Intelligence/Browser/OpenViking)的缺失计入 SaaS 差距。**这是错误的**——SaaS 的设计定位是系统管理(账号/权限/relay),不负责 AI 能力。在正确定位下,SaaS 完成度为 **~88%**(V7 初评 90% 下调 2%,因发现 Admin UI 权限 Bug 和分页未连接),修复 P0-P2 项即可达到 95%+。
|
||
|
||
---
|
||
|
||
*审计完成。本报告基于 2026-03-28 代码快照(worktree-saas-backend 分支,commit bc12f68),所有发现均可通过文档中引用的文件路径和行号验证。*
|