fix(auth): 5 BUG 修复 — refresh token 持久化 + 密码验证 + 浏览器兼容
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

BUG-1 (P1): LoginPage 注册密码验证从 6 位改为 8 位,与后端一致
BUG-2 (P0): refresh token 持久化到 OS keyring + restoreSession 三级恢复
  (access token → refresh token → cookie auth) + saveSaaSSession 改为 await
BUG-3 (P0): Tauri 聊天路由降级问题,根因同 BUG-2(会话恢复失败)
BUG-4 (P1): App.tsx 跳过 Onboarding 改用 agentStore(兼容所有 client),
  Workspace.tsx Tauri invoke 改为动态 import 避免浏览器崩溃
BUG-5: tauri.conf.json createUpdaterArtifacts 改为 boolean true
This commit is contained in:
iven
2026-04-11 09:43:17 +08:00
parent 1171218276
commit d871685e25
6 changed files with 113 additions and 54 deletions

View File

@@ -13,6 +13,7 @@ const logger = createLogger('saas-session');
// === Storage Keys ===
const SAAS_TOKEN_SECURE_KEY = 'zclaw-saas-token'; // OS keyring key
const SAAS_REFRESH_TOKEN_KEY = 'zclaw-saas-refresh-token'; // OS keyring key for refresh token
const SAASTOKEN_KEY = 'zclaw-saas-token'; // legacy localStorage — only used for cleanup
const SAASURL_KEY = 'zclaw-saas-url';
const SAASACCOUNT_KEY = 'zclaw-saas-account';
@@ -22,6 +23,7 @@ const SAASMODE_KEY = 'zclaw-connection-mode';
export interface SaaSSession {
token: string | null; // null when using cookie-based auth (page reload)
refreshToken: string | null; // for token refresh on restore
account: SaaSAccountInfo | null;
saasUrl: string;
}
@@ -51,9 +53,11 @@ export async function loadSaaSSession(): Promise<SaaSSession | null> {
// Load token from secure storage
let token: string | null = null;
let refreshToken: string | null = null;
try {
const { secureStorage } = await import('./secure-storage');
token = await secureStorage.get(SAAS_TOKEN_SECURE_KEY);
refreshToken = await secureStorage.get(SAAS_REFRESH_TOKEN_KEY);
} catch (e) {
logger.debug('Secure storage unavailable for token load', { error: e });
// Secure storage unavailable — token stays null (cookie auth will be attempted)
@@ -64,7 +68,7 @@ export async function loadSaaSSession(): Promise<SaaSSession | null> {
? (JSON.parse(accountRaw) as SaaSAccountInfo)
: null;
return { token, account, saasUrl };
return { token, refreshToken, account, saasUrl };
} catch (e) {
logger.debug('Corrupted session data, clearing', { error: e });
// Corrupted data - clear all
@@ -102,17 +106,26 @@ export function loadSaaSSessionSync(): { saasUrl: string; account: SaaSAccountIn
/**
* Persist SaaS session.
* Token goes to secure storage (OS keyring), metadata to localStorage.
* Access token + refresh token go to secure storage (OS keyring), metadata to localStorage.
*/
export async function saveSaaSSession(session: SaaSSession): Promise<void> {
// Store token in secure storage (OS keyring), not plain localStorage
// Store access token in secure storage (OS keyring)
if (session.token) {
try {
const { secureStorage } = await import('./secure-storage');
await secureStorage.set(SAAS_TOKEN_SECURE_KEY, session.token);
} catch (e) {
logger.debug('Secure storage unavailable for token save', { error: e });
// Secure storage unavailable — token only in memory
}
}
// Store refresh token in secure storage (OS keyring)
if (session.refreshToken) {
try {
const { secureStorage } = await import('./secure-storage');
await secureStorage.set(SAAS_REFRESH_TOKEN_KEY, session.refreshToken);
} catch (e) {
logger.debug('Secure storage unavailable for refresh token save', { error: e });
}
}
@@ -126,12 +139,18 @@ export async function saveSaaSSession(session: SaaSSession): Promise<void> {
* Clear the persisted SaaS session from all storage.
*/
export async function clearSaaSSession(): Promise<void> {
// Remove from secure storage
// Remove access token from secure storage
try {
const { secureStorage } = await import('./secure-storage');
await secureStorage.set(SAAS_TOKEN_SECURE_KEY, '');
} catch (e) { logger.debug('Failed to clear secure storage token', { error: e }); }
// Remove refresh token from secure storage
try {
const { secureStorage } = await import('./secure-storage');
await secureStorage.set(SAAS_REFRESH_TOKEN_KEY, '');
} catch (e) { logger.debug('Failed to clear secure storage refresh token', { error: e }); }
localStorage.removeItem(SAASTOKEN_KEY);
localStorage.removeItem(SAASURL_KEY);
localStorage.removeItem(SAASACCOUNT_KEY);