Files
zclaw_openfang/desktop/src/lib/saas-auth.ts
iven 6d1f2d108a
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
fix(audit): P1 心跳自启动 + refreshToken body + 类型修复
审计修复 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 匹配模式已正确
2026-04-04 18:26:10 +08:00

107 lines
4.0 KiB
TypeScript

/**
* SaaS Auth Methods — Mixin
*
* Installs authentication-related methods onto SaaSClient.prototype.
* Uses the same mixin pattern as gateway-api.ts.
*/
import type {
SaaSAccountInfo,
SaaSLoginResponse,
SaaSRefreshResponse,
TotpSetupResponse,
TotpResultResponse,
} from './saas-types';
export function installAuthMethods(ClientClass: { prototype: any }): void {
const proto = ClientClass.prototype;
/**
* Login with username and password.
* Auto-sets the client token on success.
*/
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
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;
};
/**
* Register a new account.
* Auto-sets the client token on success.
*/
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;
display_name?: string;
}): Promise<SaaSLoginResponse> {
// Clear stale token before register
this.token = null;
const result = await this.request<SaaSLoginResponse>(
'POST', '/api/v1/auth/register', data,
);
this.token = result.token;
this.refreshTokenValue = result.refresh_token;
return result;
};
/**
* Get the current authenticated user's account info.
*/
proto.me = async function (this: { request<T>(method: string, path: string, body?: unknown): Promise<T> }): Promise<SaaSAccountInfo> {
return this.request<SaaSAccountInfo>('GET', '/api/v1/auth/me');
};
/**
* Refresh the current token.
* Auto-updates the client token on success.
*/
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;
};
/**
* Change the current user's password.
*/
proto.changePassword = async function (this: { request<T>(method: string, path: string, body?: unknown): Promise<T> }, oldPassword: string, newPassword: string): Promise<void> {
await this.request<unknown>('PUT', '/api/v1/auth/password', {
old_password: oldPassword,
new_password: newPassword,
});
};
// --- TOTP Endpoints ---
/** Generate a TOTP secret and otpauth URI */
proto.setupTotp = async function (this: { request<T>(method: string, path: string, body?: unknown): Promise<T> }): Promise<TotpSetupResponse> {
return this.request<TotpSetupResponse>('POST', '/api/v1/auth/totp/setup');
};
/** Verify a TOTP code and enable 2FA */
proto.verifyTotp = async function (this: { request<T>(method: string, path: string, body?: unknown): Promise<T> }, code: string): Promise<TotpResultResponse> {
return this.request<TotpResultResponse>('POST', '/api/v1/auth/totp/verify', { code });
};
/** Disable 2FA (requires password confirmation) */
proto.disableTotp = async function (this: { request<T>(method: string, path: string, body?: unknown): Promise<T> }, password: string): Promise<TotpResultResponse> {
return this.request<TotpResultResponse>('POST', '/api/v1/auth/totp/disable', { password });
};
}