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
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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user