UNPKG

@clduab11/gemini-flow

Version:

Revolutionary AI agent swarm coordination platform with Google Services integration, multimedia processing, and production-ready monitoring. Features 8 Google AI services, quantum computing capabilities, and enterprise-grade security.

645 lines (565 loc) 16.9 kB
/** * Credential Storage Implementation * * Secure storage interface for authentication credentials with encryption, * multiple backend support, and comprehensive error handling */ import * as fs from "fs"; import * as path from "path"; import * as os from "os"; import * as crypto from "crypto"; import { EventEmitter } from "events"; import { Logger } from "../../utils/logger.js"; import { CredentialStorage, AuthCredentials, AuthError, StorageConfig, } from "../../types/auth.js"; /** * Storage entry with metadata */ interface StorageEntry { credentials: AuthCredentials; createdAt: number; expiresAt?: number; accessCount: number; lastAccessed: number; } /** * Encrypted storage format */ interface EncryptedEntry { iv: string; salt: string; data: string; tag: string; algorithm: string; } /** * In-memory credential storage implementation */ export class MemoryCredentialStorage extends EventEmitter implements CredentialStorage { private storage = new Map<string, StorageEntry>(); private logger: Logger; private config: StorageConfig; private cleanupInterval?: ReturnType<typeof setInterval>; constructor(config: Partial<StorageConfig> = {}) { super(); this.config = { type: "memory", maxEntries: 1000, ttl: 24 * 60 * 60 * 1000, // 24 hours default ...config, }; this.logger = new Logger("MemoryCredentialStorage"); // Start cleanup interval for expired entries this.startCleanupInterval(); this.logger.debug("Memory credential storage initialized", { maxEntries: this.config.maxEntries, ttl: this.config.ttl, }); } async store(key: string, credentials: AuthCredentials): Promise<void> { try { this.validateKey(key); this.validateCredentials(credentials); // Check storage limits if (this.storage.size >= (this.config.maxEntries || 1000)) { await this.evictOldestEntry(); } const entry: StorageEntry = { credentials: { ...credentials }, // Deep copy to prevent mutations createdAt: Date.now(), expiresAt: this.config.ttl ? Date.now() + this.config.ttl : undefined, accessCount: 0, lastAccessed: Date.now(), }; this.storage.set(key, entry); this.logger.debug("Credentials stored in memory", { key: this.maskKey(key), provider: credentials.provider, type: credentials.type, }); this.emit("stored", { key, credentials }); } catch (error) { this.logger.error("Failed to store credentials", { key: this.maskKey(key), error, }); throw this.createStorageError( "STORE_FAILED", "Failed to store credentials", error as Error, ); } } async retrieve(key: string): Promise<AuthCredentials | null> { try { this.validateKey(key); const entry = this.storage.get(key); if (!entry) { return null; } // Check expiration if (entry.expiresAt && Date.now() > entry.expiresAt) { await this.delete(key); return null; } // Update access tracking entry.accessCount++; entry.lastAccessed = Date.now(); this.logger.debug("Credentials retrieved from memory", { key: this.maskKey(key), provider: entry.credentials.provider, accessCount: entry.accessCount, }); this.emit("retrieved", { key, credentials: entry.credentials }); return { ...entry.credentials }; // Return copy to prevent mutations } catch (error) { this.logger.error("Failed to retrieve credentials", { key: this.maskKey(key), error, }); throw this.createStorageError( "RETRIEVE_FAILED", "Failed to retrieve credentials", error as Error, ); } } async delete(key: string): Promise<void> { try { this.validateKey(key); const deleted = this.storage.delete(key); if (deleted) { this.logger.debug("Credentials deleted from memory", { key: this.maskKey(key), }); this.emit("deleted", { key }); } } catch (error) { this.logger.error("Failed to delete credentials", { key: this.maskKey(key), error, }); throw this.createStorageError( "DELETE_FAILED", "Failed to delete credentials", error as Error, ); } } async list(): Promise<string[]> { try { const keys = Array.from(this.storage.keys()); this.logger.debug("Listed credential keys", { count: keys.length }); return keys; } catch (error) { this.logger.error("Failed to list credentials", { error }); throw this.createStorageError( "LIST_FAILED", "Failed to list credentials", error as Error, ); } } async clear(): Promise<void> { try { const count = this.storage.size; this.storage.clear(); this.logger.info("All credentials cleared from memory", { count }); this.emit("cleared", { count }); } catch (error) { this.logger.error("Failed to clear credentials", { error }); throw this.createStorageError( "CLEAR_FAILED", "Failed to clear credentials", error as Error, ); } } async exists(key: string): Promise<boolean> { try { this.validateKey(key); return this.storage.has(key); } catch (error) { this.logger.error("Failed to check credential existence", { key: this.maskKey(key), error, }); return false; } } /** * Get storage statistics */ getStats() { const entries = Array.from(this.storage.values()); const now = Date.now(); return { totalEntries: this.storage.size, expiredEntries: entries.filter((e) => e.expiresAt && e.expiresAt <= now) .length, totalAccesses: entries.reduce((sum, e) => sum + e.accessCount, 0), avgAccessCount: entries.length > 0 ? entries.reduce((sum, e) => sum + e.accessCount, 0) / entries.length : 0, oldestEntry: entries.length > 0 ? Math.min(...entries.map((e) => e.createdAt)) : null, newestEntry: entries.length > 0 ? Math.max(...entries.map((e) => e.createdAt)) : null, }; } /** * Cleanup expired entries */ private async cleanupExpiredEntries(): Promise<number> { const now = Date.now(); let cleanedCount = 0; for (const [key, entry] of this.storage.entries()) { if (entry.expiresAt && entry.expiresAt <= now) { this.storage.delete(key); cleanedCount++; } } if (cleanedCount > 0) { this.logger.debug("Cleaned up expired entries", { count: cleanedCount }); this.emit("cleanup", { expiredEntries: cleanedCount }); } return cleanedCount; } /** * Evict oldest entry to make room */ private async evictOldestEntry(): Promise<void> { let oldestKey: string | null = null; let oldestTime = Date.now(); for (const [key, entry] of this.storage.entries()) { if (entry.lastAccessed < oldestTime) { oldestTime = entry.lastAccessed; oldestKey = key; } } if (oldestKey) { await this.delete(oldestKey); this.logger.debug("Evicted oldest entry", { key: this.maskKey(oldestKey), }); } } /** * Start periodic cleanup of expired entries */ private startCleanupInterval(): void { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); } // Cleanup every 5 minutes this.cleanupInterval = setInterval( () => { this.cleanupExpiredEntries().catch((error) => { this.logger.error("Cleanup interval error", { error }); }); }, 5 * 60 * 1000, ); } /** * Stop cleanup interval */ public destroy(): void { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = undefined; } this.storage.clear(); this.logger.debug("Memory credential storage destroyed"); } private validateKey(key: string): void { if (!key || typeof key !== "string" || key.trim() === "") { throw new Error("Invalid storage key"); } } private validateCredentials(credentials: AuthCredentials): void { if (!credentials || typeof credentials !== "object") { throw new Error("Invalid credentials object"); } if (!credentials.type || !credentials.provider) { throw new Error("Credentials missing required fields: type and provider"); } } private maskKey(key: string): string { if (key.length <= 8) return "***"; return key.substring(0, 4) + "***" + key.substring(key.length - 4); } private createStorageError( code: string, message: string, originalError?: Error, ): AuthError { const error = new Error(message) as AuthError; error.code = code; error.type = "configuration"; error.retryable = false; error.originalError = originalError; error.context = { storageType: this.config.type, timestamp: Date.now(), }; return error; } } /** * File-based credential storage implementation */ export class FileCredentialStorage extends EventEmitter implements CredentialStorage { private basePath: string; private logger: Logger; private config: StorageConfig; constructor(config: Partial<StorageConfig> = {}) { super(); this.config = { type: "file", basePath: path.join(os.homedir(), ".gemini-flow", "credentials"), ...config, }; this.basePath = this.config.basePath!; this.logger = new Logger("FileCredentialStorage"); this.ensureStorageDirectory(); this.logger.debug("File credential storage initialized", { basePath: this.basePath, }); } async store(key: string, credentials: AuthCredentials): Promise<void> { try { this.validateKey(key); this.validateCredentials(credentials); const entry: StorageEntry = { credentials, createdAt: Date.now(), expiresAt: this.config.ttl ? Date.now() + this.config.ttl : undefined, accessCount: 0, lastAccessed: Date.now(), }; const filePath = this.getFilePath(key); const fileContent = JSON.stringify(entry, null, 2); await fs.promises.writeFile(filePath, fileContent, { mode: 0o600 }); // Secure permissions this.logger.debug("Credentials stored to file", { key: this.maskKey(key), filePath, provider: credentials.provider, }); this.emit("stored", { key, credentials }); } catch (error) { this.logger.error("Failed to store credentials to file", { key: this.maskKey(key), error, }); throw this.createStorageError( "STORE_FAILED", "Failed to store credentials to file", error as Error, ); } } async retrieve(key: string): Promise<AuthCredentials | null> { try { this.validateKey(key); const filePath = this.getFilePath(key); if (!(await this.fileExists(filePath))) { return null; } const fileContent = await fs.promises.readFile(filePath, "utf8"); const entry: StorageEntry = JSON.parse(fileContent); // Check expiration if (entry.expiresAt && Date.now() > entry.expiresAt) { await this.delete(key); return null; } // Update access tracking entry.accessCount++; entry.lastAccessed = Date.now(); await fs.promises.writeFile(filePath, JSON.stringify(entry, null, 2), { mode: 0o600, }); this.logger.debug("Credentials retrieved from file", { key: this.maskKey(key), filePath, provider: entry.credentials.provider, }); this.emit("retrieved", { key, credentials: entry.credentials }); return entry.credentials; } catch (error) { this.logger.error("Failed to retrieve credentials from file", { key: this.maskKey(key), error, }); throw this.createStorageError( "RETRIEVE_FAILED", "Failed to retrieve credentials from file", error as Error, ); } } async delete(key: string): Promise<void> { try { this.validateKey(key); const filePath = this.getFilePath(key); if (await this.fileExists(filePath)) { await fs.promises.unlink(filePath); this.logger.debug("Credentials file deleted", { key: this.maskKey(key), filePath, }); this.emit("deleted", { key }); } } catch (error) { this.logger.error("Failed to delete credentials file", { key: this.maskKey(key), error, }); throw this.createStorageError( "DELETE_FAILED", "Failed to delete credentials file", error as Error, ); } } async list(): Promise<string[]> { try { this.ensureStorageDirectory(); const files = await fs.promises.readdir(this.basePath); const keys = files .filter((file) => file.endsWith(".json")) .map((file) => file.replace(".json", "")); this.logger.debug("Listed credential files", { count: keys.length }); return keys; } catch (error) { this.logger.error("Failed to list credential files", { error }); throw this.createStorageError( "LIST_FAILED", "Failed to list credential files", error as Error, ); } } async clear(): Promise<void> { try { const keys = await this.list(); let count = 0; for (const key of keys) { try { await this.delete(key); count++; } catch (error) { this.logger.warn("Failed to delete credential file during clear", { key, error, }); } } this.logger.info("All credential files cleared", { count }); this.emit("cleared", { count }); } catch (error) { this.logger.error("Failed to clear credential files", { error }); throw this.createStorageError( "CLEAR_FAILED", "Failed to clear credential files", error as Error, ); } } async exists(key: string): Promise<boolean> { try { this.validateKey(key); const filePath = this.getFilePath(key); return await this.fileExists(filePath); } catch (error) { this.logger.error("Failed to check credential file existence", { key: this.maskKey(key), error, }); return false; } } private ensureStorageDirectory(): void { if (!fs.existsSync(this.basePath)) { fs.mkdirSync(this.basePath, { recursive: true, mode: 0o700 }); // Secure directory permissions } } private getFilePath(key: string): string { // Sanitize key for filesystem const sanitizedKey = key.replace(/[^a-zA-Z0-9_-]/g, "_"); return path.join(this.basePath, `${sanitizedKey}.json`); } private async fileExists(filePath: string): Promise<boolean> { try { await fs.promises.access(filePath); return true; } catch { return false; } } private validateKey(key: string): void { if (!key || typeof key !== "string" || key.trim() === "") { throw new Error("Invalid storage key"); } } private validateCredentials(credentials: AuthCredentials): void { if (!credentials || typeof credentials !== "object") { throw new Error("Invalid credentials object"); } if (!credentials.type || !credentials.provider) { throw new Error("Credentials missing required fields: type and provider"); } } private maskKey(key: string): string { if (key.length <= 8) return "***"; return key.substring(0, 4) + "***" + key.substring(key.length - 4); } private createStorageError( code: string, message: string, originalError?: Error, ): AuthError { const error = new Error(message) as AuthError; error.code = code; error.type = "configuration"; error.retryable = false; error.originalError = originalError; error.context = { storageType: this.config.type, basePath: this.basePath, timestamp: Date.now(), }; return error; } } /** * Factory function to create credential storage instances */ export function createCredentialStorage( config: StorageConfig, ): CredentialStorage { switch (config.type) { case "memory": return new MemoryCredentialStorage(config); case "file": case "encrypted-file": // For now, treat encrypted-file same as file return new FileCredentialStorage(config); default: throw new Error(`Unsupported storage type: ${config.type}`); } }