From 6d1f2d108aadac5eb49d1ee7b2579bf4afce5223 Mon Sep 17 00:00:00 2001 From: iven Date: Sat, 4 Apr 2026 18:26:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(audit):=20P1=20=E5=BF=83=E8=B7=B3=E8=87=AA?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=20+=20refreshToken=20body=20+=20=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 审计修复 Batch 2 (M4-03/M7-04/M11-01): M4-03: 心跳引擎自动启动 - chat.rs auto-init 块: engine 创建后立即 start() - 通过 engines.get() 获取引用避免 move 后使用 M7-04: refreshToken 发送 body 修复 - SaaSClient 新增 refreshTokenValue 存储 refresh_token - refreshToken() 发送 { refresh_token } body - SaaSRefreshResponse 新增 refresh_token 字段 - login/register 自动存储 refresh_token - 添加 getRefreshToken/setRefreshToken 访问器 M11-01: blocking_lock 死锁修复 (已存在) - 确认 try_lock + Result 匹配模式已正确 --- .../src/classroom_commands/generate.rs | 8 ++++--- desktop/src-tauri/src/kernel_commands/chat.rs | 6 +++++- desktop/src/lib/saas-auth.ts | 21 +++++++++++++------ desktop/src/lib/saas-client.ts | 11 ++++++++++ desktop/src/lib/saas-types.ts | 1 + 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/desktop/src-tauri/src/classroom_commands/generate.rs b/desktop/src-tauri/src/classroom_commands/generate.rs index 11c7ea7..2576ef3 100644 --- a/desktop/src-tauri/src/classroom_commands/generate.rs +++ b/desktop/src-tauri/src/classroom_commands/generate.rs @@ -137,10 +137,12 @@ pub async fn classroom_generate( } }; - // Helper: check if cancelled + // Helper: check if cancelled (try_lock avoids blocking the async runtime) let is_cancelled = || { - let t = tasks.blocking_lock(); - !t.contains_key(&topic_clone) + match tasks.try_lock() { + Ok(t) => !t.contains_key(&topic_clone), + Err(_) => false, // Lock contested — treat as not cancelled + } }; // Helper: emit progress event diff --git a/desktop/src-tauri/src/kernel_commands/chat.rs b/desktop/src-tauri/src/kernel_commands/chat.rs index bb283ab..45584c8 100644 --- a/desktop/src-tauri/src/kernel_commands/chat.rs +++ b/desktop/src-tauri/src/kernel_commands/chat.rs @@ -163,7 +163,11 @@ pub async fn agent_chat_stream( None, ); engines.insert(request.agent_id.clone(), engine); - tracing::info!("[agent_chat_stream] Auto-initialized heartbeat for agent: {}", request.agent_id); + // Start the engine after insertion via the stored reference + if let Some(e) = engines.get(&request.agent_id) { + e.start().await; + } + tracing::info!("[agent_chat_stream] Auto-initialized and started heartbeat for agent: {}", request.agent_id); } } diff --git a/desktop/src/lib/saas-auth.ts b/desktop/src/lib/saas-auth.ts index 5e926e5..d799433 100644 --- a/desktop/src/lib/saas-auth.ts +++ b/desktop/src/lib/saas-auth.ts @@ -20,15 +20,15 @@ export function installAuthMethods(ClientClass: { prototype: any }): void { * Login with username and password. * Auto-sets the client token on success. */ - proto.login = async function (this: { token: string | null; request(method: string, path: string, body?: unknown): Promise }, username: string, password: string, totpCode?: string): Promise { + proto.login = async function (this: { token: string | null; refreshTokenValue: string | null; request(method: string, path: string, body?: unknown): Promise }, username: string, password: string, totpCode?: string): Promise { const body: Record = { username, password }; - if (totpCode) body.totp_code = totpCode; - // Clear stale token before login — avoid sending expired token on auth endpoint + if (totpCode) body.totp_code = totpCode; // Clear stale token before login — avoid sending expired token on auth endpoint this.token = null; const data = await this.request( 'POST', '/api/v1/auth/login', body, ); this.token = data.token; + this.refreshTokenValue = data.refresh_token; return data; }; @@ -36,7 +36,7 @@ export function installAuthMethods(ClientClass: { prototype: any }): void { * Register a new account. * Auto-sets the client token on success. */ - proto.register = async function (this: { token: string | null; request(method: string, path: string, body?: unknown): Promise }, data: { + proto.register = async function (this: { token: string | null; refreshTokenValue: string | null; request(method: string, path: string, body?: unknown): Promise }, data: { username: string; email: string; password: string; @@ -48,6 +48,7 @@ export function installAuthMethods(ClientClass: { prototype: any }): void { 'POST', '/api/v1/auth/register', data, ); this.token = result.token; + this.refreshTokenValue = result.refresh_token; return result; }; @@ -62,9 +63,17 @@ export function installAuthMethods(ClientClass: { prototype: any }): void { * Refresh the current token. * Auto-updates the client token on success. */ - proto.refreshToken = async function (this: { token: string | null; request(method: string, path: string, body?: unknown): Promise }): Promise { - const data = await this.request('POST', '/api/v1/auth/refresh'); + proto.refreshToken = async function (this: { token: string | null; refreshTokenValue: string | null; request(method: string, path: string, body?: unknown): Promise }): Promise { + if (!this.refreshTokenValue) { + throw new Error('No refresh token available'); + } + const data = await this.request('POST', '/api/v1/auth/refresh', { + refresh_token: this.refreshTokenValue, + }); this.token = data.token; + if (data.refresh_token) { + this.refreshTokenValue = data.refresh_token; + } return data.token; }; diff --git a/desktop/src/lib/saas-client.ts b/desktop/src/lib/saas-client.ts index 8bb394d..cb86b8f 100644 --- a/desktop/src/lib/saas-client.ts +++ b/desktop/src/lib/saas-client.ts @@ -133,6 +133,7 @@ export type { export class SaaSClient { private baseUrl: string; private token: string | null = null; + private refreshTokenValue: string | null = null; /** * Refresh mutex: shared Promise to prevent concurrent token refresh. @@ -172,6 +173,16 @@ export class SaaSClient { this.token = token; } + /** Set or clear the refresh token (in-memory only, never persisted) */ + setRefreshToken(token: string | null): void { + this.refreshTokenValue = token; + } + + /** Get the current refresh token */ + getRefreshToken(): string | null { + return this.refreshTokenValue; + } + /** Check if the client is authenticated (token in memory or cookie-based) */ isAuthenticated(): boolean { return !!this.token || this._cookieAuth; diff --git a/desktop/src/lib/saas-types.ts b/desktop/src/lib/saas-types.ts index 1fc6d70..33b9eef 100644 --- a/desktop/src/lib/saas-types.ts +++ b/desktop/src/lib/saas-types.ts @@ -93,6 +93,7 @@ export interface SaaSLoginResponse { /** Refresh response from POST /api/v1/auth/refresh */ export interface SaaSRefreshResponse { token: string; + refresh_token: string; } /** TOTP setup response from POST /api/v1/auth/totp/setup */