fix(audit): P1 心跳自启动 + refreshToken body + 类型修复
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

审计修复 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 匹配模式已正确
This commit is contained in:
iven
2026-04-04 18:26:10 +08:00
parent 05762261be
commit 6d1f2d108a
5 changed files with 37 additions and 10 deletions

View File

@@ -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<T>(method: string, path: string, body?: unknown): Promise<T> }, username: string, password: string, totpCode?: string): Promise<SaaSLoginResponse> {
proto.login = async function (this: { token: string | null; refreshTokenValue: string | null; request<T>(method: string, path: string, body?: unknown): Promise<T> }, username: string, password: string, totpCode?: string): Promise<SaaSLoginResponse> {
const body: Record<string, string> = { 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<SaaSLoginResponse>(
'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<T>(method: string, path: string, body?: unknown): Promise<T> }, data: {
proto.register = async function (this: { token: string | null; refreshTokenValue: string | null; request<T>(method: string, path: string, body?: unknown): Promise<T> }, 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<T>(method: string, path: string, body?: unknown): Promise<T> }): Promise<string> {
const data = await this.request<SaaSRefreshResponse>('POST', '/api/v1/auth/refresh');
proto.refreshToken = async function (this: { token: string | null; refreshTokenValue: string | null; request<T>(method: string, path: string, body?: unknown): Promise<T> }): Promise<string> {
if (!this.refreshTokenValue) {
throw new Error('No refresh token available');
}
const data = await this.request<SaaSRefreshResponse>('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;
};

View File

@@ -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;

View File

@@ -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 */