refactor(desktop): split kernel_commands/pipeline_commands into modules, add SaaS client libs and gateway modules
Split monolithic kernel_commands.rs (2185 lines) and pipeline_commands.rs (1391 lines) into focused sub-modules under kernel_commands/ and pipeline_commands/ directories. Add gateway module (commands, config, io, runtime), health_check, and 15 new TypeScript client libraries for SaaS relay, auth, admin, telemetry, and kernel sub-systems (a2a, agent, chat, hands, skills, triggers). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
153
desktop/src/lib/saas-session.ts
Normal file
153
desktop/src/lib/saas-session.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* SaaS Session Persistence
|
||||
*
|
||||
* Handles loading/saving SaaS auth session data.
|
||||
* Token is stored in secure storage (OS keyring), not plain localStorage.
|
||||
* Auth state is carried by HttpOnly cookies when possible (same-origin).
|
||||
*/
|
||||
|
||||
import type { SaaSAccountInfo } from './saas-types';
|
||||
import { createLogger } from './logger';
|
||||
|
||||
const logger = createLogger('saas-session');
|
||||
|
||||
// === Storage Keys ===
|
||||
const SAAS_TOKEN_SECURE_KEY = 'zclaw-saas-token'; // OS keyring key
|
||||
const SAASTOKEN_KEY = 'zclaw-saas-token'; // legacy localStorage — only used for cleanup
|
||||
const SAASURL_KEY = 'zclaw-saas-url';
|
||||
const SAASACCOUNT_KEY = 'zclaw-saas-account';
|
||||
const SAASMODE_KEY = 'zclaw-connection-mode';
|
||||
|
||||
// === Session Interface ===
|
||||
|
||||
export interface SaaSSession {
|
||||
token: string | null; // null when using cookie-based auth (page reload)
|
||||
account: SaaSAccountInfo | null;
|
||||
saasUrl: string;
|
||||
}
|
||||
|
||||
// === Session Functions ===
|
||||
|
||||
/**
|
||||
* Load a persisted SaaS session.
|
||||
* Token is stored in secure storage (OS keyring), not plain localStorage.
|
||||
* Returns null if no URL is stored (never logged in).
|
||||
*
|
||||
* NOTE: Token loading is async due to secure storage access.
|
||||
* For synchronous checks, use loadSaaSSessionSync() (URL + account only).
|
||||
*/
|
||||
export async function loadSaaSSession(): Promise<SaaSSession | null> {
|
||||
try {
|
||||
const saasUrl = localStorage.getItem(SAASURL_KEY);
|
||||
if (!saasUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Clean up any legacy plaintext token from localStorage
|
||||
const legacyToken = localStorage.getItem(SAASTOKEN_KEY);
|
||||
if (legacyToken) {
|
||||
localStorage.removeItem(SAASTOKEN_KEY);
|
||||
}
|
||||
|
||||
// Load token from secure storage
|
||||
let token: string | null = null;
|
||||
try {
|
||||
const { secureStorage } = await import('./secure-storage');
|
||||
token = await secureStorage.get(SAAS_TOKEN_SECURE_KEY);
|
||||
} catch (e) {
|
||||
logger.debug('Secure storage unavailable for token load', { error: e });
|
||||
// Secure storage unavailable — token stays null (cookie auth will be attempted)
|
||||
}
|
||||
|
||||
const accountRaw = localStorage.getItem(SAASACCOUNT_KEY);
|
||||
const account: SaaSAccountInfo | null = accountRaw
|
||||
? (JSON.parse(accountRaw) as SaaSAccountInfo)
|
||||
: null;
|
||||
|
||||
return { token, account, saasUrl };
|
||||
} catch (e) {
|
||||
logger.debug('Corrupted session data, clearing', { error: e });
|
||||
// Corrupted data - clear all
|
||||
clearSaaSSession();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronous version — returns URL + account only (no token).
|
||||
* Used during store initialization where async is not available.
|
||||
*/
|
||||
export function loadSaaSSessionSync(): { saasUrl: string; account: SaaSAccountInfo | null } | null {
|
||||
try {
|
||||
const saasUrl = localStorage.getItem(SAASURL_KEY);
|
||||
if (!saasUrl) return null;
|
||||
|
||||
// Clean up legacy plaintext token
|
||||
const legacyToken = localStorage.getItem(SAASTOKEN_KEY);
|
||||
if (legacyToken) {
|
||||
localStorage.removeItem(SAASTOKEN_KEY);
|
||||
}
|
||||
|
||||
const accountRaw = localStorage.getItem(SAASACCOUNT_KEY);
|
||||
const account: SaaSAccountInfo | null = accountRaw
|
||||
? (JSON.parse(accountRaw) as SaaSAccountInfo)
|
||||
: null;
|
||||
|
||||
return { saasUrl, account };
|
||||
} catch (e) {
|
||||
logger.debug('Failed to load sync session', { error: e });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist SaaS session.
|
||||
* Token goes 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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem(SAASURL_KEY, session.saasUrl);
|
||||
if (session.account) {
|
||||
localStorage.setItem(SAASACCOUNT_KEY, JSON.stringify(session.account));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the persisted SaaS session from all storage.
|
||||
*/
|
||||
export async function clearSaaSSession(): Promise<void> {
|
||||
// Remove 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 }); }
|
||||
|
||||
localStorage.removeItem(SAASTOKEN_KEY);
|
||||
localStorage.removeItem(SAASURL_KEY);
|
||||
localStorage.removeItem(SAASACCOUNT_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist the connection mode to localStorage.
|
||||
*/
|
||||
export function saveConnectionMode(mode: string): void {
|
||||
localStorage.setItem(SAASMODE_KEY, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the connection mode from localStorage.
|
||||
* Returns null if not set.
|
||||
*/
|
||||
export function loadConnectionMode(): string | null {
|
||||
return localStorage.getItem(SAASMODE_KEY);
|
||||
}
|
||||
Reference in New Issue
Block a user