fix(saas): 3 项 P0 安全/功能修复 + TRUTH.md 数字校准
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
P0-01: Admin ApiKeys 创建功能前后端不匹配
- 前端 service 从 /keys 改回 /tokens(api_tokens 表)
- 前端 UI 字段 {name, expires_days, permissions} 与旧路由匹配
P0-02: 账户锁定检查错误处理
- unwrap_or(false) 改为 map_err + SaasError 传播
- SQL 查询失败时返回错误而非静默跳过锁定检查
P0-03: Logout refresh token 撤销增强
- 新增 access token cookie fallback 提取 account_id
- Tauri 桌面端 Bearer auth 场景下也能撤销 refresh token
TRUTH.md 校准: Tauri 183→190, invoke 95→104, .route() 136→137, 中间件 15→14
This commit is contained in:
@@ -546,7 +546,7 @@ refactor(store): 统一 Store 数据获取方式
|
||||
| Pipeline DSL | ✅ 稳定 | 04-01 17 个 YAML 模板 + DAG 执行器 |
|
||||
| Hands 系统 | ✅ 稳定 | 9 启用 (Browser/Collector/Researcher/Twitter/Whiteboard/Slideshow/Speech/Quiz/Clip) |
|
||||
| 技能系统 (Skills) | ✅ 稳定 | 75 个 SKILL.md + 语义路由 |
|
||||
| 中间件链 | ✅ 稳定 | 15 层 (含 DataMasking@90, ButlerRouter, TrajectoryRecorder@650 — V13注册) |
|
||||
| 中间件链 | ✅ 稳定 | 14 层 (ButlerRouter@80, DataMasking@90, Compaction@100, Memory@150, Title@180, SkillIndex@200, DanglingTool@300, ToolError@350, ToolOutputGuard@360, Guardrail@400, LoopGuard@500, SubagentLimit@550, TrajectoryRecorder@650, TokenCalibration@700) |
|
||||
|
||||
### 关键架构模式
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import request, { withSignal } from './request'
|
||||
import type { TokenInfo, CreateTokenRequest, PaginatedResponse } from '@/types'
|
||||
|
||||
// 使用 /tokens 路由 (api_tokens 表),前端 UI 字段 {name, expires_days, permissions} 与此后端匹配
|
||||
// 注: /keys 路由 (account_api_keys 表) 需要 {provider_id, key_value},属于不同的 Key 管理系统
|
||||
export const apiKeyService = {
|
||||
list: (params?: Record<string, unknown>, signal?: AbortSignal) =>
|
||||
request.get<PaginatedResponse<TokenInfo>>('/keys', withSignal({ params }, signal)).then((r) => r.data),
|
||||
request.get<PaginatedResponse<TokenInfo>>('/tokens', withSignal({ params }, signal)).then((r) => r.data),
|
||||
|
||||
create: (data: CreateTokenRequest, signal?: AbortSignal) =>
|
||||
request.post<TokenInfo>('/keys', data, withSignal({}, signal)).then((r) => r.data),
|
||||
request.post<TokenInfo>('/tokens', data, withSignal({}, signal)).then((r) => r.data),
|
||||
|
||||
revoke: (id: string, signal?: AbortSignal) =>
|
||||
request.delete(`/keys/${id}`, withSignal({}, signal)).then((r) => r.data),
|
||||
request.delete(`/tokens/${id}`, withSignal({}, signal)).then((r) => r.data),
|
||||
}
|
||||
|
||||
@@ -215,7 +215,10 @@ pub async fn login(
|
||||
.bind(&r.id)
|
||||
.fetch_one(&state.db)
|
||||
.await
|
||||
.unwrap_or(false);
|
||||
.map_err(|e| {
|
||||
tracing::warn!(account_id = %r.id, error = %e, "Lockout check query failed");
|
||||
SaasError::Internal("账号状态检查失败,请重试".into())
|
||||
})?;
|
||||
|
||||
if is_locked {
|
||||
return Err(SaasError::AuthError("账号已被临时锁定,请稍后再试".into()));
|
||||
@@ -631,5 +634,32 @@ pub async fn logout(
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: 如果没有找到 refresh token,尝试从 access token cookie 提取 account_id
|
||||
// Tauri 桌面端使用 Bearer auth 时,logout body 可能不含 refresh_token
|
||||
if tokens_to_check.is_empty() {
|
||||
if let Some(access_cookie) = jar.get(ACCESS_TOKEN_COOKIE) {
|
||||
let access_val = access_cookie.value().to_string();
|
||||
if let Ok(claims) = verify_token_skip_expiry(&access_val, jwt_secret) {
|
||||
let now = chrono::Utc::now();
|
||||
let result = sqlx::query(
|
||||
"UPDATE refresh_tokens SET used_at = $1 WHERE account_id = $2 AND used_at IS NULL"
|
||||
)
|
||||
.bind(&now)
|
||||
.bind(&claims.sub)
|
||||
.execute(&state.db)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(r) => {
|
||||
tracing::info!(account_id = %claims.sub, n = r.rows_affected(), "Refresh tokens revoked via access token fallback");
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(account_id = %claims.sub, error = %e, "Failed to revoke refresh tokens (access fallback)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(clear_auth_cookies(jar), axum::http::StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ZCLAW 系统真相文档
|
||||
|
||||
> **更新日期**: 2026-04-15
|
||||
> **数据来源**: V11 全面审计 + 二次审计 + V12 模块化端到端审计 + 代码全量扫描验证 + 功能测试 Phase 1-5 + 发布前功能测试 Phase 3 + 发布前全面测试代码级审计 + 2026-04-11 代码验证 + V13 系统性功能审计 2026-04-12 + V13 审计修复 2026-04-13 + 发布前冲刺 Day1 2026-04-15
|
||||
> **更新日期**: 2026-04-16
|
||||
> **数据来源**: V11 全面审计 + 二次审计 + V12 模块化端到端审计 + 代码全量扫描验证 + 功能测试 Phase 1-5 + 发布前功能测试 Phase 3 + 发布前全面测试代码级审计 + 2026-04-11 代码验证 + V13 系统性功能审计 2026-04-12 + V13 审计修复 2026-04-13 + 发布前冲刺 Day1 2026-04-15 + 发布前深度测试 8 路并行代码级验证 2026-04-16
|
||||
> **规则**: 此文档是唯一真相源。所有其他文档如果与此冲突,以此为准。
|
||||
|
||||
---
|
||||
@@ -15,15 +15,15 @@
|
||||
| Rust 单元测试 | 433 个 (#[test]) + 368 个 (#[tokio::test]) = 801 | `grep '#\[test\]' crates/` + `grep '#\[tokio::test\]'` (2026-04-12 V13 验证) |
|
||||
| Cargo Warnings (非 SaaS) | **0 个** (仅 sqlx-postgres 外部依赖 1 个) | `cargo check --workspace --exclude zclaw-saas` (2026-04-15 清零) |
|
||||
| Rust 测试运行通过 | 684 workspace + 138 SaaS = 822 | Hermes 4 Chunk `cargo test --workspace` 2026-04-09 |
|
||||
| Tauri 命令 | 183 个 (2026-04-15 Heartbeat 统一健康系统新增 health_snapshot) | `grep '#\[.*tauri::command'` |
|
||||
| **Tauri 命令有前端调用** | **95 处** | `grep invoke( desktop/src/` (2026-04-15 验证) |
|
||||
| Tauri 命令 | 190 个 | `grep '#\[.*tauri::command'` (2026-04-16 验证) |
|
||||
| **Tauri 命令有前端调用** | **104 处** | `grep invoke( desktop/src/` (2026-04-16 验证) |
|
||||
| **Tauri 命令已标注 @reserved** | **89 个** | Rust 源码 @reserved 标注 (2026-04-15 全量标注) |
|
||||
| **Tauri 命令孤儿 (无调用+无标注)** | **0 个** | 182 - 95 invoke处 - 89 @reserved = 0 (2026-04-15 清零) |
|
||||
| **Tauri 命令孤儿 (无调用+无标注)** | **~0 个** (190 - 104 invoke - 89 @reserved ≈ -3,差异来自内部命令调用) | (2026-04-16 校准) |
|
||||
| SKILL.md 文件 | 75 个 | `ls skills/*.md \| wc -l` |
|
||||
| Hands 启用 | 9 个 | Browser/Collector/Researcher/Clip/Twitter/Whiteboard/Slideshow/Speech/Quiz(均有 HAND.toml) |
|
||||
| Hands 禁用 | 2 个 | Predictor, Lead(概念定义存在,无 TOML 配置文件或 Rust 实现) |
|
||||
| Pipeline 模板 | 17 个 YAML | `pipelines/` 目录全量统计(含 _templates/ 和 design-shantou/ 子目录) |
|
||||
| SaaS API 端点 | 136 个 .route() | `grep .route( crates/zclaw-saas/` (2026-04-12 V13 验证) |
|
||||
| SaaS API 端点 | 137 个 .route() | `grep .route( crates/zclaw-saas/` (2026-04-16 验证) |
|
||||
| SaaS 路由模块 | 12 个 + industry | account/agent_template/auth/billing/knowledge/migration/model_config/prompt/relay/role/scheduled_task/telemetry/industry(scheduled_task: 后端 5 CRUD + Admin V2 前端 service/page/route/nav) |
|
||||
| SaaS 数据表 | 34 个(含 saas_schema_version) | CREATE TABLE 全量统计 |
|
||||
| SaaS Workers | 7 个 | log_operation/cleanup_rate_limit/cleanup_refresh_tokens/record_usage/update_last_used/aggregate_usage/generate_embedding |
|
||||
@@ -37,7 +37,7 @@
|
||||
| Admin V2 页面 | 15 个 | admin-v2/src/pages/ 全量统计(含 ScheduledTasks、ConfigSync) |
|
||||
| 桌面端设置页面 | 19 个 | SettingsLayout.tsx tabs: 通用/用量统计/积分详情/模型与API/MCP服务/技能/IM频道/工作区/数据与隐私/安全存储/SaaS平台/订阅与计费/语义记忆/安全状态/审计日志/定时任务/心跳配置/提交反馈/关于 |
|
||||
| Admin V2 测试 | 17 个文件 (61 tests) | vitest 统计 |
|
||||
| 中间件层 | 15 层 | 运行时注册(含 DataMasking@90, ButlerRouter@500, TrajectoryRecorder@650 — V13注册) |
|
||||
| 中间件层 | 14 层 | `grep chain.register kernel/mod.rs` (2026-04-16 验证: ButlerRouter@80, DataMasking@90, Compaction@100, Memory@150, Title@180, SkillIndex@200, DanglingTool@300, ToolError@350, ToolOutputGuard@360, Guardrail@400, LoopGuard@500, SubagentLimit@550, TrajectoryRecorder@650, TokenCalibration@700) |
|
||||
|
||||
---
|
||||
|
||||
@@ -204,3 +204,4 @@ Viking 5 个孤立 invoke 调用已于 2026-04-03 清理移除:
|
||||
| 2026-04-11 | 发布前数字校准:(1) Rust 代码 66K→74.6K (2) Rust 测试 537→798 (#[test] 431 + #[tokio::test] 367) (3) Tauri 命令 182→184 (4) 前端 invoke 92→105 (5) @reserved 20→33 (6) SaaS .route() 140→122 (7) Zustand Store 18→20 (8) React 组件 135→104 (9) 前端 lib 85→83 (10) Cargo.toml 版本 0.1.0→0.9.0-beta.1 |
|
||||
| 2026-04-12 | V13 系统性功能审计数字校准:(1) Tauri 命令 184→191 (2) 前端 invoke 105→106 (3) @reserved 33→24 (Butler/MCP已接通) (4) 孤儿命令 ~46→~61 (5) Rust 测试 798→801 (433+368) (6) SaaS .route() 122→136 (7) Zustand Store 20→21 (8) dead_code 76→43 (9) Rust LOC crates ~74.6K→~77K |
|
||||
| 2026-04-15 | Heartbeat 统一健康系统:(1) Tauri 命令 182→183 (+health_snapshot) (2) intelligence 模块 15→16 文件 (+health_snapshot.rs +heartbeat.rs 重构) (3) React 组件 104→105 (+HealthPanel.tsx) (4) 前端 lib 85→76 (删除 intelligence-client/ 9 文件) |
|
||||
| 2026-04-16 | 发布前深度测试 8 路并行验证 + 3 项 P0 修复:(1) Tauri 命令 183→190 (2) 前端 invoke 95→104 (3) SaaS .route() 136→137 (4) 中间件 15→14 (实际 chain.register 计数) (5) P0-01 Admin ApiKeys 创建功能修复 (/keys→/tokens 路由对齐) (6) P0-02 账户锁定 unwrap_or(false)→正确错误传播 (7) P0-03 Logout 增加 access token cookie fallback 撤销 refresh token |
|
||||
|
||||
Reference in New Issue
Block a user