From 757ca497d74363137850235f851ee3de94595853 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Mon, 22 Dec 2025 16:33:05 +0530 Subject: [PATCH 01/37] init files --- .../actions/externalSecrets/cacheService.ts | 206 +++++++++++++++++ .../encryptedStorageService.ts | 134 ++++++++++++ .../externalSecrets/externalSecretsManager.ts | 7 + .../awsSecretManagerProvider.ts | 111 ++++++++++ .../providerService/providerService.ts | 207 ++++++++++++++++++ .../externalSecrets/providerService/types.ts | 31 +++ 6 files changed, 696 insertions(+) create mode 100644 src/main/actions/externalSecrets/cacheService.ts create mode 100644 src/main/actions/externalSecrets/encryptedStorageService.ts create mode 100644 src/main/actions/externalSecrets/externalSecretsManager.ts create mode 100644 src/main/actions/externalSecrets/providerService/awsSecretManagerProvider.ts create mode 100644 src/main/actions/externalSecrets/providerService/providerService.ts create mode 100644 src/main/actions/externalSecrets/providerService/types.ts diff --git a/src/main/actions/externalSecrets/cacheService.ts b/src/main/actions/externalSecrets/cacheService.ts new file mode 100644 index 00000000..db87ec96 --- /dev/null +++ b/src/main/actions/externalSecrets/cacheService.ts @@ -0,0 +1,206 @@ +import { SecretProviderType } from "./providerService/types"; + +/** + * Cached secret entry + */ +interface CachedSecret { + value: string; + providerId: string; + providerType: SecretProviderType; + secretName: string; + fetchedAt: number; + expiresAt: number; +} + +/** + * TTL configuration per provider type + */ +const DEFAULT_TTL: Record = { + [SecretProviderType.AWS_SECRETS_MANGER]: 60 * 60 * 1000, // 60 minutes +}; + +// Only 1 provider active at a time, so no need for complex strategies +// name to value is enough + +/** + * In-memory cache for secrets + * - Session-scoped (cleared on app restart) + * - TTL-based expiration + * - Provider-aware invalidation + */ +export class CacheService { + private cache: Map = new Map(); + + get(secretName: string): string | null { + const entry = this.cache.get(secretName); + + if (!entry) { + // provider.fetchSecret(secretName); + // + + } + + // Check if expired + if (Date.now() > entry.expiresAt) { + this.cache.delete(secretName); + return null; + } + + return entry.value; + } + + /** + * Set a cached secret + */ + set( + providerId: string, + providerType: SecretProviderType, + secretName: string, + value: string, + customTTL?: number + ): void { + const key = this.getCacheKey(providerId, secretName); + const ttl = customTTL ?? DEFAULT_TTL[providerType]; + const now = Date.now(); + + this.cache.set(key, { + value, + providerId, + providerType, + secretName, + fetchedAt: now, + expiresAt: ttl === Infinity ? Infinity : now + ttl, + }); + } + + /** + * Invalidate a specific secret + */ + invalidate(providerId: string, secretName: string): boolean { + const key = this.getCacheKey(providerId, secretName); + return this.cache.delete(key); + } + + /** + * Invalidate all secrets from a specific provider + */ + invalidateByProvider(providerId: string): number { + let count = 0; + + for (const [key, entry] of this.cache.entries()) { + if (entry.providerId === providerId) { + this.cache.delete(key); + count++; + } + } + + return count; + } + + /** + * Invalidate all secrets of a specific provider type + */ + invalidateByProviderType(providerType: SecretProviderType): number { + let count = 0; + + for (const [key, entry] of this.cache.entries()) { + if (entry.providerType === providerType) { + this.cache.delete(key); + count++; + } + } + + return count; + } + + /** + * Clear all cached secrets + */ + clear(): void { + this.cache.clear(); + this.stats.hits = 0; + this.stats.misses = 0; + } + + /** + * Get cache statistics + */ + getStats(): CacheStats { + const total = this.stats.hits + this.stats.misses; + + return { + size: this.cache.size, + hits: this.stats.hits, + misses: this.stats.misses, + hitRate: total > 0 ? this.stats.hits / total : 0, + }; + } + + /** + * Clean up expired entries + * Should be called periodically + */ + cleanExpired(): number { + let count = 0; + const now = Date.now(); + + for (const [key, entry] of this.cache.entries()) { + if (entry.expiresAt !== Infinity && now > entry.expiresAt) { + this.cache.delete(key); + count++; + } + } + + return count; + } + + /** + * Get all cached entries (for debugging) + */ + getAllEntries(): Array<{ key: string; entry: CachedSecret }> { + return Array.from(this.cache.entries()).map(([key, entry]) => ({ + key, + entry, + })); + } + + /** + * Check if a specific secret is cached and valid + */ + has(providerId: string, secretName: string): boolean { + const key = this.getCacheKey(providerId, secretName); + const entry = this.cache.get(key); + + if (!entry) { + return false; + } + + // Check if expired + if (entry.expiresAt !== Infinity && Date.now() > entry.expiresAt) { + this.cache.delete(key); + return false; + } + + return true; + } + + /** + * Get time until a cached secret expires (in ms) + * Returns null if not cached or already expired + */ + getTimeToExpiry(providerId: string, secretName: string): number | null { + const key = this.getCacheKey(providerId, secretName); + const entry = this.cache.get(key); + + if (!entry) { + return null; + } + + if (entry.expiresAt === Infinity) { + return Infinity; + } + + const remaining = entry.expiresAt - Date.now(); + return remaining > 0 ? remaining : null; + } +} diff --git a/src/main/actions/externalSecrets/encryptedStorageService.ts b/src/main/actions/externalSecrets/encryptedStorageService.ts new file mode 100644 index 00000000..8c2bcedc --- /dev/null +++ b/src/main/actions/externalSecrets/encryptedStorageService.ts @@ -0,0 +1,134 @@ +// main/services/vault/EncryptedStorage.ts + +import { safeStorage } from "electron"; +import * as fs from "fs/promises"; +import * as path from "path"; +import { + SecretProviderConfig, + SecretProviderType, +} from "./providerService/types"; +import { GLOBAL_CONFIG_FOLDER_PATH } from "renderer/actions/local-sync/constants"; + +/** + * Stored provider configuration + */ +interface StoredProviderConfig { + type: SecretProviderType; + name: string; + config: SecretProviderConfig; + createdAt: number; + updatedAt: number; +} + +/** + * EncryptedStorage using Electron's safeStorage API + * + * All encryption is handled by safeStorage (OS-level): + * - macOS: Keychain + * - Windows: DPAPI + * - Linux: libsecret + * + * No need for separate vault key or CryptoService! + */ +export class EncryptedStorage { + private readonly basePath: string; + + private readonly providersPath: string; + + constructor(baseFolder: string) { + this.basePath = path.join(GLOBAL_CONFIG_FOLDER_PATH); + this.providersPath = path.join(this.basePath, baseFolder); + } + + async initialize(): Promise { + if (!safeStorage.isEncryptionAvailable()) { + throw new Error( + "Encryption is not available on this system. " + + "This may happen on Linux if libsecret is not installed." + ); + } + + await fs.mkdir(this.providersPath, { recursive: true }); + } + + async save( + data: Record, + fileName: string, + keysToEncrypt: string[] = [] + ): Promise { + const stored: any = { + ...data, + updatedAt: Date.now(), + }; + + // 2. Encrypt sensitive keys within the config + for (const key of keysToEncrypt) { + if (stored[key] !== undefined) { + const sensitiveValue = JSON.stringify(stored[key]); + const encrypted = safeStorage.encryptString(sensitiveValue); + stored[key] = encrypted.toString("base64"); + } + } + + const json = JSON.stringify(stored); + + const filePath = this.getProviderFilePath(fileName); + const tempFilePath = `${filePath}.tmp`; + await fs.writeFile(tempFilePath, json); + await fs.rename(tempFilePath, filePath); + } + + async load( + fileName: string, + keysToDecrypt: string[] = [] + ): Promise { + const filePath = this.getProviderFilePath(fileName); + + try { + const encrypted = await fs.readFile(filePath); + + const stored: any = JSON.parse(encrypted.toString()); + + // Decrypt sensitive keys within the config + for (const key of keysToDecrypt) { + if (stored[key] !== undefined) { + const encryptedBuffer = Buffer.from(stored[key], "base64"); + const decrypted = safeStorage.decryptString(encryptedBuffer); + stored[key] = JSON.parse(decrypted); + } + } + + return stored as StoredProviderConfig; + } catch (error: any) { + // TODO: check error handling + if (error.code === "ENOENT") { + return null; // File doesn't exist + } + throw error; + } + } + + /** + * Delete provider configuration + */ + async delete(fileName: string): Promise { + const filePath = this.getProviderFilePath(fileName); + + try { + await fs.unlink(filePath); + } catch (error) { + // TODO: check error handling + if (error.code !== "ENOENT") { + throw error; + } + // Ignore if file doesn't exist + } + } + + /** + * Get file path for a provider + */ + private getProviderFilePath(fileName: string): string { + return path.join(this.providersPath, `${fileName}.enc`); + } +} diff --git a/src/main/actions/externalSecrets/externalSecretsManager.ts b/src/main/actions/externalSecrets/externalSecretsManager.ts new file mode 100644 index 00000000..2818f25d --- /dev/null +++ b/src/main/actions/externalSecrets/externalSecretsManager.ts @@ -0,0 +1,7 @@ +class ExternalSecretsManager { + private isItialized=false; + + resolveSecret(variableName: string): string | null { + + } +} \ No newline at end of file diff --git a/src/main/actions/externalSecrets/providerService/awsSecretManagerProvider.ts b/src/main/actions/externalSecrets/providerService/awsSecretManagerProvider.ts new file mode 100644 index 00000000..61ef5e75 --- /dev/null +++ b/src/main/actions/externalSecrets/providerService/awsSecretManagerProvider.ts @@ -0,0 +1,111 @@ +import { + SecretsManagerClient, + GetSecretValueCommand, + ListSecretsCommand, +} from "@aws-sdk/client-secrets-manager"; +import { + AWSSecretsManagerConfig, + ISecretProvider, + SecretProviderConfig, + SecretProviderMetadata, + SecretProviderType, +} from "./types"; + +export class AWSSecretsManagerProvider implements ISecretProvider { + readonly type = SecretProviderType.AWS_SECRETS_MANGER; + + // Provider instance holds its own identity + constructor(public readonly id: string) {} + + private config: AWSSecretsManagerConfig | null = null; + private client: SecretsManagerClient | null = null; + + async configure(config: AWSSecretsManagerConfig): Promise { + // Validate + this.validateConfig(config); + + // Create AWS client + this.client = new SecretsManagerClient({ + region: config.region, + credentials: { + accessKeyId: config.accessKeyId, + secretAccessKey: config.secretAccessKey, + }, + }); + + // Test connection ?? IS this needed + // await this.testConnection(); + + // Store config + this.config = config; + } + + /** + * Get non-sensitive metadata + */ + getConfig(): Promise { + // Fetch and return metadata + } + + /** + * Test connection + */ + async testConnection(): Promise { + if (!this.client) { + throw new Error("Client not initialized"); + } + + try { + await this.client.send(new ListSecretsCommand({ MaxResults: 1 })); + return true; + } catch (error: any) { + throw new Error(`Connection test failed: ${error.message}`); + } + } + + /** + * Fetch secret + */ + async fetchSecret(secretName: string): Promise { + if (!this.client) { + throw new Error("Provider not configured"); + } + + const result = await this.client.send( + new GetSecretValueCommand({ SecretId: secretName }) + ); + + return ( + result.SecretString || Buffer.from(result.SecretBinary!).toString("utf-8") + ); + } + + /** + * List secrets (for autocomplete) + */ + async listSecrets(prefix?: string): Promise { + if (!this.client) { + throw new Error("Provider not configured"); + } + + // TODO: learn about prefix + + const result = await this.client.send( + new ListSecretsCommand({ + Filters: prefix ? [{ Key: "name", Values: [prefix] }] : undefined, + }) + ); + + return result.SecretList?.map((s) => s.Name!) || []; + } + async disconnect(): Promise { + this.client?.destroy(); + this.client = null; + } + + private validateConfig(config: AWSSecretsManagerConfig): void { + if (!config.accessKeyId || !config.secretAccessKey || !config.region) { + throw new Error("Missing required fields"); + } + } +} diff --git a/src/main/actions/externalSecrets/providerService/providerService.ts b/src/main/actions/externalSecrets/providerService/providerService.ts new file mode 100644 index 00000000..861ecd9b --- /dev/null +++ b/src/main/actions/externalSecrets/providerService/providerService.ts @@ -0,0 +1,207 @@ +import { ISecretProvider } from "./types"; + +export class ProviderService { + private providers: Map = new Map(); + + private encryptedStorage: EncryptedStorage; + private cache: SecretCache; + + constructor() { + this.encryptedStorage = new EncryptedStorage(); + this.cache = new SecretCache(); + } + + /** + * Initialize vault (load key, load existing providers) + */ + async initialize(): Promise { + // Load vault key from safeStorage + const keyManager = new KeyManager(); + + if (await keyManager.hasKey()) { + this.vaultKey = await keyManager.retrieveKey(); + this.isLocked = false; + + // Load all existing provider configs + await this.loadProviders(); + } + + this.isInitialized = true; + } + + /** + * Configure a provider (create new or update existing) + */ + async configureProvider( + id: string, + type: SecretProviderType, + name: string, + config: SecretProviderConfig + ): Promise { + if (!this.vaultKey) { + throw new Error("Vault is locked"); + } + + // 1. Create or get existing provider instance + let provider = this.providers.get(id); + + if (!provider) { + // Create new provider instance + provider = this.createProvider(id, type, name); + } + + // 2. Configure the provider (validates & tests connection) + await provider.configure(config); + + // 3. Save encrypted config to disk + await this.encryptedStorage.saveProviderConfig( + id, + { + type, + name, + config, + createdAt: Date.now(), + updatedAt: Date.now(), + }, + this.vaultKey + ); + + // 4. Store in memory + this.providers.set(id, provider); + + // 5. Clear cache for this provider + this.cache.invalidateByProvider(id); + + return provider.getMetadata(); + } + + /** + * Remove a provider + */ + async removeProvider(id: string): Promise { + const provider = this.providers.get(id); + if (!provider) { + throw new Error(`Provider not found: ${id}`); + } + + // 1. Disconnect + await provider.disconnect(); + + // 2. Remove from memory + this.providers.delete(id); + + // 3. Delete encrypted config + await this.encryptedStorage.deleteProviderConfig(id); + + // 4. Clear cache + this.cache.invalidateByProvider(id); + } + + /** + * List all configured providers + */ + listProviders(): SecretProviderMetadata[] { + return Array.from(this.providers.values()).map((p) => p.getMetadata()); + } + + /** + * Get a specific provider + */ + getProvider(id: string): ISecretProvider | undefined { + return this.providers.get(id); + } + + /** + * Resolve a secret + */ + async resolveSecret(providerId: string, secretName: string): Promise { + // Check cache + const cacheKey = `${providerId}/${secretName}`; + const cached = this.cache.get(cacheKey); + if (cached) { + return cached.value; + } + + // Get provider + const provider = this.providers.get(providerId); + if (!provider) { + throw new Error(`Provider not found: ${providerId}`); + } + + if (!provider.isConfigured) { + throw new Error(`Provider not configured: ${providerId}`); + } + + // Fetch from provider + const value = await provider.fetchSecret(secretName); + + // Cache it + this.cache.set(cacheKey, { + value, + provider: providerId, + expiresAt: Date.now() + this.getTTL(provider.type), + }); + + return value; + } + + /** + * Load providers from disk on startup + */ + private async loadProviders(): Promise { + const providerIds = await this.encryptedStorage.listProviders(); + + for (const id of providerIds) { + try { + const saved = await this.encryptedStorage.loadProviderConfig( + id, + this.vaultKey! + ); + + // Create provider instance + const provider = this.createProvider(id, saved.type, saved.name); + + // Configure it with saved config + await provider.configure(saved.config); + + // Store in memory + this.providers.set(id, provider); + } catch (error) { + console.error(`Failed to load provider ${id}:`, error); + } + } + } + + /** + * Factory method to create provider instances + */ + private createProvider( + id: string, + type: SecretProviderType, + name: string + ): ISecretProvider { + switch (type) { + case SecretProviderType.AWS_SECRETS_MANAGER: + return new AWSSecretsManagerProvider(id, name); + + case SecretProviderType.HASHICORP_VAULT: + return new HashiCorpVaultProvider(id, name); + + case SecretProviderType.AZURE_KEY_VAULT: + return new AzureKeyVaultProvider(id, name); + + default: + throw new Error(`Unknown provider type: ${type}`); + } + } + +// private getTTL(type: SecretProviderType): number { +// const TTL_CONFIG = { +// [SecretProviderType.AWS_SECRETS_MANAGER]: 5 * 60 * 1000, // 5 min +// [SecretProviderType.HASHICORP_VAULT]: 1 * 60 * 1000, // 1 min +// [SecretProviderType.AZURE_KEY_VAULT]: 5 * 60 * 1000, // 5 min +// [SecretProviderType.LOCAL_VAULT]: Infinity, // Never expire +// }; +// return TTL_CONFIG[type]; +// } +// } diff --git a/src/main/actions/externalSecrets/providerService/types.ts b/src/main/actions/externalSecrets/providerService/types.ts new file mode 100644 index 00000000..a4329728 --- /dev/null +++ b/src/main/actions/externalSecrets/providerService/types.ts @@ -0,0 +1,31 @@ +export enum SecretProviderType { + AWS_SECRETS_MANGER = "aws", +} + +export interface AWSSecretsManagerConfig { + accessKeyId: string; + secretAccessKey: string; + region: string; +} + +export interface SecretProviderMetadata { + id: string; + type: SecretProviderType; + name: string; + isConfigured: boolean; + createdAt: number; + updatedAt: number; +} + +export type SecretProviderConfig = AWSSecretsManagerConfig; + +export interface ISecretProvider { + type: SecretProviderType; + id: string; + + configure(config: SecretProviderConfig): Promise; + getConfig(id: string): Promise; // should return all or any non-sentive metadata? sensitive data is lazily fetched + + testConnection(): Promise; + fetchSecret(secretName: string): Promise; +} From 0f8cb7a21b5be21f86509b42a4c1e71f2af57b4a Mon Sep 17 00:00:00 2001 From: nafees87n Date: Mon, 22 Dec 2025 18:57:46 +0530 Subject: [PATCH 02/37] added external secrets manager interfaces --- .../actions/externalSecrets/cacheService.ts | 206 ----------------- .../encryptedFsStorageService.ts | 26 +++ .../encryptedStorageService.ts | 134 ------------ .../externalSecrets/externalSecretsManager.ts | 67 +++++- .../awsSecretManagerProvider.ts | 111 ---------- .../providerService/providerService.ts | 207 ------------------ .../externalSecrets/providerService/types.ts | 10 +- 7 files changed, 93 insertions(+), 668 deletions(-) create mode 100644 src/main/actions/externalSecrets/encryptedFsStorageService.ts delete mode 100644 src/main/actions/externalSecrets/encryptedStorageService.ts delete mode 100644 src/main/actions/externalSecrets/providerService/providerService.ts diff --git a/src/main/actions/externalSecrets/cacheService.ts b/src/main/actions/externalSecrets/cacheService.ts index db87ec96..e69de29b 100644 --- a/src/main/actions/externalSecrets/cacheService.ts +++ b/src/main/actions/externalSecrets/cacheService.ts @@ -1,206 +0,0 @@ -import { SecretProviderType } from "./providerService/types"; - -/** - * Cached secret entry - */ -interface CachedSecret { - value: string; - providerId: string; - providerType: SecretProviderType; - secretName: string; - fetchedAt: number; - expiresAt: number; -} - -/** - * TTL configuration per provider type - */ -const DEFAULT_TTL: Record = { - [SecretProviderType.AWS_SECRETS_MANGER]: 60 * 60 * 1000, // 60 minutes -}; - -// Only 1 provider active at a time, so no need for complex strategies -// name to value is enough - -/** - * In-memory cache for secrets - * - Session-scoped (cleared on app restart) - * - TTL-based expiration - * - Provider-aware invalidation - */ -export class CacheService { - private cache: Map = new Map(); - - get(secretName: string): string | null { - const entry = this.cache.get(secretName); - - if (!entry) { - // provider.fetchSecret(secretName); - // - - } - - // Check if expired - if (Date.now() > entry.expiresAt) { - this.cache.delete(secretName); - return null; - } - - return entry.value; - } - - /** - * Set a cached secret - */ - set( - providerId: string, - providerType: SecretProviderType, - secretName: string, - value: string, - customTTL?: number - ): void { - const key = this.getCacheKey(providerId, secretName); - const ttl = customTTL ?? DEFAULT_TTL[providerType]; - const now = Date.now(); - - this.cache.set(key, { - value, - providerId, - providerType, - secretName, - fetchedAt: now, - expiresAt: ttl === Infinity ? Infinity : now + ttl, - }); - } - - /** - * Invalidate a specific secret - */ - invalidate(providerId: string, secretName: string): boolean { - const key = this.getCacheKey(providerId, secretName); - return this.cache.delete(key); - } - - /** - * Invalidate all secrets from a specific provider - */ - invalidateByProvider(providerId: string): number { - let count = 0; - - for (const [key, entry] of this.cache.entries()) { - if (entry.providerId === providerId) { - this.cache.delete(key); - count++; - } - } - - return count; - } - - /** - * Invalidate all secrets of a specific provider type - */ - invalidateByProviderType(providerType: SecretProviderType): number { - let count = 0; - - for (const [key, entry] of this.cache.entries()) { - if (entry.providerType === providerType) { - this.cache.delete(key); - count++; - } - } - - return count; - } - - /** - * Clear all cached secrets - */ - clear(): void { - this.cache.clear(); - this.stats.hits = 0; - this.stats.misses = 0; - } - - /** - * Get cache statistics - */ - getStats(): CacheStats { - const total = this.stats.hits + this.stats.misses; - - return { - size: this.cache.size, - hits: this.stats.hits, - misses: this.stats.misses, - hitRate: total > 0 ? this.stats.hits / total : 0, - }; - } - - /** - * Clean up expired entries - * Should be called periodically - */ - cleanExpired(): number { - let count = 0; - const now = Date.now(); - - for (const [key, entry] of this.cache.entries()) { - if (entry.expiresAt !== Infinity && now > entry.expiresAt) { - this.cache.delete(key); - count++; - } - } - - return count; - } - - /** - * Get all cached entries (for debugging) - */ - getAllEntries(): Array<{ key: string; entry: CachedSecret }> { - return Array.from(this.cache.entries()).map(([key, entry]) => ({ - key, - entry, - })); - } - - /** - * Check if a specific secret is cached and valid - */ - has(providerId: string, secretName: string): boolean { - const key = this.getCacheKey(providerId, secretName); - const entry = this.cache.get(key); - - if (!entry) { - return false; - } - - // Check if expired - if (entry.expiresAt !== Infinity && Date.now() > entry.expiresAt) { - this.cache.delete(key); - return false; - } - - return true; - } - - /** - * Get time until a cached secret expires (in ms) - * Returns null if not cached or already expired - */ - getTimeToExpiry(providerId: string, secretName: string): number | null { - const key = this.getCacheKey(providerId, secretName); - const entry = this.cache.get(key); - - if (!entry) { - return null; - } - - if (entry.expiresAt === Infinity) { - return Infinity; - } - - const remaining = entry.expiresAt - Date.now(); - return remaining > 0 ? remaining : null; - } -} diff --git a/src/main/actions/externalSecrets/encryptedFsStorageService.ts b/src/main/actions/externalSecrets/encryptedFsStorageService.ts new file mode 100644 index 00000000..e0eb78bf --- /dev/null +++ b/src/main/actions/externalSecrets/encryptedFsStorageService.ts @@ -0,0 +1,26 @@ +import { safeStorage } from "electron"; + +export class EncryptedFsStorageService { + constructor(private readonly baseFolderPath: string) {} + + async initialize(): Promise { + if (!safeStorage.isEncryptionAvailable()) { + // Show trouble shooting steps to user + throw new Error("Encryption is not available on this system. "); + } + + // initialize directories + } + + async save( + data: Record, + path: string, + keysToEncrypt: string[] = [] + ): Promise { + // encrypted + } + + async load(path: string, keysToDecrypt: string[] = []): Promise<> {} + + async delete(path: string): Promise {} +} diff --git a/src/main/actions/externalSecrets/encryptedStorageService.ts b/src/main/actions/externalSecrets/encryptedStorageService.ts deleted file mode 100644 index 8c2bcedc..00000000 --- a/src/main/actions/externalSecrets/encryptedStorageService.ts +++ /dev/null @@ -1,134 +0,0 @@ -// main/services/vault/EncryptedStorage.ts - -import { safeStorage } from "electron"; -import * as fs from "fs/promises"; -import * as path from "path"; -import { - SecretProviderConfig, - SecretProviderType, -} from "./providerService/types"; -import { GLOBAL_CONFIG_FOLDER_PATH } from "renderer/actions/local-sync/constants"; - -/** - * Stored provider configuration - */ -interface StoredProviderConfig { - type: SecretProviderType; - name: string; - config: SecretProviderConfig; - createdAt: number; - updatedAt: number; -} - -/** - * EncryptedStorage using Electron's safeStorage API - * - * All encryption is handled by safeStorage (OS-level): - * - macOS: Keychain - * - Windows: DPAPI - * - Linux: libsecret - * - * No need for separate vault key or CryptoService! - */ -export class EncryptedStorage { - private readonly basePath: string; - - private readonly providersPath: string; - - constructor(baseFolder: string) { - this.basePath = path.join(GLOBAL_CONFIG_FOLDER_PATH); - this.providersPath = path.join(this.basePath, baseFolder); - } - - async initialize(): Promise { - if (!safeStorage.isEncryptionAvailable()) { - throw new Error( - "Encryption is not available on this system. " + - "This may happen on Linux if libsecret is not installed." - ); - } - - await fs.mkdir(this.providersPath, { recursive: true }); - } - - async save( - data: Record, - fileName: string, - keysToEncrypt: string[] = [] - ): Promise { - const stored: any = { - ...data, - updatedAt: Date.now(), - }; - - // 2. Encrypt sensitive keys within the config - for (const key of keysToEncrypt) { - if (stored[key] !== undefined) { - const sensitiveValue = JSON.stringify(stored[key]); - const encrypted = safeStorage.encryptString(sensitiveValue); - stored[key] = encrypted.toString("base64"); - } - } - - const json = JSON.stringify(stored); - - const filePath = this.getProviderFilePath(fileName); - const tempFilePath = `${filePath}.tmp`; - await fs.writeFile(tempFilePath, json); - await fs.rename(tempFilePath, filePath); - } - - async load( - fileName: string, - keysToDecrypt: string[] = [] - ): Promise { - const filePath = this.getProviderFilePath(fileName); - - try { - const encrypted = await fs.readFile(filePath); - - const stored: any = JSON.parse(encrypted.toString()); - - // Decrypt sensitive keys within the config - for (const key of keysToDecrypt) { - if (stored[key] !== undefined) { - const encryptedBuffer = Buffer.from(stored[key], "base64"); - const decrypted = safeStorage.decryptString(encryptedBuffer); - stored[key] = JSON.parse(decrypted); - } - } - - return stored as StoredProviderConfig; - } catch (error: any) { - // TODO: check error handling - if (error.code === "ENOENT") { - return null; // File doesn't exist - } - throw error; - } - } - - /** - * Delete provider configuration - */ - async delete(fileName: string): Promise { - const filePath = this.getProviderFilePath(fileName); - - try { - await fs.unlink(filePath); - } catch (error) { - // TODO: check error handling - if (error.code !== "ENOENT") { - throw error; - } - // Ignore if file doesn't exist - } - } - - /** - * Get file path for a provider - */ - private getProviderFilePath(fileName: string): string { - return path.join(this.providersPath, `${fileName}.enc`); - } -} diff --git a/src/main/actions/externalSecrets/externalSecretsManager.ts b/src/main/actions/externalSecrets/externalSecretsManager.ts index 2818f25d..497092d4 100644 --- a/src/main/actions/externalSecrets/externalSecretsManager.ts +++ b/src/main/actions/externalSecrets/externalSecretsManager.ts @@ -1,7 +1,66 @@ -class ExternalSecretsManager { - private isItialized=false; +import { EncryptedFsStorageService } from "./encryptedFsStorageService"; +import { ISecretProvider, SecretProviderConfig } from "./providerService/types"; - resolveSecret(variableName: string): string | null { +export class ExternalSecretsManager { + private providers: Map = new Map(); + constructor(private encryptedStorage: EncryptedFsStorageService) {} + + async initialize(): Promise { + this.encryptedStorage.initialize(); + this.listProviderConfigs().then((configs) => { + configs.forEach((config) => { + const providerInstance = this.createProviderInstance(config); + this.registerProviderInstance(providerInstance); + }); + }); + } + + async configureProvider(config: SecretProviderConfig) { + // process the config + // validate the config + + this.encryptedStorage.save(config, `providers/${config.id}`, [ + "config.accessKeyId", + "config.secretAccessKey", + "config.sessionToken", + ]); + } + + async removeProviderConfig(id: string) { + this.encryptedStorage.delete(`providers/${id}`); + this.unregisterProviderInstance(id); + } + + async getProviderConfig(id: string): Promise { + return this.encryptedStorage.load(`providers/${id}`, [ + "config.accessKeyId", + "config.secretAccessKey", + "config.sessionToken", + ]); + } + + private registerProviderInstance(provider: ISecretProvider) { + this.providers.set(provider.id, provider); } -} \ No newline at end of file + + private getProviderInstance(id: string): ISecretProvider | undefined { + return this.providers.get(id); + } + + private listProviderConfigs(): Promise { + return []; + } + + private hasProvider(id: string): boolean { + return this.providers.has(id); + } + + private unregisterProviderInstance(id: string): boolean { + return this.providers.delete(id); + } + + private createProviderInstance( + config: SecretProviderConfig + ): ISecretProvider {} +} diff --git a/src/main/actions/externalSecrets/providerService/awsSecretManagerProvider.ts b/src/main/actions/externalSecrets/providerService/awsSecretManagerProvider.ts index 61ef5e75..e69de29b 100644 --- a/src/main/actions/externalSecrets/providerService/awsSecretManagerProvider.ts +++ b/src/main/actions/externalSecrets/providerService/awsSecretManagerProvider.ts @@ -1,111 +0,0 @@ -import { - SecretsManagerClient, - GetSecretValueCommand, - ListSecretsCommand, -} from "@aws-sdk/client-secrets-manager"; -import { - AWSSecretsManagerConfig, - ISecretProvider, - SecretProviderConfig, - SecretProviderMetadata, - SecretProviderType, -} from "./types"; - -export class AWSSecretsManagerProvider implements ISecretProvider { - readonly type = SecretProviderType.AWS_SECRETS_MANGER; - - // Provider instance holds its own identity - constructor(public readonly id: string) {} - - private config: AWSSecretsManagerConfig | null = null; - private client: SecretsManagerClient | null = null; - - async configure(config: AWSSecretsManagerConfig): Promise { - // Validate - this.validateConfig(config); - - // Create AWS client - this.client = new SecretsManagerClient({ - region: config.region, - credentials: { - accessKeyId: config.accessKeyId, - secretAccessKey: config.secretAccessKey, - }, - }); - - // Test connection ?? IS this needed - // await this.testConnection(); - - // Store config - this.config = config; - } - - /** - * Get non-sensitive metadata - */ - getConfig(): Promise { - // Fetch and return metadata - } - - /** - * Test connection - */ - async testConnection(): Promise { - if (!this.client) { - throw new Error("Client not initialized"); - } - - try { - await this.client.send(new ListSecretsCommand({ MaxResults: 1 })); - return true; - } catch (error: any) { - throw new Error(`Connection test failed: ${error.message}`); - } - } - - /** - * Fetch secret - */ - async fetchSecret(secretName: string): Promise { - if (!this.client) { - throw new Error("Provider not configured"); - } - - const result = await this.client.send( - new GetSecretValueCommand({ SecretId: secretName }) - ); - - return ( - result.SecretString || Buffer.from(result.SecretBinary!).toString("utf-8") - ); - } - - /** - * List secrets (for autocomplete) - */ - async listSecrets(prefix?: string): Promise { - if (!this.client) { - throw new Error("Provider not configured"); - } - - // TODO: learn about prefix - - const result = await this.client.send( - new ListSecretsCommand({ - Filters: prefix ? [{ Key: "name", Values: [prefix] }] : undefined, - }) - ); - - return result.SecretList?.map((s) => s.Name!) || []; - } - async disconnect(): Promise { - this.client?.destroy(); - this.client = null; - } - - private validateConfig(config: AWSSecretsManagerConfig): void { - if (!config.accessKeyId || !config.secretAccessKey || !config.region) { - throw new Error("Missing required fields"); - } - } -} diff --git a/src/main/actions/externalSecrets/providerService/providerService.ts b/src/main/actions/externalSecrets/providerService/providerService.ts deleted file mode 100644 index 861ecd9b..00000000 --- a/src/main/actions/externalSecrets/providerService/providerService.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { ISecretProvider } from "./types"; - -export class ProviderService { - private providers: Map = new Map(); - - private encryptedStorage: EncryptedStorage; - private cache: SecretCache; - - constructor() { - this.encryptedStorage = new EncryptedStorage(); - this.cache = new SecretCache(); - } - - /** - * Initialize vault (load key, load existing providers) - */ - async initialize(): Promise { - // Load vault key from safeStorage - const keyManager = new KeyManager(); - - if (await keyManager.hasKey()) { - this.vaultKey = await keyManager.retrieveKey(); - this.isLocked = false; - - // Load all existing provider configs - await this.loadProviders(); - } - - this.isInitialized = true; - } - - /** - * Configure a provider (create new or update existing) - */ - async configureProvider( - id: string, - type: SecretProviderType, - name: string, - config: SecretProviderConfig - ): Promise { - if (!this.vaultKey) { - throw new Error("Vault is locked"); - } - - // 1. Create or get existing provider instance - let provider = this.providers.get(id); - - if (!provider) { - // Create new provider instance - provider = this.createProvider(id, type, name); - } - - // 2. Configure the provider (validates & tests connection) - await provider.configure(config); - - // 3. Save encrypted config to disk - await this.encryptedStorage.saveProviderConfig( - id, - { - type, - name, - config, - createdAt: Date.now(), - updatedAt: Date.now(), - }, - this.vaultKey - ); - - // 4. Store in memory - this.providers.set(id, provider); - - // 5. Clear cache for this provider - this.cache.invalidateByProvider(id); - - return provider.getMetadata(); - } - - /** - * Remove a provider - */ - async removeProvider(id: string): Promise { - const provider = this.providers.get(id); - if (!provider) { - throw new Error(`Provider not found: ${id}`); - } - - // 1. Disconnect - await provider.disconnect(); - - // 2. Remove from memory - this.providers.delete(id); - - // 3. Delete encrypted config - await this.encryptedStorage.deleteProviderConfig(id); - - // 4. Clear cache - this.cache.invalidateByProvider(id); - } - - /** - * List all configured providers - */ - listProviders(): SecretProviderMetadata[] { - return Array.from(this.providers.values()).map((p) => p.getMetadata()); - } - - /** - * Get a specific provider - */ - getProvider(id: string): ISecretProvider | undefined { - return this.providers.get(id); - } - - /** - * Resolve a secret - */ - async resolveSecret(providerId: string, secretName: string): Promise { - // Check cache - const cacheKey = `${providerId}/${secretName}`; - const cached = this.cache.get(cacheKey); - if (cached) { - return cached.value; - } - - // Get provider - const provider = this.providers.get(providerId); - if (!provider) { - throw new Error(`Provider not found: ${providerId}`); - } - - if (!provider.isConfigured) { - throw new Error(`Provider not configured: ${providerId}`); - } - - // Fetch from provider - const value = await provider.fetchSecret(secretName); - - // Cache it - this.cache.set(cacheKey, { - value, - provider: providerId, - expiresAt: Date.now() + this.getTTL(provider.type), - }); - - return value; - } - - /** - * Load providers from disk on startup - */ - private async loadProviders(): Promise { - const providerIds = await this.encryptedStorage.listProviders(); - - for (const id of providerIds) { - try { - const saved = await this.encryptedStorage.loadProviderConfig( - id, - this.vaultKey! - ); - - // Create provider instance - const provider = this.createProvider(id, saved.type, saved.name); - - // Configure it with saved config - await provider.configure(saved.config); - - // Store in memory - this.providers.set(id, provider); - } catch (error) { - console.error(`Failed to load provider ${id}:`, error); - } - } - } - - /** - * Factory method to create provider instances - */ - private createProvider( - id: string, - type: SecretProviderType, - name: string - ): ISecretProvider { - switch (type) { - case SecretProviderType.AWS_SECRETS_MANAGER: - return new AWSSecretsManagerProvider(id, name); - - case SecretProviderType.HASHICORP_VAULT: - return new HashiCorpVaultProvider(id, name); - - case SecretProviderType.AZURE_KEY_VAULT: - return new AzureKeyVaultProvider(id, name); - - default: - throw new Error(`Unknown provider type: ${type}`); - } - } - -// private getTTL(type: SecretProviderType): number { -// const TTL_CONFIG = { -// [SecretProviderType.AWS_SECRETS_MANAGER]: 5 * 60 * 1000, // 5 min -// [SecretProviderType.HASHICORP_VAULT]: 1 * 60 * 1000, // 1 min -// [SecretProviderType.AZURE_KEY_VAULT]: 5 * 60 * 1000, // 5 min -// [SecretProviderType.LOCAL_VAULT]: Infinity, // Never expire -// }; -// return TTL_CONFIG[type]; -// } -// } diff --git a/src/main/actions/externalSecrets/providerService/types.ts b/src/main/actions/externalSecrets/providerService/types.ts index a4329728..c8d88836 100644 --- a/src/main/actions/externalSecrets/providerService/types.ts +++ b/src/main/actions/externalSecrets/providerService/types.ts @@ -6,26 +6,24 @@ export interface AWSSecretsManagerConfig { accessKeyId: string; secretAccessKey: string; region: string; + sessionToken?: string; } -export interface SecretProviderMetadata { +export interface SecretProviderConfig { id: string; type: SecretProviderType; name: string; isConfigured: boolean; createdAt: number; updatedAt: number; + config: AWSSecretsManagerConfig; } -export type SecretProviderConfig = AWSSecretsManagerConfig; - export interface ISecretProvider { type: SecretProviderType; id: string; - configure(config: SecretProviderConfig): Promise; - getConfig(id: string): Promise; // should return all or any non-sentive metadata? sensitive data is lazily fetched - testConnection(): Promise; fetchSecret(secretName: string): Promise; + listSecrets(): Promise; } From 7b4d8696e49da5a58b4bfab2fb4953f7b51e1368 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Tue, 23 Dec 2025 14:29:02 +0530 Subject: [PATCH 03/37] fix: config type --- src/main/actions/externalSecrets/providerService/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/actions/externalSecrets/providerService/types.ts b/src/main/actions/externalSecrets/providerService/types.ts index c8d88836..1ca3bdc6 100644 --- a/src/main/actions/externalSecrets/providerService/types.ts +++ b/src/main/actions/externalSecrets/providerService/types.ts @@ -13,7 +13,6 @@ export interface SecretProviderConfig { id: string; type: SecretProviderType; name: string; - isConfigured: boolean; createdAt: number; updatedAt: number; config: AWSSecretsManagerConfig; From fa080d0c206a6a2a74c388d66f6bb0473cda2135 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Tue, 23 Dec 2025 14:38:34 +0530 Subject: [PATCH 04/37] added method --- .../actions/externalSecrets/externalSecretsManager.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/actions/externalSecrets/externalSecretsManager.ts b/src/main/actions/externalSecrets/externalSecretsManager.ts index 497092d4..2809b63a 100644 --- a/src/main/actions/externalSecrets/externalSecretsManager.ts +++ b/src/main/actions/externalSecrets/externalSecretsManager.ts @@ -19,6 +19,7 @@ export class ExternalSecretsManager { async configureProvider(config: SecretProviderConfig) { // process the config // validate the config + this.registerProviderInstance(this.createProviderInstance(config)); this.encryptedStorage.save(config, `providers/${config.id}`, [ "config.accessKeyId", @@ -28,8 +29,8 @@ export class ExternalSecretsManager { } async removeProviderConfig(id: string) { - this.encryptedStorage.delete(`providers/${id}`); this.unregisterProviderInstance(id); + this.encryptedStorage.delete(`providers/${id}`); } async getProviderConfig(id: string): Promise { @@ -40,6 +41,11 @@ export class ExternalSecretsManager { ]); } + async testProviderConnection(id: string): Promise { + const provider = this.getProviderInstance(id); + return provider?.testConnection(); + } + private registerProviderInstance(provider: ISecretProvider) { this.providers.set(provider.id, provider); } From 0caccc012af8eb71cf29f62a254599d90ffbdb71 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Wed, 24 Dec 2025 12:28:12 +0530 Subject: [PATCH 05/37] refactor --- .../externalSecrets => lib/secrets}/cacheService.ts | 0 .../secrets}/encryptedFsStorageService.ts | 0 .../secrets}/providerService/awsSecretManagerProvider.ts | 0 .../secrets}/providerService/types.ts | 0 .../secrets/secretsManager.ts} | 7 ++++++- 5 files changed, 6 insertions(+), 1 deletion(-) rename src/{main/actions/externalSecrets => lib/secrets}/cacheService.ts (100%) rename src/{main/actions/externalSecrets => lib/secrets}/encryptedFsStorageService.ts (100%) rename src/{main/actions/externalSecrets => lib/secrets}/providerService/awsSecretManagerProvider.ts (100%) rename src/{main/actions/externalSecrets => lib/secrets}/providerService/types.ts (100%) rename src/{main/actions/externalSecrets/externalSecretsManager.ts => lib/secrets/secretsManager.ts} (92%) diff --git a/src/main/actions/externalSecrets/cacheService.ts b/src/lib/secrets/cacheService.ts similarity index 100% rename from src/main/actions/externalSecrets/cacheService.ts rename to src/lib/secrets/cacheService.ts diff --git a/src/main/actions/externalSecrets/encryptedFsStorageService.ts b/src/lib/secrets/encryptedFsStorageService.ts similarity index 100% rename from src/main/actions/externalSecrets/encryptedFsStorageService.ts rename to src/lib/secrets/encryptedFsStorageService.ts diff --git a/src/main/actions/externalSecrets/providerService/awsSecretManagerProvider.ts b/src/lib/secrets/providerService/awsSecretManagerProvider.ts similarity index 100% rename from src/main/actions/externalSecrets/providerService/awsSecretManagerProvider.ts rename to src/lib/secrets/providerService/awsSecretManagerProvider.ts diff --git a/src/main/actions/externalSecrets/providerService/types.ts b/src/lib/secrets/providerService/types.ts similarity index 100% rename from src/main/actions/externalSecrets/providerService/types.ts rename to src/lib/secrets/providerService/types.ts diff --git a/src/main/actions/externalSecrets/externalSecretsManager.ts b/src/lib/secrets/secretsManager.ts similarity index 92% rename from src/main/actions/externalSecrets/externalSecretsManager.ts rename to src/lib/secrets/secretsManager.ts index 2809b63a..dd5e4796 100644 --- a/src/main/actions/externalSecrets/externalSecretsManager.ts +++ b/src/lib/secrets/secretsManager.ts @@ -1,7 +1,7 @@ import { EncryptedFsStorageService } from "./encryptedFsStorageService"; import { ISecretProvider, SecretProviderConfig } from "./providerService/types"; -export class ExternalSecretsManager { +export class SecretsManager { private providers: Map = new Map(); constructor(private encryptedStorage: EncryptedFsStorageService) {} @@ -41,6 +41,11 @@ export class ExternalSecretsManager { ]); } + private validateProviderConfig(config: SecretProviderConfig): boolean { + // implement validation logic + return true; + } + async testProviderConnection(id: string): Promise { const provider = this.getProviderInstance(id); return provider?.testConnection(); From a96565024f7080df6a303c152cb1139ce51d4c2f Mon Sep 17 00:00:00 2001 From: nafees87n Date: Thu, 25 Dec 2025 18:10:24 +0530 Subject: [PATCH 06/37] refactor folder name --- .../cacheService.ts | 0 .../encryptedFsStorageService.ts | 19 ++++++++--- .../providerService/ISecretProvider.ts | 10 ++++++ .../awsSecretManagerProvider.ts | 0 .../secretsManager.ts | 33 ++++++++++--------- .../types.ts | 9 ----- 6 files changed, 42 insertions(+), 29 deletions(-) rename src/lib/{secrets => secretsManager}/cacheService.ts (100%) rename src/lib/{secrets => secretsManager}/encryptedFsStorageService.ts (57%) create mode 100644 src/lib/secretsManager/providerService/ISecretProvider.ts rename src/lib/{secrets => secretsManager}/providerService/awsSecretManagerProvider.ts (100%) rename src/lib/{secrets => secretsManager}/secretsManager.ts (82%) rename src/lib/{secrets/providerService => secretsManager}/types.ts (64%) diff --git a/src/lib/secrets/cacheService.ts b/src/lib/secretsManager/cacheService.ts similarity index 100% rename from src/lib/secrets/cacheService.ts rename to src/lib/secretsManager/cacheService.ts diff --git a/src/lib/secrets/encryptedFsStorageService.ts b/src/lib/secretsManager/encryptedFsStorageService.ts similarity index 57% rename from src/lib/secrets/encryptedFsStorageService.ts rename to src/lib/secretsManager/encryptedFsStorageService.ts index e0eb78bf..fc437f33 100644 --- a/src/lib/secrets/encryptedFsStorageService.ts +++ b/src/lib/secretsManager/encryptedFsStorageService.ts @@ -1,5 +1,13 @@ import { safeStorage } from "electron"; +interface EncryptionConfig { + keysToEncrypt?: (keyof T)[]; +} + +interface DecryptionConfig { + keysToDecrypt?: (keyof T)[]; +} + export class EncryptedFsStorageService { constructor(private readonly baseFolderPath: string) {} @@ -12,15 +20,18 @@ export class EncryptedFsStorageService { // initialize directories } - async save( - data: Record, + async save>( + data: T, path: string, - keysToEncrypt: string[] = [] + encryptConfig: EncryptionConfig ): Promise { // encrypted } - async load(path: string, keysToDecrypt: string[] = []): Promise<> {} + async load>( + path: string, + decryptConfig: DecryptionConfig + ): Promise {} async delete(path: string): Promise {} } diff --git a/src/lib/secretsManager/providerService/ISecretProvider.ts b/src/lib/secretsManager/providerService/ISecretProvider.ts new file mode 100644 index 00000000..afef8091 --- /dev/null +++ b/src/lib/secretsManager/providerService/ISecretProvider.ts @@ -0,0 +1,10 @@ +import { SecretProviderType } from "../types"; + +export interface ISecretProvider { + type: SecretProviderType; + id: string; + + testConnection(): Promise; + fetchSecret(secretName: string): Promise; + listSecrets(): Promise; +} diff --git a/src/lib/secrets/providerService/awsSecretManagerProvider.ts b/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts similarity index 100% rename from src/lib/secrets/providerService/awsSecretManagerProvider.ts rename to src/lib/secretsManager/providerService/awsSecretManagerProvider.ts diff --git a/src/lib/secrets/secretsManager.ts b/src/lib/secretsManager/secretsManager.ts similarity index 82% rename from src/lib/secrets/secretsManager.ts rename to src/lib/secretsManager/secretsManager.ts index dd5e4796..985ab37f 100644 --- a/src/lib/secrets/secretsManager.ts +++ b/src/lib/secretsManager/secretsManager.ts @@ -1,5 +1,6 @@ import { EncryptedFsStorageService } from "./encryptedFsStorageService"; -import { ISecretProvider, SecretProviderConfig } from "./providerService/types"; +import { ISecretProvider } from "./providerService/ISecretProvider"; +import { SecretProviderConfig } from "./types"; export class SecretsManager { private providers: Map = new Map(); @@ -21,11 +22,13 @@ export class SecretsManager { // validate the config this.registerProviderInstance(this.createProviderInstance(config)); - this.encryptedStorage.save(config, `providers/${config.id}`, [ - "config.accessKeyId", - "config.secretAccessKey", - "config.sessionToken", - ]); + this.encryptedStorage.save( + config, + `providers/${config.id}`, + { + keysToEncrypt: ["config"], + } + ); } async removeProviderConfig(id: string) { @@ -34,16 +37,9 @@ export class SecretsManager { } async getProviderConfig(id: string): Promise { - return this.encryptedStorage.load(`providers/${id}`, [ - "config.accessKeyId", - "config.secretAccessKey", - "config.sessionToken", - ]); - } - - private validateProviderConfig(config: SecretProviderConfig): boolean { - // implement validation logic - return true; + return this.encryptedStorage.load(`providers/${id}`, { + keysToDecrypt: ["config"], + }); } async testProviderConnection(id: string): Promise { @@ -51,6 +47,11 @@ export class SecretsManager { return provider?.testConnection(); } + private validateProviderConfig(config: SecretProviderConfig): boolean { + // implement validation logic + return true; + } + private registerProviderInstance(provider: ISecretProvider) { this.providers.set(provider.id, provider); } diff --git a/src/lib/secrets/providerService/types.ts b/src/lib/secretsManager/types.ts similarity index 64% rename from src/lib/secrets/providerService/types.ts rename to src/lib/secretsManager/types.ts index 1ca3bdc6..6440b39e 100644 --- a/src/lib/secrets/providerService/types.ts +++ b/src/lib/secretsManager/types.ts @@ -17,12 +17,3 @@ export interface SecretProviderConfig { updatedAt: number; config: AWSSecretsManagerConfig; } - -export interface ISecretProvider { - type: SecretProviderType; - id: string; - - testConnection(): Promise; - fetchSecret(secretName: string): Promise; - listSecrets(): Promise; -} From 2f3859782f85bc9cb55465ca9efd53c21bd60f00 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Sun, 28 Dec 2025 00:53:00 +0530 Subject: [PATCH 07/37] fix: interface reusability --- .../encryptedStorage/IEncryptedStorage.ts | 6 ++ .../encryptedFsStorageService.ts | 23 ++---- .../providerRegistry/FsProviderRegistry.ts | 80 +++++++++++++++++++ .../providerRegistry/IProviderRegistry.ts | 10 +++ .../providerService/ISecretProvider.ts | 2 + src/lib/secretsManager/secretsManager.ts | 70 +++++----------- 6 files changed, 122 insertions(+), 69 deletions(-) create mode 100644 src/lib/secretsManager/encryptedStorage/IEncryptedStorage.ts rename src/lib/secretsManager/{ => encryptedStorage}/encryptedFsStorageService.ts (50%) create mode 100644 src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts create mode 100644 src/lib/secretsManager/providerRegistry/IProviderRegistry.ts diff --git a/src/lib/secretsManager/encryptedStorage/IEncryptedStorage.ts b/src/lib/secretsManager/encryptedStorage/IEncryptedStorage.ts new file mode 100644 index 00000000..7e364ba1 --- /dev/null +++ b/src/lib/secretsManager/encryptedStorage/IEncryptedStorage.ts @@ -0,0 +1,6 @@ +export interface IEncryptedStorage { + initialize(): Promise; + save>(key: string, data: T): Promise; + load>(key: string): Promise; + delete(key: string): Promise; +} diff --git a/src/lib/secretsManager/encryptedFsStorageService.ts b/src/lib/secretsManager/encryptedStorage/encryptedFsStorageService.ts similarity index 50% rename from src/lib/secretsManager/encryptedFsStorageService.ts rename to src/lib/secretsManager/encryptedStorage/encryptedFsStorageService.ts index fc437f33..ad54941a 100644 --- a/src/lib/secretsManager/encryptedFsStorageService.ts +++ b/src/lib/secretsManager/encryptedStorage/encryptedFsStorageService.ts @@ -1,14 +1,7 @@ import { safeStorage } from "electron"; +import { IEncryptedStorage } from "./IEncryptedStorage"; -interface EncryptionConfig { - keysToEncrypt?: (keyof T)[]; -} - -interface DecryptionConfig { - keysToDecrypt?: (keyof T)[]; -} - -export class EncryptedFsStorageService { +export class EncryptedFsStorageService implements IEncryptedStorage { constructor(private readonly baseFolderPath: string) {} async initialize(): Promise { @@ -21,17 +14,13 @@ export class EncryptedFsStorageService { } async save>( - data: T, - path: string, - encryptConfig: EncryptionConfig + key: string, + data: T ): Promise { // encrypted } - async load>( - path: string, - decryptConfig: DecryptionConfig - ): Promise {} + async load>(key: string): Promise {} - async delete(path: string): Promise {} + async delete(key: string): Promise {} } diff --git a/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts new file mode 100644 index 00000000..554e98aa --- /dev/null +++ b/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts @@ -0,0 +1,80 @@ +import * as fs from "fs/promises"; +import * as path from "path"; +import { IProviderRegistry } from "./IProviderRegistry"; +import { SecretProviderConfig, SecretProviderType } from "../types"; +import { IEncryptedStorage } from "../encryptedStorage/IEncryptedStorage"; + +const MANIFEST_FILENAME = "providers.json"; + +export interface ProvidersManifest { + version: string; + providers: { + id: string; + storagePath: string; + type: SecretProviderType; + }[]; +} + +export class FileBasedProviderRegistry implements IProviderRegistry { + private manifestPath: string; + + constructor( + private readonly encryptedStorage: IEncryptedStorage, + private readonly configDir: string + ) { + this.manifestPath = path.join(configDir, MANIFEST_FILENAME); + } + + async initialize(): Promise { + await this.ensureConfigDir(); + } + + async listProviders(): Promise { + const manifest = await this.loadManifest(); + return manifest.providers.map((p) => p.id); + } + + async loadAllProviderConfigs(): Promise {} + + async saveProviderConfig(config: SecretProviderConfig): Promise { + const manifest = await this.loadManifest(); + const storageKey = config.id; + + await this.encryptedStorage.save(storageKey, config); + + // Update manifest + + await this.saveManifest(manifest); + } + + async deleteProviderConfig(id: string): Promise { + const manifest = await this.loadManifest(); + const entry = manifest.providers.find((p) => p.id === id); + if (!entry) return; + + await this.encryptedStorage.delete(id); + + manifest.providers = manifest.providers.filter((p) => p.id !== id); + await this.saveManifest(manifest); + } + + async getProviderConfig(id: string): Promise { + const manifest = await this.loadManifest(); + const entry = manifest.providers.find((p) => p.id === id); + if (!entry) return null; + + return this.encryptedStorage.load(id); + } + + private async ensureConfigDir(): Promise { + try { + await fs.mkdir(this.configDir, { recursive: true }); + } catch (error) { + console.error("Failed to create config directory:", error); + } + } + + private async loadManifest(): Promise {} + + private async saveManifest(manifest: ProvidersManifest): Promise {} +} diff --git a/src/lib/secretsManager/providerRegistry/IProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/IProviderRegistry.ts new file mode 100644 index 00000000..e9fda3bc --- /dev/null +++ b/src/lib/secretsManager/providerRegistry/IProviderRegistry.ts @@ -0,0 +1,10 @@ +import { SecretProviderConfig } from "../types"; + +export interface IProviderRegistry { + initialize(): Promise; + listProviders(): Promise; + loadAllProviderConfigs(): Promise; + saveProviderConfig(config: SecretProviderConfig): Promise; + deleteProviderConfig(id: string): Promise; + getProviderConfig(id: string): Promise; +} diff --git a/src/lib/secretsManager/providerService/ISecretProvider.ts b/src/lib/secretsManager/providerService/ISecretProvider.ts index afef8091..353f8a75 100644 --- a/src/lib/secretsManager/providerService/ISecretProvider.ts +++ b/src/lib/secretsManager/providerService/ISecretProvider.ts @@ -7,4 +7,6 @@ export interface ISecretProvider { testConnection(): Promise; fetchSecret(secretName: string): Promise; listSecrets(): Promise; + + validateConfig(): boolean; } diff --git a/src/lib/secretsManager/secretsManager.ts b/src/lib/secretsManager/secretsManager.ts index 985ab37f..fde69dcd 100644 --- a/src/lib/secretsManager/secretsManager.ts +++ b/src/lib/secretsManager/secretsManager.ts @@ -1,77 +1,43 @@ -import { EncryptedFsStorageService } from "./encryptedFsStorageService"; +import { IProviderRegistry } from "./providerRegistry/IProviderRegistry"; import { ISecretProvider } from "./providerService/ISecretProvider"; import { SecretProviderConfig } from "./types"; export class SecretsManager { private providers: Map = new Map(); - constructor(private encryptedStorage: EncryptedFsStorageService) {} + constructor(private registry: IProviderRegistry) {} async initialize(): Promise { - this.encryptedStorage.initialize(); - this.listProviderConfigs().then((configs) => { - configs.forEach((config) => { - const providerInstance = this.createProviderInstance(config); - this.registerProviderInstance(providerInstance); - }); - }); + this.registry.initialize(); + this.initProvidersFromManifest(); } - async configureProvider(config: SecretProviderConfig) { - // process the config - // validate the config - this.registerProviderInstance(this.createProviderInstance(config)); + private async initProvidersFromManifest() { + const configs = await this.registry.loadAllProviderConfigs(); + configs.forEach((config) => { + this.providers.set(config.id, this.createProviderInstance(config)); + }); + } - this.encryptedStorage.save( - config, - `providers/${config.id}`, - { - keysToEncrypt: ["config"], - } - ); + async addProviderConfig(config: SecretProviderConfig) { + this.providers.set(config.id, this.createProviderInstance(config)); + this.registry.saveProviderConfig(config); } async removeProviderConfig(id: string) { - this.unregisterProviderInstance(id); - this.encryptedStorage.delete(`providers/${id}`); + this.providers.delete(id); + this.registry.deleteProviderConfig(id); } - async getProviderConfig(id: string): Promise { - return this.encryptedStorage.load(`providers/${id}`, { - keysToDecrypt: ["config"], - }); + async getProviderConfig(id: string): Promise { + return this.registry.getProviderConfig(id); } async testProviderConnection(id: string): Promise { - const provider = this.getProviderInstance(id); + const provider = this.providers.get(id); return provider?.testConnection(); } - private validateProviderConfig(config: SecretProviderConfig): boolean { - // implement validation logic - return true; - } - - private registerProviderInstance(provider: ISecretProvider) { - this.providers.set(provider.id, provider); - } - - private getProviderInstance(id: string): ISecretProvider | undefined { - return this.providers.get(id); - } - - private listProviderConfigs(): Promise { - return []; - } - - private hasProvider(id: string): boolean { - return this.providers.has(id); - } - - private unregisterProviderInstance(id: string): boolean { - return this.providers.delete(id); - } - private createProviderInstance( config: SecretProviderConfig ): ISecretProvider {} From bbefd3ff1326184c68809b9f0c2596846861def6 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Tue, 30 Dec 2025 15:39:55 +0530 Subject: [PATCH 08/37] fix --- src/lib/secretsManager/secretsManager.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/secretsManager/secretsManager.ts b/src/lib/secretsManager/secretsManager.ts index fde69dcd..d263e9f8 100644 --- a/src/lib/secretsManager/secretsManager.ts +++ b/src/lib/secretsManager/secretsManager.ts @@ -3,9 +3,13 @@ import { ISecretProvider } from "./providerService/ISecretProvider"; import { SecretProviderConfig } from "./types"; export class SecretsManager { + private registry: IProviderRegistry; + private providers: Map = new Map(); - constructor(private registry: IProviderRegistry) {} + constructor(registry: IProviderRegistry) { + this.registry = registry; + } async initialize(): Promise { this.registry.initialize(); From a808ba9397cb9bdd659d4ac8cfd8017406322351 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Fri, 9 Jan 2026 18:06:06 +0530 Subject: [PATCH 09/37] added caching service --- src/lib/secretsManager/cacheService.ts | 79 +++++++++++ .../cacheStorage/ICacheStorage.ts | 56 ++++++++ .../cacheStorage/InMemoryCacheStorage.ts | 66 +++++++++ .../encryptedStorage/IEncryptedStorage.ts | 25 +++- .../providerRegistry/FsProviderRegistry.ts | 20 ++- .../providerRegistry/IProviderRegistry.ts | 22 +++ .../providerService/ISecretProvider.ts | 25 +++- .../awsSecretManagerProvider.ts | 45 ++++++ .../providerService/providerFactory.ts | 13 ++ src/lib/secretsManager/secretsManager.ts | 129 +++++++++++++++++- src/lib/secretsManager/types.ts | 31 ++++- 11 files changed, 491 insertions(+), 20 deletions(-) create mode 100644 src/lib/secretsManager/cacheStorage/ICacheStorage.ts create mode 100644 src/lib/secretsManager/cacheStorage/InMemoryCacheStorage.ts create mode 100644 src/lib/secretsManager/providerService/providerFactory.ts diff --git a/src/lib/secretsManager/cacheService.ts b/src/lib/secretsManager/cacheService.ts index e69de29b..e0b47ffb 100644 --- a/src/lib/secretsManager/cacheService.ts +++ b/src/lib/secretsManager/cacheService.ts @@ -0,0 +1,79 @@ +import { ICacheStorage } from "./cacheStorage/ICacheStorage"; +import { CachedSecret, SecretProviderType } from "./types"; + +// Function +// 1. get/set/invalidate cached secrets +// 2. cache storage is pluggable (in-memory, file-based, redis, etc.) + +export class SecretsCacheService { + private storage: ICacheStorage; + + private defaultTTL: number; + + constructor(storage: ICacheStorage) { + this.storage = storage; + } + + async get( + providerId: string, + identifier: string, + version?: string + ): Promise { + const secret = await this.storage.findByIdentifier( + providerId, + identifier, + version + ); + return secret?.value ?? null; + } + + async set( + providerId: string, + providerType: SecretProviderType, + identifier: string, + value: string, + version?: string, + ttl?: number + ): Promise { + const now = Date.now(); + const secret: CachedSecret = { + id: this.generateId(), + identifier, + value, + providerId, + providerType, + fetchedAt: now, + expiresAt: now + (ttl ?? this.defaultTTL), + version, + }; + await this.storage.set(secret); + } + + async invalidate( + providerId: string, + key: string, + version?: string + ): Promise { + const secret = await this.storage.findByIdentifier( + providerId, + key, + version + ); + if (secret) { + await this.storage.delete(secret.id); + } + } + + async invalidateByProvider(providerId: string): Promise { + await this.storage.deleteByProvider(providerId); + } + + async clear(): Promise { + await this.storage.clear(); + } + + // eslint-disable-next-line class-methods-use-this + private generateId(): string { + return uuidv4(); + } +} diff --git a/src/lib/secretsManager/cacheStorage/ICacheStorage.ts b/src/lib/secretsManager/cacheStorage/ICacheStorage.ts new file mode 100644 index 00000000..3891dd7c --- /dev/null +++ b/src/lib/secretsManager/cacheStorage/ICacheStorage.ts @@ -0,0 +1,56 @@ +import { CachedSecret } from "../types"; +import { ISecretsStorage } from "../encryptedStorage/IEncryptedStorage"; + +// export interface ICacheStorage { +/** + * Get a cached secret by its unique ID + */ +// get(id: string): Promise; + +// /** +// * Store a cached secret +// */ +// set(secret: CachedSecret): Promise; + +// /** +// * Delete a cached secret by its ID +// */ +// delete(id: string): Promise; + +// /** +// * Find a cached secret by provider ID, key, and optional version +// */ +// findByIdentifier( +// providerId: string, +// identifier: string, +// version?: string +// ): Promise; + +// /** +// * Delete all cached secrets for a specific provider +// */ +// deleteByProvider(providerId: string): Promise; + +// /** +// * Clear all cached secrets +// */ +// clear(): Promise; +// } + +export abstract class AbstractCacheStorage implements ISecretsStorage { + abstract initialize(): Promise; + + abstract save(key: string, data: CachedSecret): Promise; + + abstract load(key: string): Promise; + + abstract delete(key: string): Promise; + + abstract findByIdentifier( + providerId: string, + identifier: string, + version?: string + ): Promise; + + abstract clear(): Promise; +} diff --git a/src/lib/secretsManager/cacheStorage/InMemoryCacheStorage.ts b/src/lib/secretsManager/cacheStorage/InMemoryCacheStorage.ts new file mode 100644 index 00000000..04fd97aa --- /dev/null +++ b/src/lib/secretsManager/cacheStorage/InMemoryCacheStorage.ts @@ -0,0 +1,66 @@ +import { CachedSecret } from "../types"; +import { AbstractCacheStorage } from "./ICacheStorage"; + +export class InMemoryCacheStorage implements AbstractCacheStorage { + private cache: Map = new Map(); + + async load(key: string): Promise { + const secret = this.cache.get(key); + if (!secret) { + return null; + } + + if (Date.now() > secret.expiresAt) { + this.cache.delete(key); + return null; + } + return secret; + } + + async save(key: string, data: CachedSecret): Promise { + this.cache.set(key, data); + } + + async delete(id: string): Promise { + this.cache.delete(id); + } + + async findByIdentifier( + key: string, + identifier: string, + version?: string // Store multiple versions of the same secret or not? + ): Promise { + const now = Date.now(); + let found: CachedSecret | null = null; + + for (const secret of this.cache.values()) { + // Skip expired entries + if (now > secret.expiresAt) { + continue; + } + + if ( + secret.providerId === key && + secret.identifier === identifier && + secret.version === version + ) { + found = secret; + break; + } + } + + return found; + } + + async deleteByProvider(providerId: string): Promise { + for (const [id, secret] of this.cache.entries()) { + if (secret.providerId === providerId) { + this.cache.delete(id); + } + } + } + + async clear(): Promise { + this.cache.clear(); + } +} diff --git a/src/lib/secretsManager/encryptedStorage/IEncryptedStorage.ts b/src/lib/secretsManager/encryptedStorage/IEncryptedStorage.ts index 7e364ba1..453ab7c5 100644 --- a/src/lib/secretsManager/encryptedStorage/IEncryptedStorage.ts +++ b/src/lib/secretsManager/encryptedStorage/IEncryptedStorage.ts @@ -1,6 +1,27 @@ -export interface IEncryptedStorage { +// Can this be storage and not encrypted storage? +// export interface IEncryptedStorage { +// initialize(): Promise; +// save>(key: string, data: T): Promise; +// load>(key: string): Promise; +// delete(key: string): Promise; +// } + +export interface ISecretsStorage { initialize(): Promise; save>(key: string, data: T): Promise; - load>(key: string): Promise; + load>(key: string): Promise; delete(key: string): Promise; } + +export abstract class AbstractEncryptedStorage implements ISecretsStorage { + abstract initialize(): Promise; + + abstract save>( + key: string, + data: T + ): Promise; + + abstract load>(key: string): Promise; + + abstract delete(key: string): Promise; +} diff --git a/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts index 554e98aa..eafd6550 100644 --- a/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts @@ -1,6 +1,8 @@ import * as fs from "fs/promises"; import * as path from "path"; -import { IProviderRegistry } from "./IProviderRegistry"; +import { + AbstractProviderRegistry, +} from "./IProviderRegistry"; import { SecretProviderConfig, SecretProviderType } from "../types"; import { IEncryptedStorage } from "../encryptedStorage/IEncryptedStorage"; @@ -15,13 +17,19 @@ export interface ProvidersManifest { }[]; } -export class FileBasedProviderRegistry implements IProviderRegistry { +// Functions +// 1. initialize registry (create config dir if not exists) +// 2. list providers +// 3. + +export class FileBasedProviderRegistry extends AbstractProviderRegistry { private manifestPath: string; - constructor( - private readonly encryptedStorage: IEncryptedStorage, - private readonly configDir: string - ) { + private configDir: string; + + constructor(encryptedStorage: IEncryptedStorage, configDir: string) { + super(encryptedStorage); + this.configDir = configDir; this.manifestPath = path.join(configDir, MANIFEST_FILENAME); } diff --git a/src/lib/secretsManager/providerRegistry/IProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/IProviderRegistry.ts index e9fda3bc..16d1a703 100644 --- a/src/lib/secretsManager/providerRegistry/IProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/IProviderRegistry.ts @@ -1,4 +1,6 @@ +// filepath: /Users/nafeesn/requestly/requestly-desktop-app/src/lib/secretsManager/providerRegistry/IProviderRegistry.ts import { SecretProviderConfig } from "../types"; +import { IEncryptedStorage } from "../encryptedStorage/IEncryptedStorage"; export interface IProviderRegistry { initialize(): Promise; @@ -8,3 +10,23 @@ export interface IProviderRegistry { deleteProviderConfig(id: string): Promise; getProviderConfig(id: string): Promise; } + +export abstract class AbstractProviderRegistry implements IProviderRegistry { + protected encryptedStorage: IEncryptedStorage + + constructor(encryptedStorage: IEncryptedStorage) { + this.encryptedStorage = encryptedStorage; + } + + abstract initialize(): Promise; + + abstract listProviders(): Promise; + + abstract loadAllProviderConfigs(): Promise; + + abstract saveProviderConfig(config: SecretProviderConfig): Promise; + + abstract deleteProviderConfig(id: string): Promise; + + abstract getProviderConfig(id: string): Promise; +} diff --git a/src/lib/secretsManager/providerService/ISecretProvider.ts b/src/lib/secretsManager/providerService/ISecretProvider.ts index 353f8a75..f0605708 100644 --- a/src/lib/secretsManager/providerService/ISecretProvider.ts +++ b/src/lib/secretsManager/providerService/ISecretProvider.ts @@ -1,12 +1,29 @@ -import { SecretProviderType } from "../types"; +import { SecretProviderType, SecretReference } from "../types"; -export interface ISecretProvider { +// evaluate if its bettter to have abstract class than interface +interface ISecretProvider { type: SecretProviderType; id: string; testConnection(): Promise; - fetchSecret(secretName: string): Promise; + fetchSecret(ref: SecretReference): Promise; listSecrets(): Promise; +} + +export abstract class AbstractSecretProvider implements ISecretProvider { + abstract readonly type: SecretProviderType; + + abstract readonly id: string; + + protected config: any; + + abstract testConnection(): Promise; + + abstract fetchSecret(ref: SecretReference): Promise; + + abstract listSecrets(): Promise; - validateConfig(): boolean; + static validateConfig(config: any): boolean { + throw new Error("Not implemented"); + } } diff --git a/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts b/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts index e69de29b..2a0a3940 100644 --- a/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts +++ b/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts @@ -0,0 +1,45 @@ +import { AbstractSecretProvider } from "./ISecretProvider"; +import { + AWSSecretsManagerConfig, + SecretProviderConfig, + SecretProviderType, + SecretReference, +} from "../types"; + +// Functions +// 1. validate config +// 2. test connection +// 3. fetch secret +// 4. list secrets + +export class AWSSecretsManagerProvider extends AbstractSecretProvider { + readonly type = SecretProviderType.AWS_SECRETS_MANAGER; + + readonly id: string; + + protected config: AWSSecretsManagerConfig; + + constructor(providerConfig: SecretProviderConfig) { + super(); + this.id = providerConfig.id; + this.config = providerConfig.config as AWSSecretsManagerConfig; + } + + static validateConfig(config: AWSSecretsManagerConfig): boolean { + return Boolean( + config.accessKeyId && config.secretAccessKey && config.region + ); + } + + async testConnection(): Promise { + if (!AWSSecretsManagerProvider.validateConfig(this.config)) { + return false; + } + + return true; + } + + async fetchSecret(ref: SecretReference): Promise {} + + async listSecrets(): Promise {} +} diff --git a/src/lib/secretsManager/providerService/providerFactory.ts b/src/lib/secretsManager/providerService/providerFactory.ts new file mode 100644 index 00000000..70a68c8e --- /dev/null +++ b/src/lib/secretsManager/providerService/providerFactory.ts @@ -0,0 +1,13 @@ +import { SecretProviderConfig, SecretProviderType } from "../types"; +import { ISecretProvider } from "./ISecretProvider"; +import { AWSSecretsManagerProvider } from "./awsSecretManagerProvider"; + +export function createProvider(config: SecretProviderConfig): ISecretProvider { + switch (config.type) { + case SecretProviderType.AWS_SECRETS_MANAGER: + return new AWSSecretsManagerProvider(config); + default: + throw new Error(`Unknown provider type: ${config.type}`); + } +} + diff --git a/src/lib/secretsManager/secretsManager.ts b/src/lib/secretsManager/secretsManager.ts index d263e9f8..a5d2008e 100644 --- a/src/lib/secretsManager/secretsManager.ts +++ b/src/lib/secretsManager/secretsManager.ts @@ -1,14 +1,40 @@ +import { SecretsCacheService } from "./cacheService"; import { IProviderRegistry } from "./providerRegistry/IProviderRegistry"; -import { ISecretProvider } from "./providerService/ISecretProvider"; -import { SecretProviderConfig } from "./types"; +import { createProvider } from "./providerService/providerFactory"; +import { SecretProviderConfig, SecretReference } from "./types"; +import { AbstractSecretProvider } from "./providerService/ISecretProvider"; + +// Questions +// 1. store multiple versions of the same secret or not? +// 2. use secretReference or do it like mp with union types? +// 3. cache invalidation strategy? +// 4. Need a new method for refreshing all the cached secrets +// --5. reuse the storage interface for encrypted storage and cache storage? + +// Functions +// 1. initialize registry and cache service +// 2. add/remove provider configs +// 3. fetch secret (with caching) +// 4. list secrets +// 5. invalidate cache + +// FLows +// 1. INIT - load all provider configs from the registry and create provider instances. +// 2. ADD/REMOVE PROVIDER CONFIG - update the registry and provider instances map. +// 3. FETCH SECRET - check cache first, if not found or expired, fetch from provider, store in cache, return secret. +// 4. Refresh Secrets - bulk fetch and update all secrets from their providers and update the cache +// export class SecretsManager { private registry: IProviderRegistry; - private providers: Map = new Map(); + private cacheService: SecretsCacheService; + + private providers: Map = new Map(); - constructor(registry: IProviderRegistry) { + constructor(registry: IProviderRegistry, cacheService: SecretsCacheService) { this.registry = registry; + this.cacheService = cacheService; } async initialize(): Promise { @@ -39,10 +65,101 @@ export class SecretsManager { async testProviderConnection(id: string): Promise { const provider = this.providers.get(id); - return provider?.testConnection(); + return provider?.testConnection() ?? false; + } + + async fetchSecret(providerId: string, ref: SecretReference): Promise { + const cached = await this.cacheService.get( + providerId, + ref.identifier, + ref.version + ); + + if (cached) { + return cached; + } + + const provider = this.providers.get(providerId); + if (!provider) { + throw new Error(`Provider not found: ${providerId}`); + } + + const secret = await provider.fetchSecret(ref); + + await this.cacheService.set( + providerId, + provider.type, + ref.identifier, + secret, + ref.version + ); + + return secret; } + async refreshSecrets( + secrets: Array<{ providerId: string; ref: SecretReference }> + ): Promise { + for (const s of secrets) { + // Invalidate cache + // Fetch fresh secret and update cache + } + } + + async fetchSecrets( + secrets: Array<{ providerId: string; ref: SecretReference }> + ): Promise> { + for (const s of secrets) { + const secret = await this.fetchSecret(s.providerId, s.ref); + } + } + + // Do we need this method? + async fetchSecretFresh( + providerId: string, + ref: SecretReference + ): Promise { + await this.cacheService.invalidate(providerId, ref.identifier, ref.version); + + const provider = this.providers.get(providerId); + if (!provider) { + throw new Error(`Provider not found: ${providerId}`); + } + + const secret = await provider.fetchSecret(ref); + + await this.cacheService.set( + providerId, + provider.type, + ref.identifier, + secret, + ref.version + ); + + return secret; + } + + async listSecrets(providerId: string): Promise { + const provider = this.providers.get(providerId); + if (!provider) { + throw new Error(`Provider not found: ${providerId}`); + } + + return provider.listSecrets(); + } + + async invalidateCache(providerId?: string): Promise { + if (providerId) { + await this.cacheService.invalidateByProvider(providerId); + } else { + await this.cacheService.clear(); + } + } + + // eslint-disable-next-line class-methods-use-this private createProviderInstance( config: SecretProviderConfig - ): ISecretProvider {} + ): AbstractSecretProvider { + return createProvider(config); + } } diff --git a/src/lib/secretsManager/types.ts b/src/lib/secretsManager/types.ts index 6440b39e..1a1a962a 100644 --- a/src/lib/secretsManager/types.ts +++ b/src/lib/secretsManager/types.ts @@ -1,5 +1,5 @@ export enum SecretProviderType { - AWS_SECRETS_MANGER = "aws", + AWS_SECRETS_MANAGER = "aws", } export interface AWSSecretsManagerConfig { @@ -9,11 +9,38 @@ export interface AWSSecretsManagerConfig { sessionToken?: string; } +export type ProviderSpecificConfig = AWSSecretsManagerConfig; // | HashicorpVaultConfig | OtherProviderConfig; + export interface SecretProviderConfig { id: string; type: SecretProviderType; name: string; createdAt: number; updatedAt: number; - config: AWSSecretsManagerConfig; + config: ProviderSpecificConfig; +} + +export interface SecretReference { + identifier: string; + version?: string; + key?: string; +} + +/** + * + * type SecretReference = + * | { type: "aws"; nameOrArn: string; versionId?: string; versionStage?: string } + * | { type: "vault"; path: string; version?: number; key?: string }; + */ + + +export interface CachedSecret { + id: string; // Unique identifier + identifier: string; // Secret identifier (name, ARN, or path) + value: string; // The actual secret value + providerId: string; + providerType: SecretProviderType; + fetchedAt: number; + expiresAt: number; + version?: string; } From aa26c497dc4447740148fee42532317826c497ce Mon Sep 17 00:00:00 2001 From: nafees87n Date: Mon, 12 Jan 2026 18:29:55 +0530 Subject: [PATCH 10/37] refactored providerRegistry --- src/lib/secretsManager/cacheService.ts | 79 ----------- .../cacheStorage/ICacheStorage.ts | 56 -------- .../cacheStorage/InMemoryCacheStorage.ts | 66 --------- .../AbstractEncryptedStorage.ts | 12 ++ .../encryptedStorage/IEncryptedStorage.ts | 27 ---- .../AbstractProviderRegistry.ts | 42 ++++++ .../providerRegistry/FsProviderRegistry.ts | 65 ++++++--- .../providerRegistry/IProviderRegistry.ts | 32 ----- .../providerService/AbstractSecretProvider.ts | 27 ++++ .../providerService/ISecretProvider.ts | 29 ---- .../awsSecretManagerProvider.ts | 15 ++- src/lib/secretsManager/secretsManager.ts | 125 ++++++------------ src/lib/secretsManager/types.ts | 18 +++ 13 files changed, 195 insertions(+), 398 deletions(-) delete mode 100644 src/lib/secretsManager/cacheService.ts delete mode 100644 src/lib/secretsManager/cacheStorage/ICacheStorage.ts delete mode 100644 src/lib/secretsManager/cacheStorage/InMemoryCacheStorage.ts create mode 100644 src/lib/secretsManager/encryptedStorage/AbstractEncryptedStorage.ts delete mode 100644 src/lib/secretsManager/encryptedStorage/IEncryptedStorage.ts create mode 100644 src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts delete mode 100644 src/lib/secretsManager/providerRegistry/IProviderRegistry.ts create mode 100644 src/lib/secretsManager/providerService/AbstractSecretProvider.ts delete mode 100644 src/lib/secretsManager/providerService/ISecretProvider.ts diff --git a/src/lib/secretsManager/cacheService.ts b/src/lib/secretsManager/cacheService.ts deleted file mode 100644 index e0b47ffb..00000000 --- a/src/lib/secretsManager/cacheService.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { ICacheStorage } from "./cacheStorage/ICacheStorage"; -import { CachedSecret, SecretProviderType } from "./types"; - -// Function -// 1. get/set/invalidate cached secrets -// 2. cache storage is pluggable (in-memory, file-based, redis, etc.) - -export class SecretsCacheService { - private storage: ICacheStorage; - - private defaultTTL: number; - - constructor(storage: ICacheStorage) { - this.storage = storage; - } - - async get( - providerId: string, - identifier: string, - version?: string - ): Promise { - const secret = await this.storage.findByIdentifier( - providerId, - identifier, - version - ); - return secret?.value ?? null; - } - - async set( - providerId: string, - providerType: SecretProviderType, - identifier: string, - value: string, - version?: string, - ttl?: number - ): Promise { - const now = Date.now(); - const secret: CachedSecret = { - id: this.generateId(), - identifier, - value, - providerId, - providerType, - fetchedAt: now, - expiresAt: now + (ttl ?? this.defaultTTL), - version, - }; - await this.storage.set(secret); - } - - async invalidate( - providerId: string, - key: string, - version?: string - ): Promise { - const secret = await this.storage.findByIdentifier( - providerId, - key, - version - ); - if (secret) { - await this.storage.delete(secret.id); - } - } - - async invalidateByProvider(providerId: string): Promise { - await this.storage.deleteByProvider(providerId); - } - - async clear(): Promise { - await this.storage.clear(); - } - - // eslint-disable-next-line class-methods-use-this - private generateId(): string { - return uuidv4(); - } -} diff --git a/src/lib/secretsManager/cacheStorage/ICacheStorage.ts b/src/lib/secretsManager/cacheStorage/ICacheStorage.ts deleted file mode 100644 index 3891dd7c..00000000 --- a/src/lib/secretsManager/cacheStorage/ICacheStorage.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { CachedSecret } from "../types"; -import { ISecretsStorage } from "../encryptedStorage/IEncryptedStorage"; - -// export interface ICacheStorage { -/** - * Get a cached secret by its unique ID - */ -// get(id: string): Promise; - -// /** -// * Store a cached secret -// */ -// set(secret: CachedSecret): Promise; - -// /** -// * Delete a cached secret by its ID -// */ -// delete(id: string): Promise; - -// /** -// * Find a cached secret by provider ID, key, and optional version -// */ -// findByIdentifier( -// providerId: string, -// identifier: string, -// version?: string -// ): Promise; - -// /** -// * Delete all cached secrets for a specific provider -// */ -// deleteByProvider(providerId: string): Promise; - -// /** -// * Clear all cached secrets -// */ -// clear(): Promise; -// } - -export abstract class AbstractCacheStorage implements ISecretsStorage { - abstract initialize(): Promise; - - abstract save(key: string, data: CachedSecret): Promise; - - abstract load(key: string): Promise; - - abstract delete(key: string): Promise; - - abstract findByIdentifier( - providerId: string, - identifier: string, - version?: string - ): Promise; - - abstract clear(): Promise; -} diff --git a/src/lib/secretsManager/cacheStorage/InMemoryCacheStorage.ts b/src/lib/secretsManager/cacheStorage/InMemoryCacheStorage.ts deleted file mode 100644 index 04fd97aa..00000000 --- a/src/lib/secretsManager/cacheStorage/InMemoryCacheStorage.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { CachedSecret } from "../types"; -import { AbstractCacheStorage } from "./ICacheStorage"; - -export class InMemoryCacheStorage implements AbstractCacheStorage { - private cache: Map = new Map(); - - async load(key: string): Promise { - const secret = this.cache.get(key); - if (!secret) { - return null; - } - - if (Date.now() > secret.expiresAt) { - this.cache.delete(key); - return null; - } - return secret; - } - - async save(key: string, data: CachedSecret): Promise { - this.cache.set(key, data); - } - - async delete(id: string): Promise { - this.cache.delete(id); - } - - async findByIdentifier( - key: string, - identifier: string, - version?: string // Store multiple versions of the same secret or not? - ): Promise { - const now = Date.now(); - let found: CachedSecret | null = null; - - for (const secret of this.cache.values()) { - // Skip expired entries - if (now > secret.expiresAt) { - continue; - } - - if ( - secret.providerId === key && - secret.identifier === identifier && - secret.version === version - ) { - found = secret; - break; - } - } - - return found; - } - - async deleteByProvider(providerId: string): Promise { - for (const [id, secret] of this.cache.entries()) { - if (secret.providerId === providerId) { - this.cache.delete(id); - } - } - } - - async clear(): Promise { - this.cache.clear(); - } -} diff --git a/src/lib/secretsManager/encryptedStorage/AbstractEncryptedStorage.ts b/src/lib/secretsManager/encryptedStorage/AbstractEncryptedStorage.ts new file mode 100644 index 00000000..d3a43082 --- /dev/null +++ b/src/lib/secretsManager/encryptedStorage/AbstractEncryptedStorage.ts @@ -0,0 +1,12 @@ +export abstract class AbstractEncryptedStorage { + abstract initialize(): Promise; + + abstract save>( + key: string, + data: T + ): Promise; + + abstract load>(key: string): Promise; + + abstract delete(key: string): Promise; +} diff --git a/src/lib/secretsManager/encryptedStorage/IEncryptedStorage.ts b/src/lib/secretsManager/encryptedStorage/IEncryptedStorage.ts deleted file mode 100644 index 453ab7c5..00000000 --- a/src/lib/secretsManager/encryptedStorage/IEncryptedStorage.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Can this be storage and not encrypted storage? -// export interface IEncryptedStorage { -// initialize(): Promise; -// save>(key: string, data: T): Promise; -// load>(key: string): Promise; -// delete(key: string): Promise; -// } - -export interface ISecretsStorage { - initialize(): Promise; - save>(key: string, data: T): Promise; - load>(key: string): Promise; - delete(key: string): Promise; -} - -export abstract class AbstractEncryptedStorage implements ISecretsStorage { - abstract initialize(): Promise; - - abstract save>( - key: string, - data: T - ): Promise; - - abstract load>(key: string): Promise; - - abstract delete(key: string): Promise; -} diff --git a/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts new file mode 100644 index 00000000..8b5fbcfc --- /dev/null +++ b/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts @@ -0,0 +1,42 @@ +import { SecretProviderConfig, SecretProviderType } from "../types"; +import { AbstractEncryptedStorage } from "../encryptedStorage/AbstractEncryptedStorage"; +import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider"; + +export interface ProvidersManifest { + version: string; + providers: { + id: string; + storagePath: string; + type: SecretProviderType; + }[]; +} + +export abstract class AbstractProviderRegistry { + protected encryptedStorage: AbstractEncryptedStorage; + + protected providers: Map = new Map(); + + constructor(encryptedStorage: AbstractEncryptedStorage) { + this.encryptedStorage = encryptedStorage; + } + + abstract initialize(): Promise; + + protected abstract createProviderInstance( + config: SecretProviderConfig + ): AbstractSecretProvider; + + protected abstract loadManifest(): Promise; + + protected abstract saveManifest(manifest: ProvidersManifest): Promise; + + abstract getAllProviderConfigs(): Promise; + + abstract getProviderConfig(id: string): Promise; + + abstract setProviderConfig(config: SecretProviderConfig): Promise; + + abstract deleteProviderConfig(id: string): Promise; + + abstract getProvider(providerId: string): AbstractSecretProvider | null; +} diff --git a/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts index eafd6550..d8428d87 100644 --- a/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts @@ -1,21 +1,14 @@ -import * as fs from "fs/promises"; +import * as fs from "fs"; import * as path from "path"; -import { - AbstractProviderRegistry, -} from "./IProviderRegistry"; import { SecretProviderConfig, SecretProviderType } from "../types"; -import { IEncryptedStorage } from "../encryptedStorage/IEncryptedStorage"; +import { createProvider } from "../providerService/providerFactory"; +import { AbstractProviderRegistry } from "./AbstractProviderRegistry"; +import { AbstractEncryptedStorage } from "../encryptedStorage/AbstractEncryptedStorage"; +import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider"; const MANIFEST_FILENAME = "providers.json"; -export interface ProvidersManifest { - version: string; - providers: { - id: string; - storagePath: string; - type: SecretProviderType; - }[]; -} + // Functions // 1. initialize registry (create config dir if not exists) @@ -27,24 +20,45 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { private configDir: string; - constructor(encryptedStorage: IEncryptedStorage, configDir: string) { + protected providers: Map = new Map(); + + constructor(encryptedStorage: AbstractEncryptedStorage, configDir: string) { super(encryptedStorage); this.configDir = configDir; this.manifestPath = path.join(configDir, MANIFEST_FILENAME); } + getProvider(providerId: string): AbstractSecretProvider | null { + return this.providers.get(providerId) || null; + } + async initialize(): Promise { await this.ensureConfigDir(); + this.initProvidersFromManifest(); } - async listProviders(): Promise { - const manifest = await this.loadManifest(); - return manifest.providers.map((p) => p.id); + private async initProvidersFromManifest() { + const configs = await this.getAllProviderConfigs(); + configs.forEach((config) => { + this.providers.set(config.id, this.createProviderInstance(config)); + }); } - async loadAllProviderConfigs(): Promise {} + async getAllProviderConfigs(): Promise { + const manifest = await this.loadManifest(); + const configs: SecretProviderConfig[] = []; - async saveProviderConfig(config: SecretProviderConfig): Promise { + for (const entry of manifest.providers) { + const config = await this.encryptedStorage.load( + entry.id + ); + configs.push(config); + } + + return configs; + } + + async setProviderConfig(config: SecretProviderConfig): Promise { const manifest = await this.loadManifest(); const storageKey = config.id; @@ -53,6 +67,7 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { // Update manifest await this.saveManifest(manifest); + this.providers.set(config.id, this.createProviderInstance(config)); } async deleteProviderConfig(id: string): Promise { @@ -64,6 +79,7 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { manifest.providers = manifest.providers.filter((p) => p.id !== id); await this.saveManifest(manifest); + this.providers.delete(id); } async getProviderConfig(id: string): Promise { @@ -82,7 +98,14 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { } } - private async loadManifest(): Promise {} + protected async loadManifest(): Promise {} + + protected async saveManifest(manifest: ProvidersManifest): Promise {} - private async saveManifest(manifest: ProvidersManifest): Promise {} + // eslint-disable-next-line class-methods-use-this + protected createProviderInstance( + config: SecretProviderConfig + ): AbstractSecretProvider { + return createProvider(config); + } } diff --git a/src/lib/secretsManager/providerRegistry/IProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/IProviderRegistry.ts deleted file mode 100644 index 16d1a703..00000000 --- a/src/lib/secretsManager/providerRegistry/IProviderRegistry.ts +++ /dev/null @@ -1,32 +0,0 @@ -// filepath: /Users/nafeesn/requestly/requestly-desktop-app/src/lib/secretsManager/providerRegistry/IProviderRegistry.ts -import { SecretProviderConfig } from "../types"; -import { IEncryptedStorage } from "../encryptedStorage/IEncryptedStorage"; - -export interface IProviderRegistry { - initialize(): Promise; - listProviders(): Promise; - loadAllProviderConfigs(): Promise; - saveProviderConfig(config: SecretProviderConfig): Promise; - deleteProviderConfig(id: string): Promise; - getProviderConfig(id: string): Promise; -} - -export abstract class AbstractProviderRegistry implements IProviderRegistry { - protected encryptedStorage: IEncryptedStorage - - constructor(encryptedStorage: IEncryptedStorage) { - this.encryptedStorage = encryptedStorage; - } - - abstract initialize(): Promise; - - abstract listProviders(): Promise; - - abstract loadAllProviderConfigs(): Promise; - - abstract saveProviderConfig(config: SecretProviderConfig): Promise; - - abstract deleteProviderConfig(id: string): Promise; - - abstract getProviderConfig(id: string): Promise; -} diff --git a/src/lib/secretsManager/providerService/AbstractSecretProvider.ts b/src/lib/secretsManager/providerService/AbstractSecretProvider.ts new file mode 100644 index 00000000..c0628d72 --- /dev/null +++ b/src/lib/secretsManager/providerService/AbstractSecretProvider.ts @@ -0,0 +1,27 @@ +import { CachedSecret, SecretProviderType, SecretReference } from "../types"; + +export abstract class AbstractSecretProvider { + protected cache: Map = new Map(); + + abstract getSecretIdentfier(ref: SecretReference): string; + + abstract readonly type: SecretProviderType; + + abstract readonly id: string; + + protected config: any; + + abstract testConnection(): Promise; + + abstract getSecret(ref: SecretReference): Promise; + + abstract getSecrets(): Promise; + + abstract setSecret(): Promise; + + abstract setSecrets(): Promise; + + static validateConfig(config: any): boolean { + throw new Error("Not implemented"); + } +} diff --git a/src/lib/secretsManager/providerService/ISecretProvider.ts b/src/lib/secretsManager/providerService/ISecretProvider.ts deleted file mode 100644 index f0605708..00000000 --- a/src/lib/secretsManager/providerService/ISecretProvider.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { SecretProviderType, SecretReference } from "../types"; - -// evaluate if its bettter to have abstract class than interface -interface ISecretProvider { - type: SecretProviderType; - id: string; - - testConnection(): Promise; - fetchSecret(ref: SecretReference): Promise; - listSecrets(): Promise; -} - -export abstract class AbstractSecretProvider implements ISecretProvider { - abstract readonly type: SecretProviderType; - - abstract readonly id: string; - - protected config: any; - - abstract testConnection(): Promise; - - abstract fetchSecret(ref: SecretReference): Promise; - - abstract listSecrets(): Promise; - - static validateConfig(config: any): boolean { - throw new Error("Not implemented"); - } -} diff --git a/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts b/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts index 2a0a3940..7b6152d4 100644 --- a/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts +++ b/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts @@ -5,6 +5,7 @@ import { SecretProviderType, SecretReference, } from "../types"; +import { SecretsCacheService } from "../cacheService"; // Functions // 1. validate config @@ -17,12 +18,24 @@ export class AWSSecretsManagerProvider extends AbstractSecretProvider { readonly id: string; + protected config: AWSSecretsManagerConfig; - constructor(providerConfig: SecretProviderConfig) { + protected cacheService: SecretsCacheService; + + // { type: "aws"; nameOrArn: string; versionId?: string; versionStage?: string } + protected getSecretIdentfier(ref: AwsReference): string { + return `name=${this.nameOrArn};version:${ref.version}`; + } + + constructor( + providerConfig: SecretProviderConfig, + cacheService: SecretsCacheService + ) { super(); this.id = providerConfig.id; this.config = providerConfig.config as AWSSecretsManagerConfig; + this.cacheService = cacheService; } static validateConfig(config: AWSSecretsManagerConfig): boolean { diff --git a/src/lib/secretsManager/secretsManager.ts b/src/lib/secretsManager/secretsManager.ts index a5d2008e..584c4e64 100644 --- a/src/lib/secretsManager/secretsManager.ts +++ b/src/lib/secretsManager/secretsManager.ts @@ -1,15 +1,16 @@ import { SecretsCacheService } from "./cacheService"; -import { IProviderRegistry } from "./providerRegistry/IProviderRegistry"; -import { createProvider } from "./providerService/providerFactory"; import { SecretProviderConfig, SecretReference } from "./types"; -import { AbstractSecretProvider } from "./providerService/ISecretProvider"; +import { EncryptedFsStorageService } from "./encryptedStorage/encryptedFsStorageService"; +import { FileBasedProviderRegistry } from "./providerRegistry/FsProviderRegistry"; +import { AbstractProviderRegistry } from "./providerRegistry/AbstractProviderRegistry"; // Questions -// 1. store multiple versions of the same secret or not? +// 1. store multiple versions of the same secret or not?@ // 2. use secretReference or do it like mp with union types? -// 3. cache invalidation strategy? +// 3. cache invalidation strategy?@ // 4. Need a new method for refreshing all the cached secrets // --5. reuse the storage interface for encrypted storage and cache storage? +// 6. // providerId in fetchByIdentifier ?? would be confusing and then I can name it key but without using "key" word. How can I make it generic? AI suggested using composite key. // Functions // 1. initialize registry and cache service @@ -25,37 +26,44 @@ import { AbstractSecretProvider } from "./providerService/ISecretProvider"; // 4. Refresh Secrets - bulk fetch and update all secrets from their providers and update the cache // -export class SecretsManager { - private registry: IProviderRegistry; +// Merging secretManager and providerRegistry +// Methods like createProviderInstance need to be moved to the providerRegistry + +const encryptedStorage = new EncryptedFsStorageService(""); +const providerRegistry = new FileBasedProviderRegistry(encryptedStorage, ""); + +const secretsManager = new SecretsManager(providerRegistry); + +// createProviderInstance should have cache Storage as dependency. +// providerRegistry cannot be exposed because it would have storage specific code like fs methods etc. - private cacheService: SecretsCacheService; +// Why need to change cache storage at provider lever? +// Different providers may have different cache storage requirements but why? +// Different providers can have different source of truth but caching should be common. WHy not? +// But agreed provider should be the one interacting with cache storage and repo layer and not secretmanager - private providers: Map = new Map(); +// Changes +// 1. cacheService associated with provider instance instead of secretManager. +// 2. registry manages the providers map +// 3. All the methods from secretManager delegated to registry and provider instances. +// 4. provider instance creation is moved to registry. - constructor(registry: IProviderRegistry, cacheService: SecretsCacheService) { +export class SecretsManager { + private registry: AbstractProviderRegistry; + + constructor(registry: AbstractProviderRegistry) { this.registry = registry; - this.cacheService = cacheService; } async initialize(): Promise { this.registry.initialize(); - this.initProvidersFromManifest(); - } - - private async initProvidersFromManifest() { - const configs = await this.registry.loadAllProviderConfigs(); - configs.forEach((config) => { - this.providers.set(config.id, this.createProviderInstance(config)); - }); } async addProviderConfig(config: SecretProviderConfig) { - this.providers.set(config.id, this.createProviderInstance(config)); - this.registry.saveProviderConfig(config); + this.registry.setProviderConfig(config); } async removeProviderConfig(id: string) { - this.providers.delete(id); this.registry.deleteProviderConfig(id); } @@ -64,37 +72,12 @@ export class SecretsManager { } async testProviderConnection(id: string): Promise { - const provider = this.providers.get(id); + const provider = this.registry.getProvider(id); return provider?.testConnection() ?? false; } async fetchSecret(providerId: string, ref: SecretReference): Promise { - const cached = await this.cacheService.get( - providerId, - ref.identifier, - ref.version - ); - - if (cached) { - return cached; - } - - const provider = this.providers.get(providerId); - if (!provider) { - throw new Error(`Provider not found: ${providerId}`); - } - - const secret = await provider.fetchSecret(ref); - - await this.cacheService.set( - providerId, - provider.type, - ref.identifier, - secret, - ref.version - ); - - return secret; + this.registry.getProvider(providerId)?.fetchSecret(ref); } async refreshSecrets( @@ -114,33 +97,8 @@ export class SecretsManager { } } - // Do we need this method? - async fetchSecretFresh( - providerId: string, - ref: SecretReference - ): Promise { - await this.cacheService.invalidate(providerId, ref.identifier, ref.version); - - const provider = this.providers.get(providerId); - if (!provider) { - throw new Error(`Provider not found: ${providerId}`); - } - - const secret = await provider.fetchSecret(ref); - - await this.cacheService.set( - providerId, - provider.type, - ref.identifier, - secret, - ref.version - ); - - return secret; - } - async listSecrets(providerId: string): Promise { - const provider = this.providers.get(providerId); + const provider = this.registry.getProvider(providerId); if (!provider) { throw new Error(`Provider not found: ${providerId}`); } @@ -148,18 +106,11 @@ export class SecretsManager { return provider.listSecrets(); } - async invalidateCache(providerId?: string): Promise { - if (providerId) { - await this.cacheService.invalidateByProvider(providerId); - } else { - await this.cacheService.clear(); + async invalidateCache(providerId: string): Promise { + const provider = this.registry.getProvider(providerId); + if (!provider) { + throw new Error(`Provider not found: ${providerId}`); } - } - - // eslint-disable-next-line class-methods-use-this - private createProviderInstance( - config: SecretProviderConfig - ): AbstractSecretProvider { - return createProvider(config); + await provider.invalidateCache(); } } diff --git a/src/lib/secretsManager/types.ts b/src/lib/secretsManager/types.ts index 1a1a962a..6b0a23f6 100644 --- a/src/lib/secretsManager/types.ts +++ b/src/lib/secretsManager/types.ts @@ -26,6 +26,8 @@ export interface SecretReference { key?: string; } + + /** * * type SecretReference = @@ -33,6 +35,22 @@ export interface SecretReference { * | { type: "vault"; path: string; version?: number; key?: string }; */ +ProviderCache: { + [key: string]: { + value: string; + ttl: number; + }[] +} = { + "foo": { + value: "bar", + ttl: 1000, + } +} + + + + + export interface CachedSecret { id: string; // Unique identifier From 13f5131de6dc6ea79aae1eb7f63b79bbdb76e0fb Mon Sep 17 00:00:00 2001 From: nafees87n Date: Mon, 12 Jan 2026 23:47:53 +0530 Subject: [PATCH 11/37] fix: types --- .../providerRegistry/FsProviderRegistry.ts | 1 + .../providerService/AbstractSecretProvider.ts | 4 +- .../awsSecretManagerProvider.ts | 64 +++++++++++++------ src/lib/secretsManager/secretsManager.ts | 28 +------- src/lib/secretsManager/types.ts | 34 ++-------- 5 files changed, 53 insertions(+), 78 deletions(-) diff --git a/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts index d8428d87..7631509c 100644 --- a/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts @@ -62,6 +62,7 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { const manifest = await this.loadManifest(); const storageKey = config.id; + // do atomic write await this.encryptedStorage.save(storageKey, config); // Update manifest diff --git a/src/lib/secretsManager/providerService/AbstractSecretProvider.ts b/src/lib/secretsManager/providerService/AbstractSecretProvider.ts index c0628d72..6902918c 100644 --- a/src/lib/secretsManager/providerService/AbstractSecretProvider.ts +++ b/src/lib/secretsManager/providerService/AbstractSecretProvider.ts @@ -3,14 +3,14 @@ import { CachedSecret, SecretProviderType, SecretReference } from "../types"; export abstract class AbstractSecretProvider { protected cache: Map = new Map(); - abstract getSecretIdentfier(ref: SecretReference): string; - abstract readonly type: SecretProviderType; abstract readonly id: string; protected config: any; + protected abstract getSecretIdentfier(ref: SecretReference): string; + abstract testConnection(): Promise; abstract getSecret(ref: SecretReference): Promise; diff --git a/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts b/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts index 7b6152d4..68587403 100644 --- a/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts +++ b/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts @@ -1,11 +1,13 @@ -import { AbstractSecretProvider } from "./ISecretProvider"; +/* eslint-disable class-methods-use-this */ import { + AwsSecretReference, AWSSecretsManagerConfig, + CachedSecret, SecretProviderConfig, SecretProviderType, SecretReference, } from "../types"; -import { SecretsCacheService } from "../cacheService"; +import { AbstractSecretProvider } from "./AbstractSecretProvider"; // Functions // 1. validate config @@ -13,35 +15,25 @@ import { SecretsCacheService } from "../cacheService"; // 3. fetch secret // 4. list secrets +const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes + export class AWSSecretsManagerProvider extends AbstractSecretProvider { readonly type = SecretProviderType.AWS_SECRETS_MANAGER; readonly id: string; - protected config: AWSSecretsManagerConfig; - protected cacheService: SecretsCacheService; + protected cache: Map = new Map(); - // { type: "aws"; nameOrArn: string; versionId?: string; versionStage?: string } - protected getSecretIdentfier(ref: AwsReference): string { - return `name=${this.nameOrArn};version:${ref.version}`; + protected getSecretIdentfier(ref: AwsSecretReference): string { + return `name=${ref.nameOrArn};version:${ref.version}`; } - constructor( - providerConfig: SecretProviderConfig, - cacheService: SecretsCacheService - ) { + constructor(providerConfig: SecretProviderConfig) { super(); this.id = providerConfig.id; this.config = providerConfig.config as AWSSecretsManagerConfig; - this.cacheService = cacheService; - } - - static validateConfig(config: AWSSecretsManagerConfig): boolean { - return Boolean( - config.accessKeyId && config.secretAccessKey && config.region - ); } async testConnection(): Promise { @@ -52,7 +44,39 @@ export class AWSSecretsManagerProvider extends AbstractSecretProvider { return true; } - async fetchSecret(ref: SecretReference): Promise {} + async getSecret(ref: AwsSecretReference): Promise { + const secretKey = this.getSecretIdentfier(ref); + const cachedSecret = this.cache.get(secretKey); + const now = Date.now(); + + if (cachedSecret && cachedSecret.expiry > now) { + return cachedSecret.value; + } + + // Fetch from AWS Secrets Manager + const secretValue = "fetched-secret-value"; // Placeholder for actual fetch logic + + this.cache.set(secretKey, { + value: secretValue, + expiry: now + DEFAULT_CACHE_TTL_MS, + }); + + return secretValue; + } + + async getSecrets(): Promise {} + + async setSecret(): Promise { + throw new Error("Method not implemented."); + } + + async setSecrets(): Promise { + throw new Error("Method not implemented."); + } - async listSecrets(): Promise {} + static validateConfig(config: AWSSecretsManagerConfig): boolean { + return Boolean( + config.accessKeyId && config.secretAccessKey && config.region + ); + } } diff --git a/src/lib/secretsManager/secretsManager.ts b/src/lib/secretsManager/secretsManager.ts index 584c4e64..5a299e94 100644 --- a/src/lib/secretsManager/secretsManager.ts +++ b/src/lib/secretsManager/secretsManager.ts @@ -1,15 +1,9 @@ -import { SecretsCacheService } from "./cacheService"; import { SecretProviderConfig, SecretReference } from "./types"; import { EncryptedFsStorageService } from "./encryptedStorage/encryptedFsStorageService"; import { FileBasedProviderRegistry } from "./providerRegistry/FsProviderRegistry"; import { AbstractProviderRegistry } from "./providerRegistry/AbstractProviderRegistry"; // Questions -// 1. store multiple versions of the same secret or not?@ -// 2. use secretReference or do it like mp with union types? -// 3. cache invalidation strategy?@ -// 4. Need a new method for refreshing all the cached secrets -// --5. reuse the storage interface for encrypted storage and cache storage? // 6. // providerId in fetchByIdentifier ?? would be confusing and then I can name it key but without using "key" word. How can I make it generic? AI suggested using composite key. // Functions @@ -26,9 +20,6 @@ import { AbstractProviderRegistry } from "./providerRegistry/AbstractProviderReg // 4. Refresh Secrets - bulk fetch and update all secrets from their providers and update the cache // -// Merging secretManager and providerRegistry -// Methods like createProviderInstance need to be moved to the providerRegistry - const encryptedStorage = new EncryptedFsStorageService(""); const providerRegistry = new FileBasedProviderRegistry(encryptedStorage, ""); @@ -77,7 +68,7 @@ export class SecretsManager { } async fetchSecret(providerId: string, ref: SecretReference): Promise { - this.registry.getProvider(providerId)?.fetchSecret(ref); + this.registry.getProvider(providerId)?.getSecret(ref); } async refreshSecrets( @@ -96,21 +87,4 @@ export class SecretsManager { const secret = await this.fetchSecret(s.providerId, s.ref); } } - - async listSecrets(providerId: string): Promise { - const provider = this.registry.getProvider(providerId); - if (!provider) { - throw new Error(`Provider not found: ${providerId}`); - } - - return provider.listSecrets(); - } - - async invalidateCache(providerId: string): Promise { - const provider = this.registry.getProvider(providerId); - if (!provider) { - throw new Error(`Provider not found: ${providerId}`); - } - await provider.invalidateCache(); - } } diff --git a/src/lib/secretsManager/types.ts b/src/lib/secretsManager/types.ts index 6b0a23f6..de87cae7 100644 --- a/src/lib/secretsManager/types.ts +++ b/src/lib/secretsManager/types.ts @@ -20,37 +20,13 @@ export interface SecretProviderConfig { config: ProviderSpecificConfig; } -export interface SecretReference { - identifier: string; +export type AwsSecretReference = { + type: SecretProviderType.AWS_SECRETS_MANAGER; + nameOrArn: string; version?: string; - key?: string; -} - - - -/** - * - * type SecretReference = - * | { type: "aws"; nameOrArn: string; versionId?: string; versionStage?: string } - * | { type: "vault"; path: string; version?: number; key?: string }; - */ - -ProviderCache: { - [key: string]: { - value: string; - ttl: number; - }[] -} = { - "foo": { - value: "bar", - ttl: 1000, - } -} - - - - +}; +export type SecretReference = AwsSecretReference; // | VaultSecretReference; // | OtherProviderSecretReference; export interface CachedSecret { id: string; // Unique identifier From 2bb53a6eaaafb40f211b2aa9b62ae6cbb0ed0b05 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Tue, 13 Jan 2026 14:22:48 +0530 Subject: [PATCH 12/37] fix --- src/lib/secretsManager/providerService/providerFactory.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/secretsManager/providerService/providerFactory.ts b/src/lib/secretsManager/providerService/providerFactory.ts index 70a68c8e..15b4bfb5 100644 --- a/src/lib/secretsManager/providerService/providerFactory.ts +++ b/src/lib/secretsManager/providerService/providerFactory.ts @@ -1,8 +1,10 @@ import { SecretProviderConfig, SecretProviderType } from "../types"; -import { ISecretProvider } from "./ISecretProvider"; import { AWSSecretsManagerProvider } from "./awsSecretManagerProvider"; +import { AbstractSecretProvider } from "./AbstractSecretProvider"; -export function createProvider(config: SecretProviderConfig): ISecretProvider { +export function createProvider( + config: SecretProviderConfig +): AbstractSecretProvider { switch (config.type) { case SecretProviderType.AWS_SECRETS_MANAGER: return new AWSSecretsManagerProvider(config); @@ -10,4 +12,3 @@ export function createProvider(config: SecretProviderConfig): ISecretProvider { throw new Error(`Unknown provider type: ${config.type}`); } } - From 36e1b45507ef9b100254dea114b7be6995cb8144 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Wed, 14 Jan 2026 13:25:32 +0530 Subject: [PATCH 13/37] poc code --- .../encryptedFsStorageService.ts | 44 +++++++++- .../AbstractProviderRegistry.ts | 2 +- .../providerRegistry/FsProviderRegistry.ts | 86 ++++++++++++++----- src/lib/secretsManager/secretsManager.ts | 6 +- src/main/events.js | 22 +++++ 5 files changed, 133 insertions(+), 27 deletions(-) diff --git a/src/lib/secretsManager/encryptedStorage/encryptedFsStorageService.ts b/src/lib/secretsManager/encryptedStorage/encryptedFsStorageService.ts index ad54941a..66fa3b0d 100644 --- a/src/lib/secretsManager/encryptedStorage/encryptedFsStorageService.ts +++ b/src/lib/secretsManager/encryptedStorage/encryptedFsStorageService.ts @@ -1,8 +1,23 @@ import { safeStorage } from "electron"; -import { IEncryptedStorage } from "./IEncryptedStorage"; +import { AbstractEncryptedStorage } from "./AbstractEncryptedStorage"; +import { + GLOBAL_CONFIG_FILE_NAME, + GLOBAL_CONFIG_FOLDER_PATH, +} from "../../../renderer/actions/local-sync/constants"; +import { + appendPath, + createFsResource, +} from "../../../renderer/actions/local-sync/common-utils"; +import { + createGlobalConfigFolder, + getIfFileExists, + getIfFolderExists, +} from "../../../renderer/actions/local-sync/fs-utils"; -export class EncryptedFsStorageService implements IEncryptedStorage { - constructor(private readonly baseFolderPath: string) {} +export class EncryptedFsStorageService extends AbstractEncryptedStorage { + constructor(private readonly baseFolderPath: string) { + super(); + } async initialize(): Promise { if (!safeStorage.isEncryptionAvailable()) { @@ -10,6 +25,29 @@ export class EncryptedFsStorageService implements IEncryptedStorage { throw new Error("Encryption is not available on this system. "); } + const globalConfigFolderResource = createFsResource({ + rootPath: GLOBAL_CONFIG_FOLDER_PATH, + path: GLOBAL_CONFIG_FOLDER_PATH, + type: "folder", + }); + const globalConfigFolderExists = await getIfFolderExists( + globalConfigFolderResource + ); + + if (!globalConfigFolderExists) { + await createGlobalConfigFolder(); + } + + const globalConfigFileResource = createFsResource({ + rootPath: GLOBAL_CONFIG_FOLDER_PATH, + path: appendPath(GLOBAL_CONFIG_FOLDER_PATH, GLOBAL_CONFIG_FILE_NAME), + type: "file", + }); + + const globalConfigFileExists = await getIfFileExists( + globalConfigFileResource + ); + // initialize directories } diff --git a/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts index 8b5fbcfc..606750ab 100644 --- a/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts @@ -3,7 +3,7 @@ import { AbstractEncryptedStorage } from "../encryptedStorage/AbstractEncryptedS import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider"; export interface ProvidersManifest { - version: string; + // version: string; providers: { id: string; storagePath: string; diff --git a/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts index 7631509c..4358556c 100644 --- a/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts @@ -2,19 +2,24 @@ import * as fs from "fs"; import * as path from "path"; import { SecretProviderConfig, SecretProviderType } from "../types"; import { createProvider } from "../providerService/providerFactory"; -import { AbstractProviderRegistry } from "./AbstractProviderRegistry"; +import { + AbstractProviderRegistry, + ProvidersManifest, +} from "./AbstractProviderRegistry"; import { AbstractEncryptedStorage } from "../encryptedStorage/AbstractEncryptedStorage"; import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider"; - -const MANIFEST_FILENAME = "providers.json"; - - - -// Functions -// 1. initialize registry (create config dir if not exists) -// 2. list providers -// 3. - +import { createFsResource } from "../../../renderer/actions/local-sync/common-utils"; +import { + createGlobalConfigFolder, + getIfFileExists, + getIfFolderExists, + parseFile, +} from "../../../renderer/actions/local-sync/fs-utils"; +import { GLOBAL_CONFIG_FILE_NAME } from "../../../renderer/actions/local-sync/constants"; +import { GlobalConfigRecordFileType } from "../../../renderer/actions/local-sync/file-types/file-types"; + +// TODO:@nafees check version of config.json +const MANIFEST_FILENAME = GLOBAL_CONFIG_FILE_NAME; export class FileBasedProviderRegistry extends AbstractProviderRegistry { private manifestPath: string; @@ -46,16 +51,18 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { async getAllProviderConfigs(): Promise { const manifest = await this.loadManifest(); + + console.log("!!!debug","manifest loaded",) const configs: SecretProviderConfig[] = []; - for (const entry of manifest.providers) { - const config = await this.encryptedStorage.load( - entry.id - ); - configs.push(config); - } + // for (const entry of manifest.providers) { + // const config = await this.encryptedStorage.load( + // entry.id + // ); + // configs.push(config); + // } - return configs; + // return configs; } async setProviderConfig(config: SecretProviderConfig): Promise { @@ -93,13 +100,52 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { private async ensureConfigDir(): Promise { try { - await fs.mkdir(this.configDir, { recursive: true }); + const globalConfigFolderResource = createFsResource({ + rootPath: this.configDir, + path: this.configDir, + type: "folder", + }); + const globalConfigFolderExists = await getIfFolderExists( + globalConfigFolderResource + ); + + if (!globalConfigFolderExists) { + await createGlobalConfigFolder(); + } } catch (error) { console.error("Failed to create config directory:", error); + throw new Error("Failed to create config directory."); } } - protected async loadManifest(): Promise {} + protected async loadManifest(): Promise { + const globalConfigFileResource = createFsResource({ + rootPath: this.configDir, + path: this.manifestPath, + type: "file", + }); + + const globalConfigFileExists = await getIfFileExists( + globalConfigFileResource + ); + + if (!globalConfigFileExists) { + return { providers: [] }; + } + + const readResult = await parseFile({ + resource: globalConfigFileResource, + fileType: new GlobalConfigRecordFileType(), + }); + + if (readResult.type === "error") { + throw new Error("Failed to parse manifest file."); + } + + console.log("!!!debug", "readResult", readResult); + // TODO:@nafees handle versioning and schema in schema.ts + return (readResult.content.providers ?? []) as ProvidersManifest; + } protected async saveManifest(manifest: ProvidersManifest): Promise {} diff --git a/src/lib/secretsManager/secretsManager.ts b/src/lib/secretsManager/secretsManager.ts index 5a299e94..6a3e434a 100644 --- a/src/lib/secretsManager/secretsManager.ts +++ b/src/lib/secretsManager/secretsManager.ts @@ -20,10 +20,10 @@ import { AbstractProviderRegistry } from "./providerRegistry/AbstractProviderReg // 4. Refresh Secrets - bulk fetch and update all secrets from their providers and update the cache // -const encryptedStorage = new EncryptedFsStorageService(""); -const providerRegistry = new FileBasedProviderRegistry(encryptedStorage, ""); +// const encryptedStorage = new EncryptedFsStorageService(""); +// const providerRegistry = new FileBasedProviderRegistry(encryptedStorage, ""); -const secretsManager = new SecretsManager(providerRegistry); +// const secretsManager = new SecretsManager(providerRegistry); // createProviderInstance should have cache Storage as dependency. // providerRegistry cannot be exposed because it would have storage specific code like fs methods etc. diff --git a/src/main/events.js b/src/main/events.js index 7aacdb54..fe05b5a1 100644 --- a/src/main/events.js +++ b/src/main/events.js @@ -23,6 +23,10 @@ import { createOrUpdateAxiosInstance } from "./actions/getProxiedAxios"; // and then build these utilites elsewhere // eslint-disable-next-line import/no-cycle import createTrayMenu from "./main"; +import { FileBasedProviderRegistry } from "../lib/secretsManager/providerRegistry/FsProviderRegistry"; +import { EncryptedFsStorageService } from "../lib/secretsManager/encryptedStorage/encryptedFsStorageService"; +import { GLOBAL_CONFIG_FOLDER_PATH } from "../renderer/actions/local-sync/constants"; +import { SecretsManager } from "../lib/secretsManager/secretsManager"; const getFileCategory = (fileExtension) => { switch (fileExtension) { @@ -269,6 +273,24 @@ export const registerMainProcessEventsForWebAppWindow = (webAppWindow) => { ipcMain.handle("helper-server-hit", () => { webAppWindow?.send("helper-server-hit"); }); + + ipcMain.handle("init-secretsManager", () => { + const encryptedStorage = new EncryptedFsStorageService( + path.join(GLOBAL_CONFIG_FOLDER_PATH, "providers") + ); + const registry = new FileBasedProviderRegistry( + encryptedStorage, + GLOBAL_CONFIG_FOLDER_PATH + ); + + const secretsManager = new SecretsManager(registry); + + try { + secretsManager.initialize(); + } catch (err) { + console.error("Error initializing Secrets Manager", err); + } + }); }; export const registerMainProcessCommonEvents = () => { From 3b34f7d9a08a7adc6545eb4c5b167e4b6b8f5d4d Mon Sep 17 00:00:00 2001 From: nafees87n Date: Wed, 14 Jan 2026 20:23:29 +0530 Subject: [PATCH 14/37] added initilization flow --- .../encryptedStorage/encryptedFsStorage.ts | 96 +++++++++++++ .../encryptedFsStorageService.ts | 64 --------- .../AbstractProviderRegistry.ts | 22 ++- ...gistry.ts => FileBasedProviderRegistry.ts} | 127 +++++++++++++----- .../providerService/providerFactory.ts | 2 +- src/lib/secretsManager/secretsManager.ts | 19 ++- src/main/events.js | 23 +++- src/renderer/actions/local-sync/constants.ts | 2 +- .../local-sync/file-types/file-types.ts | 12 +- src/renderer/actions/local-sync/fs-utils.ts | 59 ++++---- src/renderer/actions/local-sync/schemas.ts | 7 + 11 files changed, 276 insertions(+), 157 deletions(-) create mode 100644 src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts delete mode 100644 src/lib/secretsManager/encryptedStorage/encryptedFsStorageService.ts rename src/lib/secretsManager/providerRegistry/{FsProviderRegistry.ts => FileBasedProviderRegistry.ts} (51%) diff --git a/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts b/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts new file mode 100644 index 00000000..ca1ee27a --- /dev/null +++ b/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts @@ -0,0 +1,96 @@ +import { safeStorage } from "electron"; +import { AbstractEncryptedStorage } from "./AbstractEncryptedStorage"; +import { + appendPath, + createFsResource, +} from "../../../renderer/actions/local-sync/common-utils"; +import { + createFolder, + deleteFsResource, + getIfFolderExists, + parseFileRaw, + writeContentRaw, +} from "../../../renderer/actions/local-sync/fs-utils"; + +export class EncryptedFsStorage extends AbstractEncryptedStorage { + private readonly baseFolderPath: string; + + constructor(baseFolderPath: string) { + super(); + this.baseFolderPath = baseFolderPath; + } + + async initialize(): Promise { + if (!safeStorage.isEncryptionAvailable()) { + // Show trouble shooting steps to user + throw new Error("Encryption is not available on this system. "); + } + + if (!this.baseFolderPath) { + throw new Error("Base folder path is not set for EncryptedFsStorage."); + } + } + + async save>( + key: string, + data: T + ): Promise { + const stringifiedData = JSON.stringify(data); + const encryptedData = safeStorage.encryptString(stringifiedData); + + const fsFolderResource = createFsResource({ + rootPath: this.baseFolderPath, + path: this.baseFolderPath, + type: "folder", + }); + + const providerFolderExists = await getIfFolderExists(fsFolderResource); + + if (!providerFolderExists) { + await createFolder(fsFolderResource); + } + + const fsResource = createFsResource({ + rootPath: this.baseFolderPath, + path: appendPath(this.baseFolderPath, key), + type: "file", + }); + + try { + await writeContentRaw(fsResource, encryptedData.toString("base64")); + } catch (err) { + console.error("!!!debug", "Error writing encrypted data", err); + } + } + + async load>(key: string): Promise { + const fsResource = createFsResource({ + rootPath: this.baseFolderPath, + path: appendPath(this.baseFolderPath, key), + type: "file", + }); + const fileContent = await parseFileRaw({ + resource: fsResource, + }); + + if (fileContent.type === "error") { + throw new Error( + `Failed to load encrypted data for key: ${key}, error: ${fileContent.error.message}` + ); + } + + const encryptedBuffer = Buffer.from(fileContent.content, "base64"); + const decryptedString = safeStorage.decryptString(encryptedBuffer); + return JSON.parse(decryptedString) as T; + } + + async delete(key: string): Promise { + const fsResource = createFsResource({ + rootPath: this.baseFolderPath, + path: appendPath(this.baseFolderPath, key), + type: "file", + }); + + await deleteFsResource(fsResource); + } +} diff --git a/src/lib/secretsManager/encryptedStorage/encryptedFsStorageService.ts b/src/lib/secretsManager/encryptedStorage/encryptedFsStorageService.ts deleted file mode 100644 index 66fa3b0d..00000000 --- a/src/lib/secretsManager/encryptedStorage/encryptedFsStorageService.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { safeStorage } from "electron"; -import { AbstractEncryptedStorage } from "./AbstractEncryptedStorage"; -import { - GLOBAL_CONFIG_FILE_NAME, - GLOBAL_CONFIG_FOLDER_PATH, -} from "../../../renderer/actions/local-sync/constants"; -import { - appendPath, - createFsResource, -} from "../../../renderer/actions/local-sync/common-utils"; -import { - createGlobalConfigFolder, - getIfFileExists, - getIfFolderExists, -} from "../../../renderer/actions/local-sync/fs-utils"; - -export class EncryptedFsStorageService extends AbstractEncryptedStorage { - constructor(private readonly baseFolderPath: string) { - super(); - } - - async initialize(): Promise { - if (!safeStorage.isEncryptionAvailable()) { - // Show trouble shooting steps to user - throw new Error("Encryption is not available on this system. "); - } - - const globalConfigFolderResource = createFsResource({ - rootPath: GLOBAL_CONFIG_FOLDER_PATH, - path: GLOBAL_CONFIG_FOLDER_PATH, - type: "folder", - }); - const globalConfigFolderExists = await getIfFolderExists( - globalConfigFolderResource - ); - - if (!globalConfigFolderExists) { - await createGlobalConfigFolder(); - } - - const globalConfigFileResource = createFsResource({ - rootPath: GLOBAL_CONFIG_FOLDER_PATH, - path: appendPath(GLOBAL_CONFIG_FOLDER_PATH, GLOBAL_CONFIG_FILE_NAME), - type: "file", - }); - - const globalConfigFileExists = await getIfFileExists( - globalConfigFileResource - ); - - // initialize directories - } - - async save>( - key: string, - data: T - ): Promise { - // encrypted - } - - async load>(key: string): Promise {} - - async delete(key: string): Promise {} -} diff --git a/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts index 606750ab..51aba344 100644 --- a/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts @@ -2,14 +2,12 @@ import { SecretProviderConfig, SecretProviderType } from "../types"; import { AbstractEncryptedStorage } from "../encryptedStorage/AbstractEncryptedStorage"; import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider"; -export interface ProvidersManifest { - // version: string; - providers: { - id: string; - storagePath: string; - type: SecretProviderType; - }[]; -} +type ProviderManifestItem = { + id: string; + type: SecretProviderType; +}; + +export type ProviderManifest = ProviderManifestItem[]; export abstract class AbstractProviderRegistry { protected encryptedStorage: AbstractEncryptedStorage; @@ -22,13 +20,9 @@ export abstract class AbstractProviderRegistry { abstract initialize(): Promise; - protected abstract createProviderInstance( - config: SecretProviderConfig - ): AbstractSecretProvider; - - protected abstract loadManifest(): Promise; + protected abstract loadManifest(): Promise; - protected abstract saveManifest(manifest: ProvidersManifest): Promise; + protected abstract saveManifest(manifest: ProviderManifest): Promise; abstract getAllProviderConfigs(): Promise; diff --git a/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts similarity index 51% rename from src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts rename to src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts index 4358556c..1e4e9451 100644 --- a/src/lib/secretsManager/providerRegistry/FsProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts @@ -1,10 +1,9 @@ -import * as fs from "fs"; import * as path from "path"; -import { SecretProviderConfig, SecretProviderType } from "../types"; -import { createProvider } from "../providerService/providerFactory"; +import { SecretProviderConfig } from "../types"; +import { createProviderInstance } from "../providerService/providerFactory"; import { AbstractProviderRegistry, - ProvidersManifest, + ProviderManifest, } from "./AbstractProviderRegistry"; import { AbstractEncryptedStorage } from "../encryptedStorage/AbstractEncryptedStorage"; import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider"; @@ -14,9 +13,15 @@ import { getIfFileExists, getIfFolderExists, parseFile, + writeToGlobalConfig, } from "../../../renderer/actions/local-sync/fs-utils"; -import { GLOBAL_CONFIG_FILE_NAME } from "../../../renderer/actions/local-sync/constants"; +import { + CORE_CONFIG_FILE_VERSION, + GLOBAL_CONFIG_FILE_NAME, +} from "../../../renderer/actions/local-sync/constants"; import { GlobalConfigRecordFileType } from "../../../renderer/actions/local-sync/file-types/file-types"; +import { Static } from "@sinclair/typebox"; +import { GlobalConfig } from "../../../renderer/actions/local-sync/schemas"; // TODO:@nafees check version of config.json const MANIFEST_FILENAME = GLOBAL_CONFIG_FILE_NAME; @@ -39,60 +44,67 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { async initialize(): Promise { await this.ensureConfigDir(); + await this.ensureConfigFile(); this.initProvidersFromManifest(); } private async initProvidersFromManifest() { const configs = await this.getAllProviderConfigs(); configs.forEach((config) => { - this.providers.set(config.id, this.createProviderInstance(config)); + this.providers.set(config.id, createProviderInstance(config)); }); } async getAllProviderConfigs(): Promise { - const manifest = await this.loadManifest(); + const providerManifest = await this.loadManifest(); - console.log("!!!debug","manifest loaded",) + console.log("!!!debug", "manifest loaded",providerManifest); const configs: SecretProviderConfig[] = []; - // for (const entry of manifest.providers) { - // const config = await this.encryptedStorage.load( - // entry.id - // ); - // configs.push(config); - // } + for (const entry of providerManifest) { + const config = await this.encryptedStorage.load( + entry.id + ); + configs.push(config); + } - // return configs; + console.log("!!!debug", "all configs", configs); + return configs; } async setProviderConfig(config: SecretProviderConfig): Promise { - const manifest = await this.loadManifest(); const storageKey = config.id; - // do atomic write await this.encryptedStorage.save(storageKey, config); - // Update manifest + const manifest = await this.loadManifest(); + const existingEntryIndex = manifest.findIndex((p) => p.id === config.id); + if (existingEntryIndex !== -1) { + manifest[existingEntryIndex] = { id: config.id, type: config.type }; + } else { + manifest.push({ id: config.id, type: config.type }); + } await this.saveManifest(manifest); - this.providers.set(config.id, this.createProviderInstance(config)); + this.providers.set(config.id, createProviderInstance(config)); } async deleteProviderConfig(id: string): Promise { - const manifest = await this.loadManifest(); - const entry = manifest.providers.find((p) => p.id === id); + const providerManifest = await this.loadManifest(); + const entry = providerManifest.find((p) => p.id === id); if (!entry) return; await this.encryptedStorage.delete(id); - manifest.providers = manifest.providers.filter((p) => p.id !== id); - await this.saveManifest(manifest); + providerManifest.splice(providerManifest.indexOf(entry), 1); + + await this.saveManifest(providerManifest); this.providers.delete(id); } async getProviderConfig(id: string): Promise { - const manifest = await this.loadManifest(); - const entry = manifest.providers.find((p) => p.id === id); + const providerManifest = await this.loadManifest(); + const entry = providerManifest.find((p) => p.id === id); if (!entry) return null; return this.encryptedStorage.load(id); @@ -118,7 +130,7 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { } } - protected async loadManifest(): Promise { + private async ensureConfigFile(): Promise { const globalConfigFileResource = createFsResource({ rootPath: this.configDir, path: this.manifestPath, @@ -130,7 +142,32 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { ); if (!globalConfigFileExists) { - return { providers: [] }; + const config: Static = { + version: CORE_CONFIG_FILE_VERSION, + workspaces: [], + providers: [], + }; + const writeResult = await writeToGlobalConfig(config); + + if (writeResult.type === "error") { + throw new Error("Failed to create manifest file."); + } + } + } + + protected async loadManifest(): Promise { + const globalConfigFileResource = createFsResource({ + rootPath: this.configDir, + path: this.manifestPath, + type: "file", + }); + + const globalConfigFileExists = await getIfFileExists( + globalConfigFileResource + ); + + if (!globalConfigFileExists) { + return []; } const readResult = await parseFile({ @@ -144,15 +181,37 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { console.log("!!!debug", "readResult", readResult); // TODO:@nafees handle versioning and schema in schema.ts - return (readResult.content.providers ?? []) as ProvidersManifest; + return (readResult.content.providers ?? []) as ProviderManifest; } - protected async saveManifest(manifest: ProvidersManifest): Promise {} + protected async saveManifest( + providerManifest: ProviderManifest + ): Promise { + const globalConfigFileResource = createFsResource({ + rootPath: this.configDir, + path: this.manifestPath, + type: "file", + }); - // eslint-disable-next-line class-methods-use-this - protected createProviderInstance( - config: SecretProviderConfig - ): AbstractSecretProvider { - return createProvider(config); + const readResult = await parseFile({ + resource: globalConfigFileResource, + fileType: new GlobalConfigRecordFileType(), + }); + + if (readResult.type === "error") { + throw new Error("Failed to parse manifest file."); + } + + const updatedConfig: Static = { + version: readResult.content.version || CORE_CONFIG_FILE_VERSION, + workspaces: readResult.content.workspaces || [], + providers: providerManifest, + }; + + const writeResult = await writeToGlobalConfig(updatedConfig); + + if (writeResult.type === "error") { + throw new Error("Failed to write manifest file."); + } } } diff --git a/src/lib/secretsManager/providerService/providerFactory.ts b/src/lib/secretsManager/providerService/providerFactory.ts index 15b4bfb5..00e108ed 100644 --- a/src/lib/secretsManager/providerService/providerFactory.ts +++ b/src/lib/secretsManager/providerService/providerFactory.ts @@ -2,7 +2,7 @@ import { SecretProviderConfig, SecretProviderType } from "../types"; import { AWSSecretsManagerProvider } from "./awsSecretManagerProvider"; import { AbstractSecretProvider } from "./AbstractSecretProvider"; -export function createProvider( +export function createProviderInstance( config: SecretProviderConfig ): AbstractSecretProvider { switch (config.type) { diff --git a/src/lib/secretsManager/secretsManager.ts b/src/lib/secretsManager/secretsManager.ts index 6a3e434a..55765e0e 100644 --- a/src/lib/secretsManager/secretsManager.ts +++ b/src/lib/secretsManager/secretsManager.ts @@ -1,6 +1,4 @@ import { SecretProviderConfig, SecretReference } from "./types"; -import { EncryptedFsStorageService } from "./encryptedStorage/encryptedFsStorageService"; -import { FileBasedProviderRegistry } from "./providerRegistry/FsProviderRegistry"; import { AbstractProviderRegistry } from "./providerRegistry/AbstractProviderRegistry"; // Questions @@ -51,6 +49,7 @@ export class SecretsManager { } async addProviderConfig(config: SecretProviderConfig) { + console.log("!!!debug", "addconfig", config); this.registry.setProviderConfig(config); } @@ -71,14 +70,14 @@ export class SecretsManager { this.registry.getProvider(providerId)?.getSecret(ref); } - async refreshSecrets( - secrets: Array<{ providerId: string; ref: SecretReference }> - ): Promise { - for (const s of secrets) { - // Invalidate cache - // Fetch fresh secret and update cache - } - } + // async refreshSecrets( + // secrets: Array<{ providerId: string; ref: SecretReference }> + // ): Promise { + // for (const s of secrets) { + // // Invalidate cache + // // Fetch fresh secret and update cache + // } + // } async fetchSecrets( secrets: Array<{ providerId: string; ref: SecretReference }> diff --git a/src/main/events.js b/src/main/events.js index fe05b5a1..8d3a864e 100644 --- a/src/main/events.js +++ b/src/main/events.js @@ -23,10 +23,10 @@ import { createOrUpdateAxiosInstance } from "./actions/getProxiedAxios"; // and then build these utilites elsewhere // eslint-disable-next-line import/no-cycle import createTrayMenu from "./main"; -import { FileBasedProviderRegistry } from "../lib/secretsManager/providerRegistry/FsProviderRegistry"; -import { EncryptedFsStorageService } from "../lib/secretsManager/encryptedStorage/encryptedFsStorageService"; import { GLOBAL_CONFIG_FOLDER_PATH } from "../renderer/actions/local-sync/constants"; import { SecretsManager } from "../lib/secretsManager/secretsManager"; +import { EncryptedFsStorage } from "../lib/secretsManager/encryptedStorage/encryptedFsStorage"; +import { FileBasedProviderRegistry } from "../lib/secretsManager/providerRegistry/FileBasedProviderRegistry"; const getFileCategory = (fileExtension) => { switch (fileExtension) { @@ -274,8 +274,10 @@ export const registerMainProcessEventsForWebAppWindow = (webAppWindow) => { webAppWindow?.send("helper-server-hit"); }); + let secretsManager = null; + ipcMain.handle("init-secretsManager", () => { - const encryptedStorage = new EncryptedFsStorageService( + const encryptedStorage = new EncryptedFsStorage( path.join(GLOBAL_CONFIG_FOLDER_PATH, "providers") ); const registry = new FileBasedProviderRegistry( @@ -283,7 +285,7 @@ export const registerMainProcessEventsForWebAppWindow = (webAppWindow) => { GLOBAL_CONFIG_FOLDER_PATH ); - const secretsManager = new SecretsManager(registry); + secretsManager = new SecretsManager(registry); try { secretsManager.initialize(); @@ -291,6 +293,19 @@ export const registerMainProcessEventsForWebAppWindow = (webAppWindow) => { console.error("Error initializing Secrets Manager", err); } }); + + ipcMain.handle("secretsManager:addProviderConfig", async (event, config) => { + await secretsManager.addProviderConfig(config); + }); + + ipcMain.handle("secretsManager:getProviderConfig", async (event, id) => { + const providerConfig = await secretsManager.getProviderConfig(id); + console.log("!!!debug", "getConfig", providerConfig); + }); + + ipcMain.handle("secretsManager:removeProviderConfig", async (event, id) => { + await secretsManager.removeProviderConfig(id); + }); }; export const registerMainProcessCommonEvents = () => { diff --git a/src/renderer/actions/local-sync/constants.ts b/src/renderer/actions/local-sync/constants.ts index 370df04b..6a66eece 100644 --- a/src/renderer/actions/local-sync/constants.ts +++ b/src/renderer/actions/local-sync/constants.ts @@ -1,6 +1,6 @@ import { homedir } from "os"; -export const CORE_CONFIG_FILE_VERSION = 0.1; +export const CORE_CONFIG_FILE_VERSION = 0.2; export const WORKSPACE_CONFIG_FILE_VERSION = "0.0.3"; export const CONFIG_FILE = "requestly.json"; export const COLLECTION_AUTH_FILE = "auth.json"; diff --git a/src/renderer/actions/local-sync/file-types/file-types.ts b/src/renderer/actions/local-sync/file-types/file-types.ts index cf00c812..44594d24 100644 --- a/src/renderer/actions/local-sync/file-types/file-types.ts +++ b/src/renderer/actions/local-sync/file-types/file-types.ts @@ -13,7 +13,7 @@ import { FileTypeEnum } from "../types"; import { parseJsonContent, parseRaw } from "../common-utils"; export class ApiRecordFileType extends FileType { - validator = ApiRecord; + validator: typeof ApiRecord = ApiRecord; type = FileTypeEnum.API; @@ -25,7 +25,7 @@ export class ApiRecordFileType extends FileType { export class EnvironmentRecordFileType extends FileType< typeof EnvironmentRecord > { - validator = EnvironmentRecord; + validator: typeof EnvironmentRecord = EnvironmentRecord; type = FileTypeEnum.ENVIRONMENT; @@ -37,7 +37,7 @@ export class EnvironmentRecordFileType extends FileType< export class CollectionVariablesRecordFileType extends FileType< typeof Variables > { - validator = Variables; + validator: typeof Variables = Variables; type = FileTypeEnum.COLLECTION_VARIABLES; @@ -47,7 +47,7 @@ export class CollectionVariablesRecordFileType extends FileType< } export class ReadmeRecordFileType extends FileType { - validator = Description; + validator: typeof Description = Description; type = FileTypeEnum.DESCRIPTION; @@ -57,7 +57,7 @@ export class ReadmeRecordFileType extends FileType { } export class AuthRecordFileType extends FileType { - validator = Auth; + validator: typeof Auth = Auth; type = FileTypeEnum.AUTH; @@ -67,7 +67,7 @@ export class AuthRecordFileType extends FileType { } export class GlobalConfigRecordFileType extends FileType { - validator = GlobalConfig; + validator: typeof GlobalConfig = GlobalConfig; type = FileTypeEnum.GLOBAL_CONFIG; diff --git a/src/renderer/actions/local-sync/fs-utils.ts b/src/renderer/actions/local-sync/fs-utils.ts index 0dbc4373..e5eadda7 100644 --- a/src/renderer/actions/local-sync/fs-utils.ts +++ b/src/renderer/actions/local-sync/fs-utils.ts @@ -111,8 +111,8 @@ export async function deleteFsResource( } await FsService.unlink(resource.path); fileIndex.remove({ - type: 'path', - path: resource.path + type: "path", + path: resource.path, }); } else { const exists = await getIfFolderExists(resource); @@ -126,8 +126,8 @@ export async function deleteFsResource( } await FsService.rmdir(resource.path, { recursive: true }); fileIndex.remove({ - type: 'path', - path: resource.path + type: "path", + path: resource.path, }); } return { @@ -170,8 +170,6 @@ export async function createFolder( } else { fileIndex.getId(resource.path); } - - } else if (errorIfExist) { return createFileSystemError( { message: "Folder already exists!" }, @@ -196,21 +194,23 @@ export async function rename( ): Promise> { try { const alreadyExists = await (async () => { - if (newResource.type === 'folder') { + if (newResource.type === "folder") { return getIfFolderExists(newResource); } return getIfFileExists(newResource); })(); - const isSamePath = getNormalizedPath(oldResource.path).toLowerCase() === getNormalizedPath(newResource.path).toLowerCase(); + const isSamePath = + getNormalizedPath(oldResource.path).toLowerCase() === + getNormalizedPath(newResource.path).toLowerCase(); if (!isSamePath && alreadyExists) { return { - type: 'error', + type: "error", error: { - message: 'Entity already exists!', + message: "Entity already exists!", fileType: FileTypeEnum.UNKNOWN, path: newResource.path, code: ErrorCode.EntityAlreadyExists, - } + }, }; } await FsService.rename(oldResource.path, newResource.path); @@ -259,19 +259,20 @@ export async function writeContent( } ): Promise> { try { - const { writeWithElevatedAccess = false, performExistenceCheck = false } = options || {}; + const { writeWithElevatedAccess = false, performExistenceCheck = false } = + options || {}; if (performExistenceCheck) { const alreadyExists = await getIfFileExists(resource); if (alreadyExists) { return { - type: 'error', + type: "error", error: { - message: 'Entity already exists!', + message: "Entity already exists!", fileType: fileType.type, path: resource.path, code: ErrorCode.EntityAlreadyExists, - } + }, }; } } @@ -430,7 +431,7 @@ export async function parseFileRaw(params: { /* NOTE: This is the ONLY function that should write to the global config file. All global config mutations must go through this writer to avoid partial writes & concurrency issues. */ -async function writeToGlobalConfig( +export async function writeToGlobalConfig( config: Static ): Promise> { const originalPath = appendPath( @@ -527,6 +528,7 @@ export async function addWorkspaceToGlobalConfig(params: { const config: Static = { version: CORE_CONFIG_FILE_VERSION, workspaces: [newWorkspace], + providers: [], }; const writeResult = await writeToGlobalConfig(config); if (writeResult.type === "error") { @@ -548,6 +550,7 @@ export async function addWorkspaceToGlobalConfig(params: { } const updatedConfig = { + ...readResult.content, version: readResult.content.version, workspaces: [...readResult.content.workspaces, newWorkspace], }; @@ -613,6 +616,15 @@ export async function migrateGlobalConfig(oldConfig: any) { return { version: CORE_CONFIG_FILE_VERSION, workspaces: oldConfig, + providers: [], + }; + } + + if (oldConfig.version === 0.1) { + return { + version: CORE_CONFIG_FILE_VERSION, + workspaces: oldConfig.workspaces, + providers: [], }; } @@ -621,14 +633,14 @@ export async function migrateGlobalConfig(oldConfig: any) { type WorkspaceValidationResult = | { - valid: true; - ws: Static["workspaces"][number]; - } + valid: true; + ws: Static["workspaces"][number]; + } | { - valid: false; - ws: Static["workspaces"][number]; - error: { message: string }; - }; + valid: false; + ws: Static["workspaces"][number]; + error: { message: string }; + }; async function validateWorkspace( ws: Static["workspaces"][number] @@ -730,6 +742,7 @@ export async function getAllWorkspaces(): Promise< const updatedConfig: Static = { version: effectiveConfig.version || CORE_CONFIG_FILE_VERSION, workspaces: valid, + providers: effectiveConfig.providers || [], }; const pruneWriteResult = await writeToGlobalConfig(updatedConfig); if (pruneWriteResult.type === "error") { diff --git a/src/renderer/actions/local-sync/schemas.ts b/src/renderer/actions/local-sync/schemas.ts index d7eeaeb6..9d8d21ba 100644 --- a/src/renderer/actions/local-sync/schemas.ts +++ b/src/renderer/actions/local-sync/schemas.ts @@ -201,4 +201,11 @@ export const GlobalConfig = Type.Object({ path: Type.String(), }) ), + // TODO:@nafees Should this be optional? + providers: Type.Array( + Type.Object({ + id: Type.String(), + type: Type.String(), + }) + ), }); From bdb67fbc279fc64ec11ef095b9eb66debf91d0ee Mon Sep 17 00:00:00 2001 From: nafees87n Date: Thu, 15 Jan 2026 23:40:59 +0530 Subject: [PATCH 15/37] remove unused code --- .../awsSecretManagerProvider.ts | 80 +------------------ src/lib/secretsManager/secretsManager.ts | 59 +------------- 2 files changed, 2 insertions(+), 137 deletions(-) diff --git a/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts b/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts index 68587403..a20629c4 100644 --- a/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts +++ b/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts @@ -1,82 +1,4 @@ /* eslint-disable class-methods-use-this */ -import { - AwsSecretReference, - AWSSecretsManagerConfig, - CachedSecret, - SecretProviderConfig, - SecretProviderType, - SecretReference, -} from "../types"; import { AbstractSecretProvider } from "./AbstractSecretProvider"; -// Functions -// 1. validate config -// 2. test connection -// 3. fetch secret -// 4. list secrets - -const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes - -export class AWSSecretsManagerProvider extends AbstractSecretProvider { - readonly type = SecretProviderType.AWS_SECRETS_MANAGER; - - readonly id: string; - - protected config: AWSSecretsManagerConfig; - - protected cache: Map = new Map(); - - protected getSecretIdentfier(ref: AwsSecretReference): string { - return `name=${ref.nameOrArn};version:${ref.version}`; - } - - constructor(providerConfig: SecretProviderConfig) { - super(); - this.id = providerConfig.id; - this.config = providerConfig.config as AWSSecretsManagerConfig; - } - - async testConnection(): Promise { - if (!AWSSecretsManagerProvider.validateConfig(this.config)) { - return false; - } - - return true; - } - - async getSecret(ref: AwsSecretReference): Promise { - const secretKey = this.getSecretIdentfier(ref); - const cachedSecret = this.cache.get(secretKey); - const now = Date.now(); - - if (cachedSecret && cachedSecret.expiry > now) { - return cachedSecret.value; - } - - // Fetch from AWS Secrets Manager - const secretValue = "fetched-secret-value"; // Placeholder for actual fetch logic - - this.cache.set(secretKey, { - value: secretValue, - expiry: now + DEFAULT_CACHE_TTL_MS, - }); - - return secretValue; - } - - async getSecrets(): Promise {} - - async setSecret(): Promise { - throw new Error("Method not implemented."); - } - - async setSecrets(): Promise { - throw new Error("Method not implemented."); - } - - static validateConfig(config: AWSSecretsManagerConfig): boolean { - return Boolean( - config.accessKeyId && config.secretAccessKey && config.region - ); - } -} +export class AWSSecretsManagerProvider extends AbstractSecretProvider {} diff --git a/src/lib/secretsManager/secretsManager.ts b/src/lib/secretsManager/secretsManager.ts index 55765e0e..9557be32 100644 --- a/src/lib/secretsManager/secretsManager.ts +++ b/src/lib/secretsManager/secretsManager.ts @@ -1,42 +1,6 @@ -import { SecretProviderConfig, SecretReference } from "./types"; +import { SecretProviderConfig } from "./types"; import { AbstractProviderRegistry } from "./providerRegistry/AbstractProviderRegistry"; -// Questions -// 6. // providerId in fetchByIdentifier ?? would be confusing and then I can name it key but without using "key" word. How can I make it generic? AI suggested using composite key. - -// Functions -// 1. initialize registry and cache service -// 2. add/remove provider configs -// 3. fetch secret (with caching) -// 4. list secrets -// 5. invalidate cache - -// FLows -// 1. INIT - load all provider configs from the registry and create provider instances. -// 2. ADD/REMOVE PROVIDER CONFIG - update the registry and provider instances map. -// 3. FETCH SECRET - check cache first, if not found or expired, fetch from provider, store in cache, return secret. -// 4. Refresh Secrets - bulk fetch and update all secrets from their providers and update the cache -// - -// const encryptedStorage = new EncryptedFsStorageService(""); -// const providerRegistry = new FileBasedProviderRegistry(encryptedStorage, ""); - -// const secretsManager = new SecretsManager(providerRegistry); - -// createProviderInstance should have cache Storage as dependency. -// providerRegistry cannot be exposed because it would have storage specific code like fs methods etc. - -// Why need to change cache storage at provider lever? -// Different providers may have different cache storage requirements but why? -// Different providers can have different source of truth but caching should be common. WHy not? -// But agreed provider should be the one interacting with cache storage and repo layer and not secretmanager - -// Changes -// 1. cacheService associated with provider instance instead of secretManager. -// 2. registry manages the providers map -// 3. All the methods from secretManager delegated to registry and provider instances. -// 4. provider instance creation is moved to registry. - export class SecretsManager { private registry: AbstractProviderRegistry; @@ -65,25 +29,4 @@ export class SecretsManager { const provider = this.registry.getProvider(id); return provider?.testConnection() ?? false; } - - async fetchSecret(providerId: string, ref: SecretReference): Promise { - this.registry.getProvider(providerId)?.getSecret(ref); - } - - // async refreshSecrets( - // secrets: Array<{ providerId: string; ref: SecretReference }> - // ): Promise { - // for (const s of secrets) { - // // Invalidate cache - // // Fetch fresh secret and update cache - // } - // } - - async fetchSecrets( - secrets: Array<{ providerId: string; ref: SecretReference }> - ): Promise> { - for (const s of secrets) { - const secret = await this.fetchSecret(s.providerId, s.ref); - } - } } From 696891908ea9231bcdb079d1644e71965de6e02c Mon Sep 17 00:00:00 2001 From: nafees87n Date: Fri, 16 Jan 2026 12:06:30 +0530 Subject: [PATCH 16/37] fix --- .../providerRegistry/FileBasedProviderRegistry.ts | 12 +++++++++--- src/lib/secretsManager/secretsManager.ts | 11 +++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts index 1e4e9451..fd1e4507 100644 --- a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts @@ -45,7 +45,7 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { async initialize(): Promise { await this.ensureConfigDir(); await this.ensureConfigFile(); - this.initProvidersFromManifest(); + await this.initProvidersFromManifest(); } private async initProvidersFromManifest() { @@ -58,14 +58,20 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { async getAllProviderConfigs(): Promise { const providerManifest = await this.loadManifest(); - console.log("!!!debug", "manifest loaded",providerManifest); + console.log("!!!debug", "manifest loaded", providerManifest); const configs: SecretProviderConfig[] = []; for (const entry of providerManifest) { const config = await this.encryptedStorage.load( entry.id ); - configs.push(config); + + if (config) { + configs.push(config); + } else { + // Should we throw error for this case? + console.log("!!!debug", "Config not found for entry", entry); + } } console.log("!!!debug", "all configs", configs); diff --git a/src/lib/secretsManager/secretsManager.ts b/src/lib/secretsManager/secretsManager.ts index 9557be32..b92f9192 100644 --- a/src/lib/secretsManager/secretsManager.ts +++ b/src/lib/secretsManager/secretsManager.ts @@ -9,24 +9,19 @@ export class SecretsManager { } async initialize(): Promise { - this.registry.initialize(); + await this.registry.initialize(); } async addProviderConfig(config: SecretProviderConfig) { console.log("!!!debug", "addconfig", config); - this.registry.setProviderConfig(config); + await this.registry.setProviderConfig(config); } async removeProviderConfig(id: string) { - this.registry.deleteProviderConfig(id); + await this.registry.deleteProviderConfig(id); } async getProviderConfig(id: string): Promise { return this.registry.getProviderConfig(id); } - - async testProviderConnection(id: string): Promise { - const provider = this.registry.getProvider(id); - return provider?.testConnection() ?? false; - } } From 1756a2109f82fbca6d5b34498d54d7d6295dd37f Mon Sep 17 00:00:00 2001 From: nafees87n Date: Tue, 20 Jan 2026 16:27:20 +0530 Subject: [PATCH 17/37] fix: encrypted storage init --- src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts | 1 + .../secretsManager/providerRegistry/FileBasedProviderRegistry.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts b/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts index ca1ee27a..d245c8f4 100644 --- a/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts +++ b/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts @@ -23,6 +23,7 @@ export class EncryptedFsStorage extends AbstractEncryptedStorage { async initialize(): Promise { if (!safeStorage.isEncryptionAvailable()) { // Show trouble shooting steps to user + // Create a custom error for this throw new Error("Encryption is not available on this system. "); } diff --git a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts index fd1e4507..0bf735a1 100644 --- a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts @@ -43,6 +43,7 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { } async initialize(): Promise { + await this.encryptedStorage.initialize(); await this.ensureConfigDir(); await this.ensureConfigFile(); await this.initProvidersFromManifest(); From e60389966b3499eeaa4d7a394f931de85f48b720 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Tue, 20 Jan 2026 16:51:49 +0530 Subject: [PATCH 18/37] fix: error --- .../encryptedStorage/AbstractEncryptedStorage.ts | 2 +- .../encryptedStorage/encryptedFsStorage.ts | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/lib/secretsManager/encryptedStorage/AbstractEncryptedStorage.ts b/src/lib/secretsManager/encryptedStorage/AbstractEncryptedStorage.ts index d3a43082..5a3bc321 100644 --- a/src/lib/secretsManager/encryptedStorage/AbstractEncryptedStorage.ts +++ b/src/lib/secretsManager/encryptedStorage/AbstractEncryptedStorage.ts @@ -6,7 +6,7 @@ export abstract class AbstractEncryptedStorage { data: T ): Promise; - abstract load>(key: string): Promise; + abstract load>(key: string): Promise; abstract delete(key: string): Promise; } diff --git a/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts b/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts index d245c8f4..b43f1952 100644 --- a/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts +++ b/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts @@ -7,6 +7,7 @@ import { import { createFolder, deleteFsResource, + getIfFileExists, getIfFolderExists, parseFileRaw, writeContentRaw, @@ -64,17 +65,24 @@ export class EncryptedFsStorage extends AbstractEncryptedStorage { } } - async load>(key: string): Promise { + async load>(key: string): Promise { const fsResource = createFsResource({ rootPath: this.baseFolderPath, path: appendPath(this.baseFolderPath, key), type: "file", }); + + const fileExists = await getIfFileExists(fsResource); + if (!fileExists) { + return null; + } + const fileContent = await parseFileRaw({ resource: fsResource, }); if (fileContent.type === "error") { + // File exists but couldn't be read - this is an actual error throw new Error( `Failed to load encrypted data for key: ${key}, error: ${fileContent.error.message}` ); @@ -82,7 +90,11 @@ export class EncryptedFsStorage extends AbstractEncryptedStorage { const encryptedBuffer = Buffer.from(fileContent.content, "base64"); const decryptedString = safeStorage.decryptString(encryptedBuffer); - return JSON.parse(decryptedString) as T; + try { + return JSON.parse(decryptedString) as T; + } catch (err) { + throw new Error(`Failed to parse decrypted data for key`) + } } async delete(key: string): Promise { From 4375589b6a99ee3ba5017acb63cc7ec8d2da7fa7 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Tue, 20 Jan 2026 16:54:56 +0530 Subject: [PATCH 19/37] fix: type --- .../secretsManager/providerService/AbstractSecretProvider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/secretsManager/providerService/AbstractSecretProvider.ts b/src/lib/secretsManager/providerService/AbstractSecretProvider.ts index 6902918c..b58b49a2 100644 --- a/src/lib/secretsManager/providerService/AbstractSecretProvider.ts +++ b/src/lib/secretsManager/providerService/AbstractSecretProvider.ts @@ -1,4 +1,4 @@ -import { CachedSecret, SecretProviderType, SecretReference } from "../types"; +import { CachedSecret, ProviderSpecificConfig, SecretProviderType, SecretReference } from "../types"; export abstract class AbstractSecretProvider { protected cache: Map = new Map(); @@ -7,7 +7,7 @@ export abstract class AbstractSecretProvider { abstract readonly id: string; - protected config: any; + protected config: ProviderSpecificConfig; protected abstract getSecretIdentfier(ref: SecretReference): string; From 04682842571a25ab26e8b1447967f7de7bc5c96f Mon Sep 17 00:00:00 2001 From: nafees87n Date: Tue, 20 Jan 2026 17:10:03 +0530 Subject: [PATCH 20/37] fix: schemas --- src/renderer/actions/local-sync/schemas.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/actions/local-sync/schemas.ts b/src/renderer/actions/local-sync/schemas.ts index 9d8d21ba..9a1d8a97 100644 --- a/src/renderer/actions/local-sync/schemas.ts +++ b/src/renderer/actions/local-sync/schemas.ts @@ -1,6 +1,7 @@ /* eslint-disable no-shadow */ /* eslint-disable no-unused-vars */ import { Type } from "@sinclair/typebox"; +import { SecretProviderType } from "lib/secretsManager/types"; export const Config = Type.Object({ version: Type.String(), @@ -205,7 +206,7 @@ export const GlobalConfig = Type.Object({ providers: Type.Array( Type.Object({ id: Type.String(), - type: Type.String(), + type: Type.Enum(SecretProviderType), }) ), }); From c0b5a9360e6d10bc3bc73ecf91d5d94fa249bbb0 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Tue, 20 Jan 2026 17:52:03 +0530 Subject: [PATCH 21/37] fix: config read/write --- .../FileBasedProviderRegistry.ts | 74 ++++++++++--------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts index 0bf735a1..bddd994c 100644 --- a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts @@ -9,22 +9,22 @@ import { AbstractEncryptedStorage } from "../encryptedStorage/AbstractEncryptedS import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider"; import { createFsResource } from "../../../renderer/actions/local-sync/common-utils"; import { - createGlobalConfigFolder, + createFolder, getIfFileExists, getIfFolderExists, - parseFile, - writeToGlobalConfig, + parseFileRaw, + writeContentRaw, } from "../../../renderer/actions/local-sync/fs-utils"; import { CORE_CONFIG_FILE_VERSION, GLOBAL_CONFIG_FILE_NAME, } from "../../../renderer/actions/local-sync/constants"; -import { GlobalConfigRecordFileType } from "../../../renderer/actions/local-sync/file-types/file-types"; import { Static } from "@sinclair/typebox"; -import { GlobalConfig } from "../../../renderer/actions/local-sync/schemas"; +import { GlobalConfig } from "renderer/actions/local-sync/schemas"; // TODO:@nafees check version of config.json const MANIFEST_FILENAME = GLOBAL_CONFIG_FILE_NAME; + export class FileBasedProviderRegistry extends AbstractProviderRegistry { private manifestPath: string; @@ -119,17 +119,15 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { private async ensureConfigDir(): Promise { try { - const globalConfigFolderResource = createFsResource({ + const configDirResource = createFsResource({ rootPath: this.configDir, path: this.configDir, type: "folder", }); - const globalConfigFolderExists = await getIfFolderExists( - globalConfigFolderResource - ); + const configDirExists = await getIfFolderExists(configDirResource); - if (!globalConfigFolderExists) { - await createGlobalConfigFolder(); + if (!configDirExists) { + await createFolder(configDirResource); } } catch (error) { console.error("Failed to create config directory:", error); @@ -138,23 +136,22 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { } private async ensureConfigFile(): Promise { - const globalConfigFileResource = createFsResource({ + const configFileResource = createFsResource({ rootPath: this.configDir, path: this.manifestPath, type: "file", }); - const globalConfigFileExists = await getIfFileExists( - globalConfigFileResource - ); + const configFileExists = await getIfFileExists(configFileResource); - if (!globalConfigFileExists) { + if (!configFileExists) { const config: Static = { version: CORE_CONFIG_FILE_VERSION, workspaces: [], providers: [], }; - const writeResult = await writeToGlobalConfig(config); + + const writeResult = await writeContentRaw(configFileResource, config); if (writeResult.type === "error") { throw new Error("Failed to create manifest file."); @@ -163,23 +160,22 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { } protected async loadManifest(): Promise { - const globalConfigFileResource = createFsResource({ + const configFileResource = createFsResource({ rootPath: this.configDir, path: this.manifestPath, type: "file", }); - const globalConfigFileExists = await getIfFileExists( - globalConfigFileResource + const configFileExists = await getIfFileExists( + configFileResource ); - if (!globalConfigFileExists) { + if (!configFileExists) { return []; } - const readResult = await parseFile({ - resource: globalConfigFileResource, - fileType: new GlobalConfigRecordFileType(), + const readResult = await parseFileRaw({ + resource: configFileResource, }); if (readResult.type === "error") { @@ -187,35 +183,47 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { } console.log("!!!debug", "readResult", readResult); - // TODO:@nafees handle versioning and schema in schema.ts - return (readResult.content.providers ?? []) as ProviderManifest; + + try { + const manifest = JSON.parse(readResult.content) as ProviderManifest; + // TODO:@nafees handle versioning and schema in schema.ts + return (manifest ?? []) as ProviderManifest; + } catch (err) { + throw new Error("Failed to parse manifest file: Invalid JSON."); + } } protected async saveManifest( providerManifest: ProviderManifest ): Promise { - const globalConfigFileResource = createFsResource({ + const configFileResource = createFsResource({ rootPath: this.configDir, path: this.manifestPath, type: "file", }); - const readResult = await parseFile({ - resource: globalConfigFileResource, - fileType: new GlobalConfigRecordFileType(), + const readResult = await parseFileRaw({ + resource: configFileResource, }); if (readResult.type === "error") { throw new Error("Failed to parse manifest file."); } + const manifest = JSON.parse(readResult.content) as Static< + typeof GlobalConfig + >; + const updatedConfig: Static = { - version: readResult.content.version || CORE_CONFIG_FILE_VERSION, - workspaces: readResult.content.workspaces || [], + version: manifest.version || CORE_CONFIG_FILE_VERSION, + workspaces: manifest.workspaces || [], providers: providerManifest, }; - const writeResult = await writeToGlobalConfig(updatedConfig); + const writeResult = await writeContentRaw( + configFileResource, + updatedConfig + ); if (writeResult.type === "error") { throw new Error("Failed to write manifest file."); From 3faf236b8a6fbf008a1491fa87e0cd2659b8bca9 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Tue, 20 Jan 2026 17:57:48 +0530 Subject: [PATCH 22/37] fix: provider reading --- .../providerRegistry/FileBasedProviderRegistry.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts index bddd994c..bf6ddb0c 100644 --- a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts @@ -166,9 +166,7 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { type: "file", }); - const configFileExists = await getIfFileExists( - configFileResource - ); + const configFileExists = await getIfFileExists(configFileResource); if (!configFileExists) { return []; @@ -185,9 +183,10 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { console.log("!!!debug", "readResult", readResult); try { - const manifest = JSON.parse(readResult.content) as ProviderManifest; + const config = JSON.parse(readResult.content); + const manifest = config.providers as ProviderManifest; // TODO:@nafees handle versioning and schema in schema.ts - return (manifest ?? []) as ProviderManifest; + return manifest ?? []; } catch (err) { throw new Error("Failed to parse manifest file: Invalid JSON."); } From ae99164bf9ff67864e544d58444d6635e4715f7b Mon Sep 17 00:00:00 2001 From: nafees87n Date: Tue, 20 Jan 2026 18:05:05 +0530 Subject: [PATCH 23/37] added key sanitization --- .../encryptedStorage/encryptedFsStorage.ts | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts b/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts index b43f1952..f4aeb092 100644 --- a/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts +++ b/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts @@ -13,6 +13,25 @@ import { writeContentRaw, } from "../../../renderer/actions/local-sync/fs-utils"; +const sanitizeKey = (key: string): string => { + if (!key) { + throw new Error("Key cannot be empty"); + } + + // Remove any path separators and directory traversal sequences + const sanitized = key + .replace(/\.\./g, "") // Remove ".." + .replace(/[/\\]/g, "_") // Replace path separators with underscore + .replace(/^\.+/, "") // Remove leading dots + .trim(); + + if (!sanitized) { + throw new Error("Key contains only invalid characters"); + } + + return sanitized; +}; + export class EncryptedFsStorage extends AbstractEncryptedStorage { private readonly baseFolderPath: string; @@ -37,6 +56,7 @@ export class EncryptedFsStorage extends AbstractEncryptedStorage { key: string, data: T ): Promise { + const sanitizedKey = sanitizeKey(key); const stringifiedData = JSON.stringify(data); const encryptedData = safeStorage.encryptString(stringifiedData); @@ -54,7 +74,7 @@ export class EncryptedFsStorage extends AbstractEncryptedStorage { const fsResource = createFsResource({ rootPath: this.baseFolderPath, - path: appendPath(this.baseFolderPath, key), + path: appendPath(this.baseFolderPath, sanitizedKey), type: "file", }); @@ -66,9 +86,10 @@ export class EncryptedFsStorage extends AbstractEncryptedStorage { } async load>(key: string): Promise { + const sanitizedKey = sanitizeKey(key); const fsResource = createFsResource({ rootPath: this.baseFolderPath, - path: appendPath(this.baseFolderPath, key), + path: appendPath(this.baseFolderPath, sanitizedKey), type: "file", }); @@ -93,14 +114,15 @@ export class EncryptedFsStorage extends AbstractEncryptedStorage { try { return JSON.parse(decryptedString) as T; } catch (err) { - throw new Error(`Failed to parse decrypted data for key`) + throw new Error(`Failed to parse decrypted data for key`); } } async delete(key: string): Promise { + const sanitizedKey = sanitizeKey(key); const fsResource = createFsResource({ rootPath: this.baseFolderPath, - path: appendPath(this.baseFolderPath, key), + path: appendPath(this.baseFolderPath, sanitizedKey), type: "file", }); From d8b746239e5a51026410dc08ffdfefebcc7fd032 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Tue, 20 Jan 2026 18:22:39 +0530 Subject: [PATCH 24/37] fix: deletion result --- .../secretsManager/encryptedStorage/encryptedFsStorage.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts b/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts index f4aeb092..41179cb4 100644 --- a/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts +++ b/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts @@ -114,7 +114,7 @@ export class EncryptedFsStorage extends AbstractEncryptedStorage { try { return JSON.parse(decryptedString) as T; } catch (err) { - throw new Error(`Failed to parse decrypted data for key`); + throw new Error(`Failed to parse decrypted data for key: ${key}`); } } @@ -126,6 +126,10 @@ export class EncryptedFsStorage extends AbstractEncryptedStorage { type: "file", }); - await deleteFsResource(fsResource); + const fsResult = await deleteFsResource(fsResource); + + if (fsResult.type === "error") { + throw new Error(`Failed to delete encrypted data for key: ${key}`); + } } } From 9ae079bce0e53fcf4adca232aa7529cb303b63a3 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Tue, 20 Jan 2026 23:33:49 +0530 Subject: [PATCH 25/37] fix: orphaned indexes in manifest --- .../providerRegistry/FileBasedProviderRegistry.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts index bf6ddb0c..2fa09abb 100644 --- a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts @@ -62,6 +62,8 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { console.log("!!!debug", "manifest loaded", providerManifest); const configs: SecretProviderConfig[] = []; + const orphanedIndexes: number[] = []; + for (const entry of providerManifest) { const config = await this.encryptedStorage.load( entry.id @@ -71,10 +73,22 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { configs.push(config); } else { // Should we throw error for this case? + // TODO: Manifest orphaned entry needs to be cleanup up + // Clean up orphaned entry from manifest + const orphanedIndex = providerManifest.findIndex((p) => p.id === entry.id); + if (orphanedIndex !== -1) { + orphanedIndexes.push(orphanedIndex); + } + console.log("!!!debug", "Config not found for entry", entry); } } + if (orphanedIndexes.length > 0) { + const updatedManifest = providerManifest.filter((_, index) => !orphanedIndexes.includes(index)); + await this.saveManifest(updatedManifest); + } + console.log("!!!debug", "all configs", configs); return configs; } From 8de17563657070ed26ab890127f9964817e4f2a0 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Wed, 21 Jan 2026 16:07:53 +0530 Subject: [PATCH 26/37] use electron-store --- .../AbstractEncryptedStorage.ts | 12 - .../AbstractSecretsManagerStorage.ts | 11 + .../SecretsManagerEncryptedStorage.ts | 29 +++ .../encryptedStorage/encryptedFsStorage.ts | 135 ---------- .../AbstractProviderRegistry.ts | 29 +-- .../FileBasedProviderRegistry.ts | 234 ++---------------- src/lib/storage/EncryptedElectronStore.ts | 125 ++++++++++ src/main/events.js | 13 +- 8 files changed, 193 insertions(+), 395 deletions(-) delete mode 100644 src/lib/secretsManager/encryptedStorage/AbstractEncryptedStorage.ts create mode 100644 src/lib/secretsManager/encryptedStorage/AbstractSecretsManagerStorage.ts create mode 100644 src/lib/secretsManager/encryptedStorage/SecretsManagerEncryptedStorage.ts delete mode 100644 src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts create mode 100644 src/lib/storage/EncryptedElectronStore.ts diff --git a/src/lib/secretsManager/encryptedStorage/AbstractEncryptedStorage.ts b/src/lib/secretsManager/encryptedStorage/AbstractEncryptedStorage.ts deleted file mode 100644 index 5a3bc321..00000000 --- a/src/lib/secretsManager/encryptedStorage/AbstractEncryptedStorage.ts +++ /dev/null @@ -1,12 +0,0 @@ -export abstract class AbstractEncryptedStorage { - abstract initialize(): Promise; - - abstract save>( - key: string, - data: T - ): Promise; - - abstract load>(key: string): Promise; - - abstract delete(key: string): Promise; -} diff --git a/src/lib/secretsManager/encryptedStorage/AbstractSecretsManagerStorage.ts b/src/lib/secretsManager/encryptedStorage/AbstractSecretsManagerStorage.ts new file mode 100644 index 00000000..fb601918 --- /dev/null +++ b/src/lib/secretsManager/encryptedStorage/AbstractSecretsManagerStorage.ts @@ -0,0 +1,11 @@ +import { SecretProviderConfig } from "../types"; + +export abstract class AbstractSecretsManagerStorage { + abstract set(_key: string, _data: SecretProviderConfig): Promise; + + abstract get(_key: string): Promise; + + abstract getAll(): Promise; + + abstract delete(_key: string): Promise; +} diff --git a/src/lib/secretsManager/encryptedStorage/SecretsManagerEncryptedStorage.ts b/src/lib/secretsManager/encryptedStorage/SecretsManagerEncryptedStorage.ts new file mode 100644 index 00000000..25953de5 --- /dev/null +++ b/src/lib/secretsManager/encryptedStorage/SecretsManagerEncryptedStorage.ts @@ -0,0 +1,29 @@ +import { AbstractSecretsManagerStorage } from "./AbstractSecretsManagerStorage"; +import { EncryptedElectronStore } from "../../storage/EncryptedElectronStore"; +import { SecretProviderConfig } from "../types"; + +export class SecretsManagerEncryptedStorage extends AbstractSecretsManagerStorage { + private encryptedStore: EncryptedElectronStore; + + constructor(storeName: string, schema: any) { + super(); + this.encryptedStore = new EncryptedElectronStore(storeName, schema); + } + + async set(key: string, data: SecretProviderConfig): Promise { + return this.encryptedStore.set(key, data); + } + + async get(key: string): Promise { + return this.encryptedStore.get(key); + } + + async getAll(): Promise { + const allData = this.encryptedStore.getAll(); + return Object.values(allData); + } + + async delete(key: string): Promise { + return this.encryptedStore.delete(key); + } +} diff --git a/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts b/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts deleted file mode 100644 index 41179cb4..00000000 --- a/src/lib/secretsManager/encryptedStorage/encryptedFsStorage.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { safeStorage } from "electron"; -import { AbstractEncryptedStorage } from "./AbstractEncryptedStorage"; -import { - appendPath, - createFsResource, -} from "../../../renderer/actions/local-sync/common-utils"; -import { - createFolder, - deleteFsResource, - getIfFileExists, - getIfFolderExists, - parseFileRaw, - writeContentRaw, -} from "../../../renderer/actions/local-sync/fs-utils"; - -const sanitizeKey = (key: string): string => { - if (!key) { - throw new Error("Key cannot be empty"); - } - - // Remove any path separators and directory traversal sequences - const sanitized = key - .replace(/\.\./g, "") // Remove ".." - .replace(/[/\\]/g, "_") // Replace path separators with underscore - .replace(/^\.+/, "") // Remove leading dots - .trim(); - - if (!sanitized) { - throw new Error("Key contains only invalid characters"); - } - - return sanitized; -}; - -export class EncryptedFsStorage extends AbstractEncryptedStorage { - private readonly baseFolderPath: string; - - constructor(baseFolderPath: string) { - super(); - this.baseFolderPath = baseFolderPath; - } - - async initialize(): Promise { - if (!safeStorage.isEncryptionAvailable()) { - // Show trouble shooting steps to user - // Create a custom error for this - throw new Error("Encryption is not available on this system. "); - } - - if (!this.baseFolderPath) { - throw new Error("Base folder path is not set for EncryptedFsStorage."); - } - } - - async save>( - key: string, - data: T - ): Promise { - const sanitizedKey = sanitizeKey(key); - const stringifiedData = JSON.stringify(data); - const encryptedData = safeStorage.encryptString(stringifiedData); - - const fsFolderResource = createFsResource({ - rootPath: this.baseFolderPath, - path: this.baseFolderPath, - type: "folder", - }); - - const providerFolderExists = await getIfFolderExists(fsFolderResource); - - if (!providerFolderExists) { - await createFolder(fsFolderResource); - } - - const fsResource = createFsResource({ - rootPath: this.baseFolderPath, - path: appendPath(this.baseFolderPath, sanitizedKey), - type: "file", - }); - - try { - await writeContentRaw(fsResource, encryptedData.toString("base64")); - } catch (err) { - console.error("!!!debug", "Error writing encrypted data", err); - } - } - - async load>(key: string): Promise { - const sanitizedKey = sanitizeKey(key); - const fsResource = createFsResource({ - rootPath: this.baseFolderPath, - path: appendPath(this.baseFolderPath, sanitizedKey), - type: "file", - }); - - const fileExists = await getIfFileExists(fsResource); - if (!fileExists) { - return null; - } - - const fileContent = await parseFileRaw({ - resource: fsResource, - }); - - if (fileContent.type === "error") { - // File exists but couldn't be read - this is an actual error - throw new Error( - `Failed to load encrypted data for key: ${key}, error: ${fileContent.error.message}` - ); - } - - const encryptedBuffer = Buffer.from(fileContent.content, "base64"); - const decryptedString = safeStorage.decryptString(encryptedBuffer); - try { - return JSON.parse(decryptedString) as T; - } catch (err) { - throw new Error(`Failed to parse decrypted data for key: ${key}`); - } - } - - async delete(key: string): Promise { - const sanitizedKey = sanitizeKey(key); - const fsResource = createFsResource({ - rootPath: this.baseFolderPath, - path: appendPath(this.baseFolderPath, sanitizedKey), - type: "file", - }); - - const fsResult = await deleteFsResource(fsResource); - - if (fsResult.type === "error") { - throw new Error(`Failed to delete encrypted data for key: ${key}`); - } - } -} diff --git a/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts index 51aba344..3d7850df 100644 --- a/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts @@ -1,36 +1,25 @@ -import { SecretProviderConfig, SecretProviderType } from "../types"; -import { AbstractEncryptedStorage } from "../encryptedStorage/AbstractEncryptedStorage"; +import { SecretProviderConfig } from "../types"; +import { AbstractSecretsManagerStorage } from "../encryptedStorage/AbstractSecretsManagerStorage"; import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider"; -type ProviderManifestItem = { - id: string; - type: SecretProviderType; -}; - -export type ProviderManifest = ProviderManifestItem[]; - export abstract class AbstractProviderRegistry { - protected encryptedStorage: AbstractEncryptedStorage; + protected store: AbstractSecretsManagerStorage; protected providers: Map = new Map(); - constructor(encryptedStorage: AbstractEncryptedStorage) { - this.encryptedStorage = encryptedStorage; + constructor(store: AbstractSecretsManagerStorage) { + this.store = store; } abstract initialize(): Promise; - protected abstract loadManifest(): Promise; - - protected abstract saveManifest(manifest: ProviderManifest): Promise; - abstract getAllProviderConfigs(): Promise; - abstract getProviderConfig(id: string): Promise; + abstract getProviderConfig(_id: string): Promise; - abstract setProviderConfig(config: SecretProviderConfig): Promise; + abstract setProviderConfig(_config: SecretProviderConfig): Promise; - abstract deleteProviderConfig(id: string): Promise; + abstract deleteProviderConfig(_id: string): Promise; - abstract getProvider(providerId: string): AbstractSecretProvider | null; + abstract getProvider(_providerId: string): AbstractSecretProvider | null; } diff --git a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts index 2fa09abb..ac86dc8d 100644 --- a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts @@ -1,55 +1,14 @@ -import * as path from "path"; import { SecretProviderConfig } from "../types"; import { createProviderInstance } from "../providerService/providerFactory"; -import { - AbstractProviderRegistry, - ProviderManifest, -} from "./AbstractProviderRegistry"; -import { AbstractEncryptedStorage } from "../encryptedStorage/AbstractEncryptedStorage"; import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider"; -import { createFsResource } from "../../../renderer/actions/local-sync/common-utils"; -import { - createFolder, - getIfFileExists, - getIfFolderExists, - parseFileRaw, - writeContentRaw, -} from "../../../renderer/actions/local-sync/fs-utils"; -import { - CORE_CONFIG_FILE_VERSION, - GLOBAL_CONFIG_FILE_NAME, -} from "../../../renderer/actions/local-sync/constants"; -import { Static } from "@sinclair/typebox"; -import { GlobalConfig } from "renderer/actions/local-sync/schemas"; - -// TODO:@nafees check version of config.json -const MANIFEST_FILENAME = GLOBAL_CONFIG_FILE_NAME; - -export class FileBasedProviderRegistry extends AbstractProviderRegistry { - private manifestPath: string; - - private configDir: string; - - protected providers: Map = new Map(); - - constructor(encryptedStorage: AbstractEncryptedStorage, configDir: string) { - super(encryptedStorage); - this.configDir = configDir; - this.manifestPath = path.join(configDir, MANIFEST_FILENAME); - } - - getProvider(providerId: string): AbstractSecretProvider | null { - return this.providers.get(providerId) || null; - } +import { AbstractProviderRegistry } from "./AbstractProviderRegistry"; +export class ProviderRegistry extends AbstractProviderRegistry { async initialize(): Promise { - await this.encryptedStorage.initialize(); - await this.ensureConfigDir(); - await this.ensureConfigFile(); - await this.initProvidersFromManifest(); + await this.initProvidersFromStorage(); } - private async initProvidersFromManifest() { + private async initProvidersFromStorage(): Promise { const configs = await this.getAllProviderConfigs(); configs.forEach((config) => { this.providers.set(config.id, createProviderInstance(config)); @@ -57,189 +16,30 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { } async getAllProviderConfigs(): Promise { - const providerManifest = await this.loadManifest(); - - console.log("!!!debug", "manifest loaded", providerManifest); - const configs: SecretProviderConfig[] = []; - - const orphanedIndexes: number[] = []; - - for (const entry of providerManifest) { - const config = await this.encryptedStorage.load( - entry.id - ); - - if (config) { - configs.push(config); - } else { - // Should we throw error for this case? - // TODO: Manifest orphaned entry needs to be cleanup up - // Clean up orphaned entry from manifest - const orphanedIndex = providerManifest.findIndex((p) => p.id === entry.id); - if (orphanedIndex !== -1) { - orphanedIndexes.push(orphanedIndex); - } - - console.log("!!!debug", "Config not found for entry", entry); - } - } - - if (orphanedIndexes.length > 0) { - const updatedManifest = providerManifest.filter((_, index) => !orphanedIndexes.includes(index)); - await this.saveManifest(updatedManifest); - } - - console.log("!!!debug", "all configs", configs); - return configs; - } - - async setProviderConfig(config: SecretProviderConfig): Promise { - const storageKey = config.id; - - await this.encryptedStorage.save(storageKey, config); - - const manifest = await this.loadManifest(); - const existingEntryIndex = manifest.findIndex((p) => p.id === config.id); - if (existingEntryIndex !== -1) { - manifest[existingEntryIndex] = { id: config.id, type: config.type }; - } else { - manifest.push({ id: config.id, type: config.type }); - } - - await this.saveManifest(manifest); - this.providers.set(config.id, createProviderInstance(config)); - } - - async deleteProviderConfig(id: string): Promise { - const providerManifest = await this.loadManifest(); - const entry = providerManifest.find((p) => p.id === id); - if (!entry) return; - - await this.encryptedStorage.delete(id); - - providerManifest.splice(providerManifest.indexOf(entry), 1); - - await this.saveManifest(providerManifest); - this.providers.delete(id); + const allConfigs = this.store.getAll(); + return allConfigs; } async getProviderConfig(id: string): Promise { - const providerManifest = await this.loadManifest(); - const entry = providerManifest.find((p) => p.id === id); - if (!entry) return null; - - return this.encryptedStorage.load(id); - } - - private async ensureConfigDir(): Promise { try { - const configDirResource = createFsResource({ - rootPath: this.configDir, - path: this.configDir, - type: "folder", - }); - const configDirExists = await getIfFolderExists(configDirResource); - - if (!configDirExists) { - await createFolder(configDirResource); - } + return await this.store.get(id); } catch (error) { - console.error("Failed to create config directory:", error); - throw new Error("Failed to create config directory."); + console.error(`Failed to load provider config for id: ${id}`, error); + return null; } } - private async ensureConfigFile(): Promise { - const configFileResource = createFsResource({ - rootPath: this.configDir, - path: this.manifestPath, - type: "file", - }); - - const configFileExists = await getIfFileExists(configFileResource); - - if (!configFileExists) { - const config: Static = { - version: CORE_CONFIG_FILE_VERSION, - workspaces: [], - providers: [], - }; - - const writeResult = await writeContentRaw(configFileResource, config); - - if (writeResult.type === "error") { - throw new Error("Failed to create manifest file."); - } - } + async setProviderConfig(config: SecretProviderConfig): Promise { + await this.store.set(config.id, config); + this.providers.set(config.id, createProviderInstance(config)); } - protected async loadManifest(): Promise { - const configFileResource = createFsResource({ - rootPath: this.configDir, - path: this.manifestPath, - type: "file", - }); - - const configFileExists = await getIfFileExists(configFileResource); - - if (!configFileExists) { - return []; - } - - const readResult = await parseFileRaw({ - resource: configFileResource, - }); - - if (readResult.type === "error") { - throw new Error("Failed to parse manifest file."); - } - - console.log("!!!debug", "readResult", readResult); - - try { - const config = JSON.parse(readResult.content); - const manifest = config.providers as ProviderManifest; - // TODO:@nafees handle versioning and schema in schema.ts - return manifest ?? []; - } catch (err) { - throw new Error("Failed to parse manifest file: Invalid JSON."); - } + async deleteProviderConfig(id: string): Promise { + await this.store.delete(id); + this.providers.delete(id); } - protected async saveManifest( - providerManifest: ProviderManifest - ): Promise { - const configFileResource = createFsResource({ - rootPath: this.configDir, - path: this.manifestPath, - type: "file", - }); - - const readResult = await parseFileRaw({ - resource: configFileResource, - }); - - if (readResult.type === "error") { - throw new Error("Failed to parse manifest file."); - } - - const manifest = JSON.parse(readResult.content) as Static< - typeof GlobalConfig - >; - - const updatedConfig: Static = { - version: manifest.version || CORE_CONFIG_FILE_VERSION, - workspaces: manifest.workspaces || [], - providers: providerManifest, - }; - - const writeResult = await writeContentRaw( - configFileResource, - updatedConfig - ); - - if (writeResult.type === "error") { - throw new Error("Failed to write manifest file."); - } + getProvider(providerId: string): AbstractSecretProvider | null { + return this.providers.get(providerId) ?? null; } } diff --git a/src/lib/storage/EncryptedElectronStore.ts b/src/lib/storage/EncryptedElectronStore.ts new file mode 100644 index 00000000..ef8bfa20 --- /dev/null +++ b/src/lib/storage/EncryptedElectronStore.ts @@ -0,0 +1,125 @@ +import Store from "electron-store"; +import { safeStorage } from "electron"; + +interface EncryptedStoreSchema { + version: number; + data: Record; // { [key]: base64 encrypted value } +} + +const STORE_VERSION = 1; + +/** + * Generic encrypted key-value storage using electron-store + Electron's safeStorage. + * - OS-level encryption via safeStorage (Keychain/DPAPI/libsecret) + * + * Storage location: /storage/.json + * + * Structure: + * ```json + * { + * "version": 1, + * "data": { + * "key1": "", + * "key2": "" + * } + * } + * ``` + */ +export class EncryptedElectronStore { + private store: Store; + + constructor(storeName: string, schema?: any) { + if (!safeStorage.isEncryptionAvailable()) { + throw new Error( + "Encryption is not available on this system. Please ensure your operating system's secure storage is properly configured." + ); + } + + const storeOptions: Store.Options = { + name: storeName, + cwd: "storage", + serialize: (data) => { + const jsonString = JSON.stringify(data); + const encrypted = safeStorage.encryptString(jsonString); + const base64 = encrypted.toString("base64"); + return base64; + }, + deserialize: (data) => { + const encryptedBuffer = Buffer.from(data, "base64"); + const decrypted = safeStorage.decryptString(encryptedBuffer); + return JSON.parse(decrypted) as EncryptedStoreSchema; + }, + defaults: { + version: STORE_VERSION, + data: {}, + }, + }; + + if (schema) { + storeOptions.schema = schema; + } + + this.store = new Store(storeOptions); + } + + set(key: string, data: T) { + this.store.set(`data.${key}`, data); + } + + get(key: string): T | null { + const data = this.store.get(`data.${key}`) as T; + return data ?? null; + } + + getAll(): T { + return this.store.get("data") as T; + } + + delete(key: string): void { + this.store.delete(`data.${key}` as keyof EncryptedStoreSchema); + } + + /** + * Check if a key exists in storage. + * + * @param key - Storage key + * @returns true if key exists, false otherwise + */ + has(key: string): boolean { + return this.store.has(`data.${key}` as keyof EncryptedStoreSchema); + } + + /** + * Get all keys in storage. + * + * @returns Array of all storage keys + */ + keys(): string[] { + return Object.keys(this.store.get("data")); + } + + /** + * Clear all data from storage. + */ + clear(): void { + this.store.set("data", {}); + } + + /** + * Registers a callback for when storage changes. + * + * @param callback - Function to call when data changes + * @returns Unsubscribe function + */ + onChange(callback: (_data: Record) => void): () => void { + return this.store.onDidChange("data", (newValue) => { + if (newValue) { + callback(newValue); + } + }); + } + + getStore(): Store { + return this.store; + } +} diff --git a/src/main/events.js b/src/main/events.js index 8d3a864e..e2c06a01 100644 --- a/src/main/events.js +++ b/src/main/events.js @@ -23,10 +23,8 @@ import { createOrUpdateAxiosInstance } from "./actions/getProxiedAxios"; // and then build these utilites elsewhere // eslint-disable-next-line import/no-cycle import createTrayMenu from "./main"; -import { GLOBAL_CONFIG_FOLDER_PATH } from "../renderer/actions/local-sync/constants"; import { SecretsManager } from "../lib/secretsManager/secretsManager"; -import { EncryptedFsStorage } from "../lib/secretsManager/encryptedStorage/encryptedFsStorage"; -import { FileBasedProviderRegistry } from "../lib/secretsManager/providerRegistry/FileBasedProviderRegistry"; +import { ProviderRegistry } from "../lib/secretsManager/providerRegistry/FileBasedProviderRegistry"; const getFileCategory = (fileExtension) => { switch (fileExtension) { @@ -277,14 +275,7 @@ export const registerMainProcessEventsForWebAppWindow = (webAppWindow) => { let secretsManager = null; ipcMain.handle("init-secretsManager", () => { - const encryptedStorage = new EncryptedFsStorage( - path.join(GLOBAL_CONFIG_FOLDER_PATH, "providers") - ); - const registry = new FileBasedProviderRegistry( - encryptedStorage, - GLOBAL_CONFIG_FOLDER_PATH - ); - + const registry = new ProviderRegistry(); secretsManager = new SecretsManager(registry); try { From 56c2b4baec329fea5dd6e1d5bc11ab6da708384f Mon Sep 17 00:00:00 2001 From: nafees87n Date: Wed, 21 Jan 2026 16:10:29 +0530 Subject: [PATCH 27/37] fix: initialization --- .../encryptedStorage/SecretsManagerEncryptedStorage.ts | 4 ++-- src/lib/storage/EncryptedElectronStore.ts | 6 +----- src/main/events.js | 4 +++- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/lib/secretsManager/encryptedStorage/SecretsManagerEncryptedStorage.ts b/src/lib/secretsManager/encryptedStorage/SecretsManagerEncryptedStorage.ts index 25953de5..1e134633 100644 --- a/src/lib/secretsManager/encryptedStorage/SecretsManagerEncryptedStorage.ts +++ b/src/lib/secretsManager/encryptedStorage/SecretsManagerEncryptedStorage.ts @@ -5,9 +5,9 @@ import { SecretProviderConfig } from "../types"; export class SecretsManagerEncryptedStorage extends AbstractSecretsManagerStorage { private encryptedStore: EncryptedElectronStore; - constructor(storeName: string, schema: any) { + constructor(storeName: string) { super(); - this.encryptedStore = new EncryptedElectronStore(storeName, schema); + this.encryptedStore = new EncryptedElectronStore(storeName); } async set(key: string, data: SecretProviderConfig): Promise { diff --git a/src/lib/storage/EncryptedElectronStore.ts b/src/lib/storage/EncryptedElectronStore.ts index ef8bfa20..8d9de4c9 100644 --- a/src/lib/storage/EncryptedElectronStore.ts +++ b/src/lib/storage/EncryptedElectronStore.ts @@ -28,7 +28,7 @@ const STORE_VERSION = 1; export class EncryptedElectronStore { private store: Store; - constructor(storeName: string, schema?: any) { + constructor(storeName: string) { if (!safeStorage.isEncryptionAvailable()) { throw new Error( "Encryption is not available on this system. Please ensure your operating system's secure storage is properly configured." @@ -55,10 +55,6 @@ export class EncryptedElectronStore { }, }; - if (schema) { - storeOptions.schema = schema; - } - this.store = new Store(storeOptions); } diff --git a/src/main/events.js b/src/main/events.js index e2c06a01..a0976888 100644 --- a/src/main/events.js +++ b/src/main/events.js @@ -25,6 +25,7 @@ import { createOrUpdateAxiosInstance } from "./actions/getProxiedAxios"; import createTrayMenu from "./main"; import { SecretsManager } from "../lib/secretsManager/secretsManager"; import { ProviderRegistry } from "../lib/secretsManager/providerRegistry/FileBasedProviderRegistry"; +import { SecretsManagerEncryptedStorage } from "lib/secretsManager/encryptedStorage/SecretsManagerEncryptedStorage"; const getFileCategory = (fileExtension) => { switch (fileExtension) { @@ -275,7 +276,8 @@ export const registerMainProcessEventsForWebAppWindow = (webAppWindow) => { let secretsManager = null; ipcMain.handle("init-secretsManager", () => { - const registry = new ProviderRegistry(); + const secretsStore = new SecretsManagerEncryptedStorage("providers"); + const registry = new ProviderRegistry(secretsStore); secretsManager = new SecretsManager(registry); try { From 0f93b01bdf035293014a461788c57133bc0b7f1b Mon Sep 17 00:00:00 2001 From: nafees87n Date: Wed, 21 Jan 2026 16:12:46 +0530 Subject: [PATCH 28/37] fix: file name --- .../providerRegistry/FileBasedProviderRegistry.ts | 2 +- src/main/events.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts index ac86dc8d..1113f33c 100644 --- a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts @@ -3,7 +3,7 @@ import { createProviderInstance } from "../providerService/providerFactory"; import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider"; import { AbstractProviderRegistry } from "./AbstractProviderRegistry"; -export class ProviderRegistry extends AbstractProviderRegistry { +export class FileBasedProviderRegistry extends AbstractProviderRegistry { async initialize(): Promise { await this.initProvidersFromStorage(); } diff --git a/src/main/events.js b/src/main/events.js index a0976888..a1ac4107 100644 --- a/src/main/events.js +++ b/src/main/events.js @@ -24,7 +24,7 @@ import { createOrUpdateAxiosInstance } from "./actions/getProxiedAxios"; // eslint-disable-next-line import/no-cycle import createTrayMenu from "./main"; import { SecretsManager } from "../lib/secretsManager/secretsManager"; -import { ProviderRegistry } from "../lib/secretsManager/providerRegistry/FileBasedProviderRegistry"; +import { FileBasedProviderRegistry } from "../lib/secretsManager/providerRegistry/FileBasedProviderRegistry"; import { SecretsManagerEncryptedStorage } from "lib/secretsManager/encryptedStorage/SecretsManagerEncryptedStorage"; const getFileCategory = (fileExtension) => { @@ -277,7 +277,7 @@ export const registerMainProcessEventsForWebAppWindow = (webAppWindow) => { ipcMain.handle("init-secretsManager", () => { const secretsStore = new SecretsManagerEncryptedStorage("providers"); - const registry = new ProviderRegistry(secretsStore); + const registry = new FileBasedProviderRegistry(secretsStore); secretsManager = new SecretsManager(registry); try { From 11f1ede99d082b2fd0e035df9d34c40b1e4a4791 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Wed, 21 Jan 2026 16:58:35 +0530 Subject: [PATCH 29/37] fix: encryption code --- src/lib/storage/EncryptedElectronStore.ts | 34 ++++++----------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/src/lib/storage/EncryptedElectronStore.ts b/src/lib/storage/EncryptedElectronStore.ts index 8d9de4c9..2a323c67 100644 --- a/src/lib/storage/EncryptedElectronStore.ts +++ b/src/lib/storage/EncryptedElectronStore.ts @@ -3,7 +3,7 @@ import { safeStorage } from "electron"; interface EncryptedStoreSchema { version: number; - data: Record; // { [key]: base64 encrypted value } + data: Record; } const STORE_VERSION = 1; @@ -12,18 +12,8 @@ const STORE_VERSION = 1; * Generic encrypted key-value storage using electron-store + Electron's safeStorage. * - OS-level encryption via safeStorage (Keychain/DPAPI/libsecret) * - * Storage location: /storage/.json + * Storage location: /storage/.txt * - * Structure: - * ```json - * { - * "version": 1, - * "data": { - * "key1": "", - * "key2": "" - * } - * } - * ``` */ export class EncryptedElectronStore { private store: Store; @@ -38,6 +28,8 @@ export class EncryptedElectronStore { const storeOptions: Store.Options = { name: storeName, cwd: "storage", + watch: true, + fileExtension: "txt", serialize: (data) => { const jsonString = JSON.stringify(data); const encrypted = safeStorage.encryptString(jsonString); @@ -49,6 +41,10 @@ export class EncryptedElectronStore { const decrypted = safeStorage.decryptString(encryptedBuffer); return JSON.parse(decrypted) as EncryptedStoreSchema; }, + schema: { + version: { type: "number" }, + data: { type: "object", additionalProperties: true }, + }, defaults: { version: STORE_VERSION, data: {}, @@ -75,28 +71,14 @@ export class EncryptedElectronStore { this.store.delete(`data.${key}` as keyof EncryptedStoreSchema); } - /** - * Check if a key exists in storage. - * - * @param key - Storage key - * @returns true if key exists, false otherwise - */ has(key: string): boolean { return this.store.has(`data.${key}` as keyof EncryptedStoreSchema); } - /** - * Get all keys in storage. - * - * @returns Array of all storage keys - */ keys(): string[] { return Object.keys(this.store.get("data")); } - /** - * Clear all data from storage. - */ clear(): void { this.store.set("data", {}); } From 8ff48a705bfa98ff08980cb086061f14347b019d Mon Sep 17 00:00:00 2001 From: nafees87n Date: Wed, 21 Jan 2026 17:00:48 +0530 Subject: [PATCH 30/37] cleanup unncessary changes --- src/renderer/actions/local-sync/constants.ts | 2 +- src/renderer/actions/local-sync/fs-utils.ts | 14 +------------- src/renderer/actions/local-sync/schemas.ts | 8 -------- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/src/renderer/actions/local-sync/constants.ts b/src/renderer/actions/local-sync/constants.ts index 6a66eece..370df04b 100644 --- a/src/renderer/actions/local-sync/constants.ts +++ b/src/renderer/actions/local-sync/constants.ts @@ -1,6 +1,6 @@ import { homedir } from "os"; -export const CORE_CONFIG_FILE_VERSION = 0.2; +export const CORE_CONFIG_FILE_VERSION = 0.1; export const WORKSPACE_CONFIG_FILE_VERSION = "0.0.3"; export const CONFIG_FILE = "requestly.json"; export const COLLECTION_AUTH_FILE = "auth.json"; diff --git a/src/renderer/actions/local-sync/fs-utils.ts b/src/renderer/actions/local-sync/fs-utils.ts index e5eadda7..a6ec8fc8 100644 --- a/src/renderer/actions/local-sync/fs-utils.ts +++ b/src/renderer/actions/local-sync/fs-utils.ts @@ -431,7 +431,7 @@ export async function parseFileRaw(params: { /* NOTE: This is the ONLY function that should write to the global config file. All global config mutations must go through this writer to avoid partial writes & concurrency issues. */ -export async function writeToGlobalConfig( +async function writeToGlobalConfig( config: Static ): Promise> { const originalPath = appendPath( @@ -528,7 +528,6 @@ export async function addWorkspaceToGlobalConfig(params: { const config: Static = { version: CORE_CONFIG_FILE_VERSION, workspaces: [newWorkspace], - providers: [], }; const writeResult = await writeToGlobalConfig(config); if (writeResult.type === "error") { @@ -550,7 +549,6 @@ export async function addWorkspaceToGlobalConfig(params: { } const updatedConfig = { - ...readResult.content, version: readResult.content.version, workspaces: [...readResult.content.workspaces, newWorkspace], }; @@ -616,15 +614,6 @@ export async function migrateGlobalConfig(oldConfig: any) { return { version: CORE_CONFIG_FILE_VERSION, workspaces: oldConfig, - providers: [], - }; - } - - if (oldConfig.version === 0.1) { - return { - version: CORE_CONFIG_FILE_VERSION, - workspaces: oldConfig.workspaces, - providers: [], }; } @@ -742,7 +731,6 @@ export async function getAllWorkspaces(): Promise< const updatedConfig: Static = { version: effectiveConfig.version || CORE_CONFIG_FILE_VERSION, workspaces: valid, - providers: effectiveConfig.providers || [], }; const pruneWriteResult = await writeToGlobalConfig(updatedConfig); if (pruneWriteResult.type === "error") { diff --git a/src/renderer/actions/local-sync/schemas.ts b/src/renderer/actions/local-sync/schemas.ts index 9a1d8a97..d7eeaeb6 100644 --- a/src/renderer/actions/local-sync/schemas.ts +++ b/src/renderer/actions/local-sync/schemas.ts @@ -1,7 +1,6 @@ /* eslint-disable no-shadow */ /* eslint-disable no-unused-vars */ import { Type } from "@sinclair/typebox"; -import { SecretProviderType } from "lib/secretsManager/types"; export const Config = Type.Object({ version: Type.String(), @@ -202,11 +201,4 @@ export const GlobalConfig = Type.Object({ path: Type.String(), }) ), - // TODO:@nafees Should this be optional? - providers: Type.Array( - Type.Object({ - id: Type.String(), - type: Type.Enum(SecretProviderType), - }) - ), }); From 04f496b2bacbd7f2d3d8bca2d286c18822916f8c Mon Sep 17 00:00:00 2001 From: nafees87n Date: Wed, 21 Jan 2026 17:24:57 +0530 Subject: [PATCH 31/37] wrap in trycatch --- .../providerRegistry/FileBasedProviderRegistry.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts index 1113f33c..31a41a6d 100644 --- a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts @@ -11,7 +11,16 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { private async initProvidersFromStorage(): Promise { const configs = await this.getAllProviderConfigs(); configs.forEach((config) => { - this.providers.set(config.id, createProviderInstance(config)); + try { + this.providers.set(config.id, createProviderInstance(config)); + } catch (error) { + // TODO error to be propagated + console.log( + "!!!debug", + `Failed to initialize provider for config id: ${config.id}`, + error + ); + } }); } @@ -30,8 +39,9 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { } async setProviderConfig(config: SecretProviderConfig): Promise { + const provider = createProviderInstance(config); await this.store.set(config.id, config); - this.providers.set(config.id, createProviderInstance(config)); + this.providers.set(config.id, provider); } async deleteProviderConfig(id: string): Promise { From cbec03c70e5bb9d6491f2e8139cde3039f69f385 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Wed, 28 Jan 2026 12:45:38 +0530 Subject: [PATCH 32/37] removed linting changes --- .../local-sync/file-types/file-types.ts | 14 +++--- src/renderer/actions/local-sync/fs-utils.ts | 47 +++++++++---------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/renderer/actions/local-sync/file-types/file-types.ts b/src/renderer/actions/local-sync/file-types/file-types.ts index 44594d24..4df81ddd 100644 --- a/src/renderer/actions/local-sync/file-types/file-types.ts +++ b/src/renderer/actions/local-sync/file-types/file-types.ts @@ -13,7 +13,7 @@ import { FileTypeEnum } from "../types"; import { parseJsonContent, parseRaw } from "../common-utils"; export class ApiRecordFileType extends FileType { - validator: typeof ApiRecord = ApiRecord; + validator = ApiRecord; type = FileTypeEnum.API; @@ -25,7 +25,7 @@ export class ApiRecordFileType extends FileType { export class EnvironmentRecordFileType extends FileType< typeof EnvironmentRecord > { - validator: typeof EnvironmentRecord = EnvironmentRecord; + validator = EnvironmentRecord; type = FileTypeEnum.ENVIRONMENT; @@ -37,7 +37,7 @@ export class EnvironmentRecordFileType extends FileType< export class CollectionVariablesRecordFileType extends FileType< typeof Variables > { - validator: typeof Variables = Variables; + validator = Variables; type = FileTypeEnum.COLLECTION_VARIABLES; @@ -47,7 +47,7 @@ export class CollectionVariablesRecordFileType extends FileType< } export class ReadmeRecordFileType extends FileType { - validator: typeof Description = Description; + validator = Description; type = FileTypeEnum.DESCRIPTION; @@ -57,7 +57,7 @@ export class ReadmeRecordFileType extends FileType { } export class AuthRecordFileType extends FileType { - validator: typeof Auth = Auth; + validator = Auth; type = FileTypeEnum.AUTH; @@ -67,7 +67,7 @@ export class AuthRecordFileType extends FileType { } export class GlobalConfigRecordFileType extends FileType { - validator: typeof GlobalConfig = GlobalConfig; + validator = GlobalConfig; type = FileTypeEnum.GLOBAL_CONFIG; @@ -93,4 +93,4 @@ export function parseFileType(type: string) { default: throw new Error(`${type} is an invalid file type`); } -} +} \ No newline at end of file diff --git a/src/renderer/actions/local-sync/fs-utils.ts b/src/renderer/actions/local-sync/fs-utils.ts index a6ec8fc8..7f4bee4f 100644 --- a/src/renderer/actions/local-sync/fs-utils.ts +++ b/src/renderer/actions/local-sync/fs-utils.ts @@ -111,8 +111,8 @@ export async function deleteFsResource( } await FsService.unlink(resource.path); fileIndex.remove({ - type: "path", - path: resource.path, + type: 'path', + path: resource.path }); } else { const exists = await getIfFolderExists(resource); @@ -126,8 +126,8 @@ export async function deleteFsResource( } await FsService.rmdir(resource.path, { recursive: true }); fileIndex.remove({ - type: "path", - path: resource.path, + type: 'path', + path: resource.path }); } return { @@ -170,6 +170,8 @@ export async function createFolder( } else { fileIndex.getId(resource.path); } + + } else if (errorIfExist) { return createFileSystemError( { message: "Folder already exists!" }, @@ -194,23 +196,21 @@ export async function rename( ): Promise> { try { const alreadyExists = await (async () => { - if (newResource.type === "folder") { + if (newResource.type === 'folder') { return getIfFolderExists(newResource); } return getIfFileExists(newResource); })(); - const isSamePath = - getNormalizedPath(oldResource.path).toLowerCase() === - getNormalizedPath(newResource.path).toLowerCase(); + const isSamePath = getNormalizedPath(oldResource.path).toLowerCase() === getNormalizedPath(newResource.path).toLowerCase(); if (!isSamePath && alreadyExists) { return { - type: "error", + type: 'error', error: { - message: "Entity already exists!", + message: 'Entity already exists!', fileType: FileTypeEnum.UNKNOWN, path: newResource.path, code: ErrorCode.EntityAlreadyExists, - }, + } }; } await FsService.rename(oldResource.path, newResource.path); @@ -259,20 +259,19 @@ export async function writeContent( } ): Promise> { try { - const { writeWithElevatedAccess = false, performExistenceCheck = false } = - options || {}; + const { writeWithElevatedAccess = false, performExistenceCheck = false } = options || {}; if (performExistenceCheck) { const alreadyExists = await getIfFileExists(resource); if (alreadyExists) { return { - type: "error", + type: 'error', error: { - message: "Entity already exists!", + message: 'Entity already exists!', fileType: fileType.type, path: resource.path, code: ErrorCode.EntityAlreadyExists, - }, + } }; } } @@ -622,14 +621,14 @@ export async function migrateGlobalConfig(oldConfig: any) { type WorkspaceValidationResult = | { - valid: true; - ws: Static["workspaces"][number]; - } + valid: true; + ws: Static["workspaces"][number]; + } | { - valid: false; - ws: Static["workspaces"][number]; - error: { message: string }; - }; + valid: false; + ws: Static["workspaces"][number]; + error: { message: string }; + }; async function validateWorkspace( ws: Static["workspaces"][number] @@ -1118,4 +1117,4 @@ export function getFileNameFromPath(filePath: string) { } const parts = filePath.split("/"); return parts[parts.length - 1]; -} +} \ No newline at end of file From 63a10fcc7ec75954e39d3a1ce36f31ace0367c0d Mon Sep 17 00:00:00 2001 From: nafees87n Date: Wed, 28 Jan 2026 12:46:39 +0530 Subject: [PATCH 33/37] fix: new line --- src/renderer/actions/local-sync/file-types/file-types.ts | 2 +- src/renderer/actions/local-sync/fs-utils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/actions/local-sync/file-types/file-types.ts b/src/renderer/actions/local-sync/file-types/file-types.ts index 4df81ddd..cf00c812 100644 --- a/src/renderer/actions/local-sync/file-types/file-types.ts +++ b/src/renderer/actions/local-sync/file-types/file-types.ts @@ -93,4 +93,4 @@ export function parseFileType(type: string) { default: throw new Error(`${type} is an invalid file type`); } -} \ No newline at end of file +} diff --git a/src/renderer/actions/local-sync/fs-utils.ts b/src/renderer/actions/local-sync/fs-utils.ts index 7f4bee4f..0dbc4373 100644 --- a/src/renderer/actions/local-sync/fs-utils.ts +++ b/src/renderer/actions/local-sync/fs-utils.ts @@ -1117,4 +1117,4 @@ export function getFileNameFromPath(filePath: string) { } const parts = filePath.split("/"); return parts[parts.length - 1]; -} \ No newline at end of file +} From 88fb8fb2ad9838b3b503c8b989fd622fc0b34f3c Mon Sep 17 00:00:00 2001 From: Nafees Nehar Date: Fri, 30 Jan 2026 00:39:58 +0530 Subject: [PATCH 34/37] [DB-21] added variable fetching flow and awsSecretsManagerProvider (#266) --- package-lock.json | 3333 ++++++++++++++++- package.json | 5 +- release/app/package-lock.json | 26 - .../providerService/AbstractSecretProvider.ts | 85 +- .../awsSecretManagerProvider.ts | 156 +- src/lib/secretsManager/secretsManager.ts | 137 +- src/lib/secretsManager/types.ts | 22 +- src/main/events.js | 67 +- 8 files changed, 3571 insertions(+), 260 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d51014b..6607f1ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "hasInstallScript": true, "license": "UNLICENSED", "dependencies": { + "@aws-sdk/client-secrets-manager": "^3.969.0", "@devicefarmer/adbkit": "^3.2.6", "@electron/remote": "^2.1.2", "@requestly/requestly-core": "1.1.1", @@ -199,6 +200,763 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/client-secrets-manager": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.969.0.tgz", + "integrity": "sha512-lSM6SE1FG+U8kr4z2smdwfu18J3WkNI06p/0Pej2gB4gA52NwFk0HwO8UspWPCPDHqSuL18ZZ3yxl3N+CEq3Fw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.969.0", + "@aws-sdk/credential-provider-node": "3.969.0", + "@aws-sdk/middleware-host-header": "3.969.0", + "@aws-sdk/middleware-logger": "3.969.0", + "@aws-sdk/middleware-recursion-detection": "3.969.0", + "@aws-sdk/middleware-user-agent": "3.969.0", + "@aws-sdk/region-config-resolver": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@aws-sdk/util-endpoints": "3.969.0", + "@aws-sdk/util-user-agent-browser": "3.969.0", + "@aws-sdk/util-user-agent-node": "3.969.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.5", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.6", + "@smithy/middleware-retry": "^4.4.22", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.21", + "@smithy/util-defaults-mode-node": "^4.2.24", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.969.0.tgz", + "integrity": "sha512-Qn0Uz6o15q2S+1E6OpwRKmaAMoT4LktEn+Oibk28qb2Mne+emaDawhZXahOJb/wFw5lN2FEH7XoiSNenNNUmCw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.969.0", + "@aws-sdk/middleware-host-header": "3.969.0", + "@aws-sdk/middleware-logger": "3.969.0", + "@aws-sdk/middleware-recursion-detection": "3.969.0", + "@aws-sdk/middleware-user-agent": "3.969.0", + "@aws-sdk/region-config-resolver": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@aws-sdk/util-endpoints": "3.969.0", + "@aws-sdk/util-user-agent-browser": "3.969.0", + "@aws-sdk/util-user-agent-node": "3.969.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.5", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.6", + "@smithy/middleware-retry": "^4.4.22", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.21", + "@smithy/util-defaults-mode-node": "^4.2.24", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/core": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.969.0.tgz", + "integrity": "sha512-qqmQt4z5rEK1OYVkVkboWgy/58CC5QaQ7oy0tvLe3iri/mfZbgJkA+pkwQyRP827DfCBZ3W7Ki9iwSa+B2U7uQ==", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@aws-sdk/xml-builder": "3.969.0", + "@smithy/core": "^3.20.5", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.969.0.tgz", + "integrity": "sha512-yS96heH5XDUqS3qQNcdObKKMOqZaivuNInMVRpRli48aXW8fX1M3fY67K/Onlqa3Wxu6WfDc3ZGF52SywdLvbg==", + "dependencies": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.969.0.tgz", + "integrity": "sha512-QCEFxBiUYFUW5VG6k8jKhT4luZndpC7uUY4u1olwt+OnJrl3N2yC7oS34isVBa3ioXZ4A0YagbXTa/3mXUhlAA==", + "dependencies": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.969.0.tgz", + "integrity": "sha512-lsXyTDkUrZPxjr0XruZrqdcHY9zHcIuoY3TOCQEm23VTc8Np2BenTtjGAIexkL3ar69K4u3FVLQroLpmFxeXqA==", + "dependencies": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/credential-provider-env": "3.969.0", + "@aws-sdk/credential-provider-http": "3.969.0", + "@aws-sdk/credential-provider-login": "3.969.0", + "@aws-sdk/credential-provider-process": "3.969.0", + "@aws-sdk/credential-provider-sso": "3.969.0", + "@aws-sdk/credential-provider-web-identity": "3.969.0", + "@aws-sdk/nested-clients": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.969.0.tgz", + "integrity": "sha512-bIRFDf54qIUFFLTZNYt40d6EseNeK9w80dHEs7BVEAWoS23c9+MSqkdg/LJBBK9Kgy01vRmjiedfBZN+jGypLw==", + "dependencies": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/nested-clients": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.969.0.tgz", + "integrity": "sha512-lImMjcy/5SGDIBk7PFJCqFO4rFuapKCvo1z2PidD3Cbz2D7wsJnyqUNQIp5Ix0Xc3/uAYG9zXI9kgaMf1dspIQ==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.969.0", + "@aws-sdk/credential-provider-http": "3.969.0", + "@aws-sdk/credential-provider-ini": "3.969.0", + "@aws-sdk/credential-provider-process": "3.969.0", + "@aws-sdk/credential-provider-sso": "3.969.0", + "@aws-sdk/credential-provider-web-identity": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.969.0.tgz", + "integrity": "sha512-2qQkM0rwd8Hl9nIHtUaqT8Z/djrulovqx/wBHsbRKaISwc2fiT3De1Lk1jx34Jzrz/dTHAMJJi+cML1N4Lk3kw==", + "dependencies": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.969.0.tgz", + "integrity": "sha512-JHqXw9Ct3dtZB86/zGFJYWyodr961GyIrqTBhV0brrZFPvcinM9abDSK58jt6GNBM2lqfMCvXL6I4ahNsMdkrg==", + "dependencies": { + "@aws-sdk/client-sso": "3.969.0", + "@aws-sdk/core": "3.969.0", + "@aws-sdk/token-providers": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.969.0.tgz", + "integrity": "sha512-mKCZtqrs3ts3YmIjT4NFlYgT2Oe6syW0nX5m2l7iyrFrLXw26Zo3rx29DjGzycPdJHZZvsIy5y6yqChDuF65ng==", + "dependencies": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/nested-clients": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.969.0.tgz", + "integrity": "sha512-AWa4rVsAfBR4xqm7pybQ8sUNJYnjyP/bJjfAw34qPuh3M9XrfGbAHG0aiAfQGrBnmS28jlO6Kz69o+c6PRw1dw==", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.969.0.tgz", + "integrity": "sha512-xwrxfip7Y2iTtCMJ+iifN1E1XMOuhxIHY9DreMCvgdl4r7+48x2S1bCYPWH3eNY85/7CapBWdJ8cerpEl12sQQ==", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.969.0.tgz", + "integrity": "sha512-2r3PuNquU3CcS1Am4vn/KHFwLi8QFjMdA/R+CRDXT4AFO/0qxevF/YStW3gAKntQIgWgQV8ZdEtKAoJvLI4UWg==", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.969.0.tgz", + "integrity": "sha512-Y6WkW8QQ2X9jG9HNBWyzp5KlJOCtLqX8VIvGLoGc2wXdZH7dgOy62uFhkfnHbgfiel6fkNYaycjGx/yyxi0JLQ==", + "dependencies": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@aws-sdk/util-endpoints": "3.969.0", + "@smithy/core": "^3.20.5", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.969.0.tgz", + "integrity": "sha512-MJrejgODxVYZjQjSpPLJkVuxnbrue1x1R8+as3anT5V/wk9Qc/Pf5B1IFjM3Ak6uOtzuRYNY4auOvcg4U8twDA==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.969.0", + "@aws-sdk/middleware-host-header": "3.969.0", + "@aws-sdk/middleware-logger": "3.969.0", + "@aws-sdk/middleware-recursion-detection": "3.969.0", + "@aws-sdk/middleware-user-agent": "3.969.0", + "@aws-sdk/region-config-resolver": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@aws-sdk/util-endpoints": "3.969.0", + "@aws-sdk/util-user-agent-browser": "3.969.0", + "@aws-sdk/util-user-agent-node": "3.969.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.5", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.6", + "@smithy/middleware-retry": "^4.4.22", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.21", + "@smithy/util-defaults-mode-node": "^4.2.24", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.969.0.tgz", + "integrity": "sha512-scj9OXqKpcjJ4jsFLtqYWz3IaNvNOQTFFvEY8XMJXTv+3qF5I7/x9SJtKzTRJEBF3spjzBUYPtGFbs9sj4fisQ==", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.969.0.tgz", + "integrity": "sha512-ucs6QczPkvGinbGmhMlPCQnagGJ+xsM6itsSWlJzxo9YsP6jR75cBU8pRdaM7nEbtCDnrUHf8W9g3D2Hd9mgVA==", + "dependencies": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/nested-clients": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/types": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.969.0.tgz", + "integrity": "sha512-7IIzM5TdiXn+VtgPdVLjmE6uUBUtnga0f4RiSEI1WW10RPuNvZ9U+pL3SwDiRDAdoGrOF9tSLJOFZmfuwYuVYQ==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.969.0.tgz", + "integrity": "sha512-H2x2UwYiA1pHg40jE+OCSc668W9GXRShTiCWy1UPKtZKREbQ63Mgd7NAj+bEMsZUSCdHywqmSsLqKM9IcqQ3Bg==", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.2.tgz", + "integrity": "sha512-qKgO7wAYsXzhwCHhdbaKFyxd83Fgs8/1Ka+jjSPrv2Ll7mB55Wbwlo0kkfMLh993/yEc8aoDIAc1Fz9h4Spi4Q==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.969.0.tgz", + "integrity": "sha512-bpJGjuKmFr0rA6UKUCmN8D19HQFMLXMx5hKBXqBlPFdalMhxJSjcxzX9DbQh0Fn6bJtxCguFmRGOBdQqNOt49g==", + "dependencies": { + "@aws-sdk/types": "3.969.0", + "@smithy/types": "^4.12.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.969.0.tgz", + "integrity": "sha512-D11ZuXNXdUMv8XTthMx+LPzkYNQAeQ68FnCTGnFLgLpnR8hVTeZMBBKjQ77wYGzWDk/csHKdCy697gU1On5KjA==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/util-user-agent-node/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.969.0.tgz", + "integrity": "sha512-BSe4Lx/qdRQQdX8cSSI7Et20vqBspzAjBy8ZmXVoyLkol3y4sXBXzn+BiLtR+oh60ExQn6o2DU4QjdOZbXaKIQ==", + "dependencies": { + "@smithy/types": "^4.12.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -2868,125 +3626,860 @@ "@sinonjs/commons": "^1.7.0" } }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "license": "MIT", + "node_modules/@smithy/abort-controller": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", + "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", "dependencies": { - "defer-to-connect": "^2.0.0" + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@teamsupercell/typings-for-css-modules-loader": { - "version": "2.5.2", - "dev": true, - "license": "MIT", + "node_modules/@smithy/abort-controller/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", + "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", "dependencies": { - "camelcase": "^5.3.1", - "loader-utils": "^1.4.2", - "schema-utils": "^2.0.1" + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" }, - "optionalDependencies": { - "prettier": "*" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@teamsupercell/typings-for-css-modules-loader/node_modules/json5": { - "version": "1.0.2", - "dev": true, - "license": "MIT", + "node_modules/@smithy/config-resolver/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/core": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.5.tgz", + "integrity": "sha512-0Tz77Td8ynHaowXfOdrD0F1IH4tgWGUhwmLwmpFyTbr+U9WHXNNp9u/k2VjBXGnSe7BwjBERRpXsokGTXzNjhA==", "dependencies": { - "minimist": "^1.2.0" + "@smithy/middleware-serde": "^4.2.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" }, - "bin": { - "json5": "lib/cli.js" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@teamsupercell/typings-for-css-modules-loader/node_modules/loader-utils": { - "version": "1.4.2", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", + "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "tslib": "^2.6.2" }, "engines": { - "node": ">=4.0.0" + "node": ">=18.0.0" } }, - "node_modules/@teamsupercell/typings-for-css-modules-loader/node_modules/schema-utils": { - "version": "2.7.1", - "dev": true, - "license": "MIT", + "node_modules/@smithy/credential-provider-imds/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", + "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "node": ">=18.0.0" } }, - "node_modules/@testing-library/jest-dom": { - "version": "5.16.5", - "dev": true, - "license": "MIT", + "node_modules/@smithy/fetch-http-handler/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", + "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", "dependencies": { - "@adobe/css-tools": "^4.0.1", - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", - "aria-query": "^5.0.0", - "chalk": "^3.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", - "lodash": "^4.17.15", - "redent": "^3.0.0" + "@smithy/types": "^4.12.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=8", - "npm": ">=6", - "yarn": ">=1" + "node": ">=18.0.0" } }, - "node_modules/@testing-library/jest-dom/node_modules/chalk": { - "version": "3.0.0", - "dev": true, - "license": "MIT", + "node_modules/@smithy/hash-node/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", + "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=8" + "node": ">=18.0.0" } }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "dev": true, - "license": "MIT", + "node_modules/@smithy/invalid-dependency/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">= 10" + "node": ">=18.0.0" } }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "dev": true, - "license": "ISC", + "node_modules/@smithy/is-array-buffer/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", + "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=10.13.0" + "node": ">=18.0.0" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "license": "MIT" + "node_modules/@smithy/middleware-content-length/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.6.tgz", + "integrity": "sha512-dpq3bHqbEOBqGBjRVHVFP3eUSPpX0BYtg1D5d5Irgk6orGGAuZfY22rC4sErhg+ZfY/Y0kPqm1XpAmDZg7DeuA==", + "dependencies": { + "@smithy/core": "^3.20.5", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.22", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.22.tgz", + "integrity": "sha512-vwWDMaObSMjw6WCC/3Ae9G7uul5Sk95jr07CDk1gkIMpaDic0phPS1MpVAZ6+YkF7PAzRlpsDjxPwRlh/S11FQ==", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", + "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", + "dependencies": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", + "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", + "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", + "dependencies": { + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.8.tgz", + "integrity": "sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg==", + "dependencies": { + "@smithy/abort-controller": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", + "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", + "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", + "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", + "dependencies": { + "@smithy/types": "^4.12.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", + "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", + "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", + "dependencies": { + "@smithy/types": "^4.12.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", + "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", + "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/smithy-client": { + "version": "4.10.7", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.7.tgz", + "integrity": "sha512-Uznt0I9z3os3Z+8pbXrOSCTXCA6vrjyN7Ub+8l2pRDum44vLv8qw0qGVkJN0/tZBZotaEFHrDPKUoPNueTr5Vg==", + "dependencies": { + "@smithy/core": "^3.20.5", + "@smithy/middleware-endpoint": "^4.4.6", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", + "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", + "dependencies": { + "@smithy/querystring-parser": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.21", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.21.tgz", + "integrity": "sha512-DtmVJarzqtjghtGjCw/PFJolcJkP7GkZgy+hWTAN3YLXNH+IC82uMoMhFoC3ZtIz5mOgCm5+hOGi1wfhVYgrxw==", + "dependencies": { + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.24", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.24.tgz", + "integrity": "sha512-JelBDKPAVswVY666rezBvY6b0nF/v9TXjUbNwDNAyme7qqKYEX687wJv0uze8lBIZVbg30wlWnlYfVSjjpKYFA==", + "dependencies": { + "@smithy/config-resolver": "^4.4.6", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", + "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", + "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", + "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", + "dependencies": { + "@smithy/service-error-classification": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.10", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.10.tgz", + "integrity": "sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g==", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@teamsupercell/typings-for-css-modules-loader": { + "version": "2.5.2", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^5.3.1", + "loader-utils": "^1.4.2", + "schema-utils": "^2.0.1" + }, + "optionalDependencies": { + "prettier": "*" + } + }, + "node_modules/@teamsupercell/typings-for-css-modules-loader/node_modules/json5": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/@teamsupercell/typings-for-css-modules-loader/node_modules/loader-utils": { + "version": "1.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@teamsupercell/typings-for-css-modules-loader/node_modules/schema-utils": { + "version": "2.7.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "5.16.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.0.1", + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=8", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", "license": "MIT" }, "node_modules/@tsconfig/node14": { @@ -4839,6 +6332,11 @@ "license": "MIT", "optional": true }, + "node_modules/bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==" + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -8571,6 +10069,23 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "dev": true, @@ -15936,6 +17451,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, "node_modules/style-loader": { "version": "3.3.3", "dev": true, @@ -17759,24 +19285,736 @@ "@jridgewell/trace-mapping": "^0.3.9" } }, - "@appium/logger": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@appium/logger/-/logger-1.6.1.tgz", - "integrity": "sha512-3TWpLR1qVQ0usLJ6R49iN4TV9Zs0nog1oL3hakCglwP0g4ZllwwEbp+2b1ovJfX6oOv1wXNREyokq2uxU5gB/Q==", + "@appium/logger": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@appium/logger/-/logger-1.6.1.tgz", + "integrity": "sha512-3TWpLR1qVQ0usLJ6R49iN4TV9Zs0nog1oL3hakCglwP0g4ZllwwEbp+2b1ovJfX6oOv1wXNREyokq2uxU5gB/Q==", + "requires": { + "console-control-strings": "1.1.0", + "lodash": "4.17.21", + "lru-cache": "10.4.3", + "set-blocking": "2.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + } + } + }, + "@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "requires": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "requires": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/client-secrets-manager": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.969.0.tgz", + "integrity": "sha512-lSM6SE1FG+U8kr4z2smdwfu18J3WkNI06p/0Pej2gB4gA52NwFk0HwO8UspWPCPDHqSuL18ZZ3yxl3N+CEq3Fw==", + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.969.0", + "@aws-sdk/credential-provider-node": "3.969.0", + "@aws-sdk/middleware-host-header": "3.969.0", + "@aws-sdk/middleware-logger": "3.969.0", + "@aws-sdk/middleware-recursion-detection": "3.969.0", + "@aws-sdk/middleware-user-agent": "3.969.0", + "@aws-sdk/region-config-resolver": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@aws-sdk/util-endpoints": "3.969.0", + "@aws-sdk/util-user-agent-browser": "3.969.0", + "@aws-sdk/util-user-agent-node": "3.969.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.5", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.6", + "@smithy/middleware-retry": "^4.4.22", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.21", + "@smithy/util-defaults-mode-node": "^4.2.24", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/client-sso": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.969.0.tgz", + "integrity": "sha512-Qn0Uz6o15q2S+1E6OpwRKmaAMoT4LktEn+Oibk28qb2Mne+emaDawhZXahOJb/wFw5lN2FEH7XoiSNenNNUmCw==", + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.969.0", + "@aws-sdk/middleware-host-header": "3.969.0", + "@aws-sdk/middleware-logger": "3.969.0", + "@aws-sdk/middleware-recursion-detection": "3.969.0", + "@aws-sdk/middleware-user-agent": "3.969.0", + "@aws-sdk/region-config-resolver": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@aws-sdk/util-endpoints": "3.969.0", + "@aws-sdk/util-user-agent-browser": "3.969.0", + "@aws-sdk/util-user-agent-node": "3.969.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.5", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.6", + "@smithy/middleware-retry": "^4.4.22", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.21", + "@smithy/util-defaults-mode-node": "^4.2.24", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/core": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.969.0.tgz", + "integrity": "sha512-qqmQt4z5rEK1OYVkVkboWgy/58CC5QaQ7oy0tvLe3iri/mfZbgJkA+pkwQyRP827DfCBZ3W7Ki9iwSa+B2U7uQ==", + "requires": { + "@aws-sdk/types": "3.969.0", + "@aws-sdk/xml-builder": "3.969.0", + "@smithy/core": "^3.20.5", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/credential-provider-env": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.969.0.tgz", + "integrity": "sha512-yS96heH5XDUqS3qQNcdObKKMOqZaivuNInMVRpRli48aXW8fX1M3fY67K/Onlqa3Wxu6WfDc3ZGF52SywdLvbg==", + "requires": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/credential-provider-http": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.969.0.tgz", + "integrity": "sha512-QCEFxBiUYFUW5VG6k8jKhT4luZndpC7uUY4u1olwt+OnJrl3N2yC7oS34isVBa3ioXZ4A0YagbXTa/3mXUhlAA==", + "requires": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/credential-provider-ini": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.969.0.tgz", + "integrity": "sha512-lsXyTDkUrZPxjr0XruZrqdcHY9zHcIuoY3TOCQEm23VTc8Np2BenTtjGAIexkL3ar69K4u3FVLQroLpmFxeXqA==", + "requires": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/credential-provider-env": "3.969.0", + "@aws-sdk/credential-provider-http": "3.969.0", + "@aws-sdk/credential-provider-login": "3.969.0", + "@aws-sdk/credential-provider-process": "3.969.0", + "@aws-sdk/credential-provider-sso": "3.969.0", + "@aws-sdk/credential-provider-web-identity": "3.969.0", + "@aws-sdk/nested-clients": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/credential-provider-login": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.969.0.tgz", + "integrity": "sha512-bIRFDf54qIUFFLTZNYt40d6EseNeK9w80dHEs7BVEAWoS23c9+MSqkdg/LJBBK9Kgy01vRmjiedfBZN+jGypLw==", + "requires": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/nested-clients": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/credential-provider-node": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.969.0.tgz", + "integrity": "sha512-lImMjcy/5SGDIBk7PFJCqFO4rFuapKCvo1z2PidD3Cbz2D7wsJnyqUNQIp5Ix0Xc3/uAYG9zXI9kgaMf1dspIQ==", + "requires": { + "@aws-sdk/credential-provider-env": "3.969.0", + "@aws-sdk/credential-provider-http": "3.969.0", + "@aws-sdk/credential-provider-ini": "3.969.0", + "@aws-sdk/credential-provider-process": "3.969.0", + "@aws-sdk/credential-provider-sso": "3.969.0", + "@aws-sdk/credential-provider-web-identity": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/credential-provider-process": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.969.0.tgz", + "integrity": "sha512-2qQkM0rwd8Hl9nIHtUaqT8Z/djrulovqx/wBHsbRKaISwc2fiT3De1Lk1jx34Jzrz/dTHAMJJi+cML1N4Lk3kw==", + "requires": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/credential-provider-sso": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.969.0.tgz", + "integrity": "sha512-JHqXw9Ct3dtZB86/zGFJYWyodr961GyIrqTBhV0brrZFPvcinM9abDSK58jt6GNBM2lqfMCvXL6I4ahNsMdkrg==", + "requires": { + "@aws-sdk/client-sso": "3.969.0", + "@aws-sdk/core": "3.969.0", + "@aws-sdk/token-providers": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/credential-provider-web-identity": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.969.0.tgz", + "integrity": "sha512-mKCZtqrs3ts3YmIjT4NFlYgT2Oe6syW0nX5m2l7iyrFrLXw26Zo3rx29DjGzycPdJHZZvsIy5y6yqChDuF65ng==", + "requires": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/nested-clients": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/middleware-host-header": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.969.0.tgz", + "integrity": "sha512-AWa4rVsAfBR4xqm7pybQ8sUNJYnjyP/bJjfAw34qPuh3M9XrfGbAHG0aiAfQGrBnmS28jlO6Kz69o+c6PRw1dw==", + "requires": { + "@aws-sdk/types": "3.969.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/middleware-logger": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.969.0.tgz", + "integrity": "sha512-xwrxfip7Y2iTtCMJ+iifN1E1XMOuhxIHY9DreMCvgdl4r7+48x2S1bCYPWH3eNY85/7CapBWdJ8cerpEl12sQQ==", + "requires": { + "@aws-sdk/types": "3.969.0", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/middleware-recursion-detection": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.969.0.tgz", + "integrity": "sha512-2r3PuNquU3CcS1Am4vn/KHFwLi8QFjMdA/R+CRDXT4AFO/0qxevF/YStW3gAKntQIgWgQV8ZdEtKAoJvLI4UWg==", + "requires": { + "@aws-sdk/types": "3.969.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/middleware-user-agent": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.969.0.tgz", + "integrity": "sha512-Y6WkW8QQ2X9jG9HNBWyzp5KlJOCtLqX8VIvGLoGc2wXdZH7dgOy62uFhkfnHbgfiel6fkNYaycjGx/yyxi0JLQ==", + "requires": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@aws-sdk/util-endpoints": "3.969.0", + "@smithy/core": "^3.20.5", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/nested-clients": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.969.0.tgz", + "integrity": "sha512-MJrejgODxVYZjQjSpPLJkVuxnbrue1x1R8+as3anT5V/wk9Qc/Pf5B1IFjM3Ak6uOtzuRYNY4auOvcg4U8twDA==", + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.969.0", + "@aws-sdk/middleware-host-header": "3.969.0", + "@aws-sdk/middleware-logger": "3.969.0", + "@aws-sdk/middleware-recursion-detection": "3.969.0", + "@aws-sdk/middleware-user-agent": "3.969.0", + "@aws-sdk/region-config-resolver": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@aws-sdk/util-endpoints": "3.969.0", + "@aws-sdk/util-user-agent-browser": "3.969.0", + "@aws-sdk/util-user-agent-node": "3.969.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.20.5", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.6", + "@smithy/middleware-retry": "^4.4.22", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.21", + "@smithy/util-defaults-mode-node": "^4.2.24", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/region-config-resolver": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.969.0.tgz", + "integrity": "sha512-scj9OXqKpcjJ4jsFLtqYWz3IaNvNOQTFFvEY8XMJXTv+3qF5I7/x9SJtKzTRJEBF3spjzBUYPtGFbs9sj4fisQ==", + "requires": { + "@aws-sdk/types": "3.969.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/token-providers": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.969.0.tgz", + "integrity": "sha512-ucs6QczPkvGinbGmhMlPCQnagGJ+xsM6itsSWlJzxo9YsP6jR75cBU8pRdaM7nEbtCDnrUHf8W9g3D2Hd9mgVA==", + "requires": { + "@aws-sdk/core": "3.969.0", + "@aws-sdk/nested-clients": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/types": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.969.0.tgz", + "integrity": "sha512-7IIzM5TdiXn+VtgPdVLjmE6uUBUtnga0f4RiSEI1WW10RPuNvZ9U+pL3SwDiRDAdoGrOF9tSLJOFZmfuwYuVYQ==", + "requires": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/util-endpoints": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.969.0.tgz", + "integrity": "sha512-H2x2UwYiA1pHg40jE+OCSc668W9GXRShTiCWy1UPKtZKREbQ63Mgd7NAj+bEMsZUSCdHywqmSsLqKM9IcqQ3Bg==", + "requires": { + "@aws-sdk/types": "3.969.0", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/util-locate-window": { + "version": "3.965.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.2.tgz", + "integrity": "sha512-qKgO7wAYsXzhwCHhdbaKFyxd83Fgs8/1Ka+jjSPrv2Ll7mB55Wbwlo0kkfMLh993/yEc8aoDIAc1Fz9h4Spi4Q==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/util-user-agent-browser": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.969.0.tgz", + "integrity": "sha512-bpJGjuKmFr0rA6UKUCmN8D19HQFMLXMx5hKBXqBlPFdalMhxJSjcxzX9DbQh0Fn6bJtxCguFmRGOBdQqNOt49g==", + "requires": { + "@aws-sdk/types": "3.969.0", + "@smithy/types": "^4.12.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/util-user-agent-node": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.969.0.tgz", + "integrity": "sha512-D11ZuXNXdUMv8XTthMx+LPzkYNQAeQ68FnCTGnFLgLpnR8hVTeZMBBKjQ77wYGzWDk/csHKdCy697gU1On5KjA==", + "requires": { + "@aws-sdk/middleware-user-agent": "3.969.0", + "@aws-sdk/types": "3.969.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@aws-sdk/xml-builder": { + "version": "3.969.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.969.0.tgz", + "integrity": "sha512-BSe4Lx/qdRQQdX8cSSI7Et20vqBspzAjBy8ZmXVoyLkol3y4sXBXzn+BiLtR+oh60ExQn6o2DU4QjdOZbXaKIQ==", "requires": { - "console-control-strings": "1.1.0", - "lodash": "4.17.21", - "lru-cache": "10.4.3", - "set-blocking": "2.0.0" + "@smithy/types": "^4.12.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" }, "dependencies": { - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" } } }, + "@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==" + }, "@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -19492,138 +21730,831 @@ } } }, - "@sentry-internal/browser-utils": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.51.0.tgz", - "integrity": "sha512-r94yfRK17zNJER0hgQE4qOSy5pWzsnFcGTJQSqhSEKUcC4KK37qSfoPrPejFxtIqXhqlkd/dTWKvrMwXWcn0MQ==", + "@sentry-internal/browser-utils": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.51.0.tgz", + "integrity": "sha512-r94yfRK17zNJER0hgQE4qOSy5pWzsnFcGTJQSqhSEKUcC4KK37qSfoPrPejFxtIqXhqlkd/dTWKvrMwXWcn0MQ==", + "requires": { + "@sentry/core": "8.51.0" + } + }, + "@sentry-internal/feedback": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.51.0.tgz", + "integrity": "sha512-VgfxSZWLYUPKDnkt2zG+Oe5ccv8U3WPM6Mo4kfABIJT3Ai4VbZB7+vb2a4pm6lUCF9DeOPXHb5o9Tg17SHDAHw==", + "requires": { + "@sentry/core": "8.51.0" + } + }, + "@sentry-internal/replay": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.51.0.tgz", + "integrity": "sha512-lkm7id3a2n3yMZeF5socCVQUeEeShNOGr7Wtsmb5RORacEnld0z+NfbMTilo1mDwiWBzI5OYBjm62eglm1HFsQ==", + "requires": { + "@sentry-internal/browser-utils": "8.51.0", + "@sentry/core": "8.51.0" + } + }, + "@sentry-internal/replay-canvas": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.51.0.tgz", + "integrity": "sha512-ERXIbwdULkdtIQnfkMLRVfpoGV2rClwySGRlTPepFKeLxlcXo9o09cPu+qbukiDnGK0cgEgRnrV961hMg21Bmw==", + "requires": { + "@sentry-internal/replay": "8.51.0", + "@sentry/core": "8.51.0" + } + }, + "@sentry/browser": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.51.0.tgz", + "integrity": "sha512-1kbbyVfBBAx5Xyynp+lC5lLnAHo0qJ2r4mtmdT6koPjesvoOocEK0QQnouQBmdUbm3L0L/bPI1SgXjbeJyhzHQ==", + "requires": { + "@sentry-internal/browser-utils": "8.51.0", + "@sentry-internal/feedback": "8.51.0", + "@sentry-internal/replay": "8.51.0", + "@sentry-internal/replay-canvas": "8.51.0", + "@sentry/core": "8.51.0" + } + }, + "@sentry/core": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.51.0.tgz", + "integrity": "sha512-Go0KxCYLw+OBIlLSv5YsYX+x9NW43fNVcyB6rhkSp2Q5Zme3tAE6KtZFvyu4SO7G/903wisW5Q6qV6UuK/ee4A==" + }, + "@sentry/electron": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@sentry/electron/-/electron-5.11.0.tgz", + "integrity": "sha512-LxwIZziAyXF3jk/CNItuCsGIcPunbj+Kr6up4OUoux75YxWepLnXgYtZmXSoKm4y1uq6InqUpIdDzt7AO5UgZw==", + "requires": { + "@sentry/browser": "8.51.0", + "@sentry/core": "8.51.0", + "@sentry/node": "8.51.0", + "deepmerge": "4.3.1" + } + }, + "@sentry/node": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-8.51.0.tgz", + "integrity": "sha512-KfXk3QaeNXmJgUUCDAwZW7cdZ+1GvRXNdTPLpWbAKGaNulAeimck5fGGL8FRMSF0sMz6BT6Ku7u6DUaZTtbB7w==", + "requires": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.29.0", + "@opentelemetry/core": "^1.29.0", + "@opentelemetry/instrumentation": "^0.56.0", + "@opentelemetry/instrumentation-amqplib": "^0.45.0", + "@opentelemetry/instrumentation-connect": "0.42.0", + "@opentelemetry/instrumentation-dataloader": "0.15.0", + "@opentelemetry/instrumentation-express": "0.46.0", + "@opentelemetry/instrumentation-fastify": "0.43.0", + "@opentelemetry/instrumentation-fs": "0.18.0", + "@opentelemetry/instrumentation-generic-pool": "0.42.0", + "@opentelemetry/instrumentation-graphql": "0.46.0", + "@opentelemetry/instrumentation-hapi": "0.44.0", + "@opentelemetry/instrumentation-http": "0.56.0", + "@opentelemetry/instrumentation-ioredis": "0.46.0", + "@opentelemetry/instrumentation-kafkajs": "0.6.0", + "@opentelemetry/instrumentation-knex": "0.43.0", + "@opentelemetry/instrumentation-koa": "0.46.0", + "@opentelemetry/instrumentation-lru-memoizer": "0.43.0", + "@opentelemetry/instrumentation-mongodb": "0.50.0", + "@opentelemetry/instrumentation-mongoose": "0.45.0", + "@opentelemetry/instrumentation-mysql": "0.44.0", + "@opentelemetry/instrumentation-mysql2": "0.44.0", + "@opentelemetry/instrumentation-nestjs-core": "0.43.0", + "@opentelemetry/instrumentation-pg": "0.49.0", + "@opentelemetry/instrumentation-redis-4": "0.45.0", + "@opentelemetry/instrumentation-tedious": "0.17.0", + "@opentelemetry/instrumentation-undici": "0.9.0", + "@opentelemetry/resources": "^1.29.0", + "@opentelemetry/sdk-trace-base": "^1.29.0", + "@opentelemetry/semantic-conventions": "^1.28.0", + "@prisma/instrumentation": "5.22.0", + "@sentry/core": "8.51.0", + "@sentry/opentelemetry": "8.51.0", + "import-in-the-middle": "^1.11.2" + } + }, + "@sentry/opentelemetry": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-8.51.0.tgz", + "integrity": "sha512-SvH/rl/P+S7EKXIZA6kq2HzFYfXKQx8Ytgx4WZJV+katsdaDay24QtycYE+PaqbotAkV6MOMECEb8a9XXttQcg==", + "requires": { + "@sentry/core": "8.51.0" + } + }, + "@sinclair/typebox": { + "version": "0.34.25", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.25.tgz", + "integrity": "sha512-gu+tdy9WZIRulrR4CAcGXZAAixwakKszkUXudMJ4EhtNflBEify5Pm5vnVEVqdmMkxnT4tcdfJps5XYqaNeF9Q==" + }, + "@sindresorhus/is": { + "version": "4.6.0" + }, + "@sinonjs/commons": { + "version": "1.8.6", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "8.1.0", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@smithy/abort-controller": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", + "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", + "requires": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/config-resolver": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", + "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", + "requires": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/core": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.5.tgz", + "integrity": "sha512-0Tz77Td8ynHaowXfOdrD0F1IH4tgWGUhwmLwmpFyTbr+U9WHXNNp9u/k2VjBXGnSe7BwjBERRpXsokGTXzNjhA==", + "requires": { + "@smithy/middleware-serde": "^4.2.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.10", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/credential-provider-imds": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", + "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", + "requires": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/fetch-http-handler": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", + "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", + "requires": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/hash-node": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", + "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", + "requires": { + "@smithy/types": "^4.12.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/invalid-dependency": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", + "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", + "requires": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/middleware-content-length": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", + "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", + "requires": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/middleware-endpoint": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.6.tgz", + "integrity": "sha512-dpq3bHqbEOBqGBjRVHVFP3eUSPpX0BYtg1D5d5Irgk6orGGAuZfY22rC4sErhg+ZfY/Y0kPqm1XpAmDZg7DeuA==", + "requires": { + "@smithy/core": "^3.20.5", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-middleware": "^4.2.8", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/middleware-retry": { + "version": "4.4.22", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.22.tgz", + "integrity": "sha512-vwWDMaObSMjw6WCC/3Ae9G7uul5Sk95jr07CDk1gkIMpaDic0phPS1MpVAZ6+YkF7PAzRlpsDjxPwRlh/S11FQ==", + "requires": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/middleware-serde": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", + "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", + "requires": { + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/middleware-stack": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", + "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", + "requires": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/node-config-provider": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", + "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", + "requires": { + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/node-http-handler": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.8.tgz", + "integrity": "sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg==", + "requires": { + "@smithy/abort-controller": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/property-provider": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", + "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", "requires": { - "@sentry/core": "8.51.0" + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } } }, - "@sentry-internal/feedback": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.51.0.tgz", - "integrity": "sha512-VgfxSZWLYUPKDnkt2zG+Oe5ccv8U3WPM6Mo4kfABIJT3Ai4VbZB7+vb2a4pm6lUCF9DeOPXHb5o9Tg17SHDAHw==", + "@smithy/protocol-http": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", + "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", "requires": { - "@sentry/core": "8.51.0" + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } } }, - "@sentry-internal/replay": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.51.0.tgz", - "integrity": "sha512-lkm7id3a2n3yMZeF5socCVQUeEeShNOGr7Wtsmb5RORacEnld0z+NfbMTilo1mDwiWBzI5OYBjm62eglm1HFsQ==", + "@smithy/querystring-builder": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", + "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", "requires": { - "@sentry-internal/browser-utils": "8.51.0", - "@sentry/core": "8.51.0" + "@smithy/types": "^4.12.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } } }, - "@sentry-internal/replay-canvas": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.51.0.tgz", - "integrity": "sha512-ERXIbwdULkdtIQnfkMLRVfpoGV2rClwySGRlTPepFKeLxlcXo9o09cPu+qbukiDnGK0cgEgRnrV961hMg21Bmw==", + "@smithy/querystring-parser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", + "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", "requires": { - "@sentry-internal/replay": "8.51.0", - "@sentry/core": "8.51.0" + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } } }, - "@sentry/browser": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.51.0.tgz", - "integrity": "sha512-1kbbyVfBBAx5Xyynp+lC5lLnAHo0qJ2r4mtmdT6koPjesvoOocEK0QQnouQBmdUbm3L0L/bPI1SgXjbeJyhzHQ==", + "@smithy/service-error-classification": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", + "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", "requires": { - "@sentry-internal/browser-utils": "8.51.0", - "@sentry-internal/feedback": "8.51.0", - "@sentry-internal/replay": "8.51.0", - "@sentry-internal/replay-canvas": "8.51.0", - "@sentry/core": "8.51.0" + "@smithy/types": "^4.12.0" } }, - "@sentry/core": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.51.0.tgz", - "integrity": "sha512-Go0KxCYLw+OBIlLSv5YsYX+x9NW43fNVcyB6rhkSp2Q5Zme3tAE6KtZFvyu4SO7G/903wisW5Q6qV6UuK/ee4A==" + "@smithy/shared-ini-file-loader": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", + "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", + "requires": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } }, - "@sentry/electron": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@sentry/electron/-/electron-5.11.0.tgz", - "integrity": "sha512-LxwIZziAyXF3jk/CNItuCsGIcPunbj+Kr6up4OUoux75YxWepLnXgYtZmXSoKm4y1uq6InqUpIdDzt7AO5UgZw==", + "@smithy/signature-v4": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", + "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", "requires": { - "@sentry/browser": "8.51.0", - "@sentry/core": "8.51.0", - "@sentry/node": "8.51.0", - "deepmerge": "4.3.1" + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } } }, - "@sentry/node": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-8.51.0.tgz", - "integrity": "sha512-KfXk3QaeNXmJgUUCDAwZW7cdZ+1GvRXNdTPLpWbAKGaNulAeimck5fGGL8FRMSF0sMz6BT6Ku7u6DUaZTtbB7w==", + "@smithy/smithy-client": { + "version": "4.10.7", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.7.tgz", + "integrity": "sha512-Uznt0I9z3os3Z+8pbXrOSCTXCA6vrjyN7Ub+8l2pRDum44vLv8qw0qGVkJN0/tZBZotaEFHrDPKUoPNueTr5Vg==", "requires": { - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.29.0", - "@opentelemetry/core": "^1.29.0", - "@opentelemetry/instrumentation": "^0.56.0", - "@opentelemetry/instrumentation-amqplib": "^0.45.0", - "@opentelemetry/instrumentation-connect": "0.42.0", - "@opentelemetry/instrumentation-dataloader": "0.15.0", - "@opentelemetry/instrumentation-express": "0.46.0", - "@opentelemetry/instrumentation-fastify": "0.43.0", - "@opentelemetry/instrumentation-fs": "0.18.0", - "@opentelemetry/instrumentation-generic-pool": "0.42.0", - "@opentelemetry/instrumentation-graphql": "0.46.0", - "@opentelemetry/instrumentation-hapi": "0.44.0", - "@opentelemetry/instrumentation-http": "0.56.0", - "@opentelemetry/instrumentation-ioredis": "0.46.0", - "@opentelemetry/instrumentation-kafkajs": "0.6.0", - "@opentelemetry/instrumentation-knex": "0.43.0", - "@opentelemetry/instrumentation-koa": "0.46.0", - "@opentelemetry/instrumentation-lru-memoizer": "0.43.0", - "@opentelemetry/instrumentation-mongodb": "0.50.0", - "@opentelemetry/instrumentation-mongoose": "0.45.0", - "@opentelemetry/instrumentation-mysql": "0.44.0", - "@opentelemetry/instrumentation-mysql2": "0.44.0", - "@opentelemetry/instrumentation-nestjs-core": "0.43.0", - "@opentelemetry/instrumentation-pg": "0.49.0", - "@opentelemetry/instrumentation-redis-4": "0.45.0", - "@opentelemetry/instrumentation-tedious": "0.17.0", - "@opentelemetry/instrumentation-undici": "0.9.0", - "@opentelemetry/resources": "^1.29.0", - "@opentelemetry/sdk-trace-base": "^1.29.0", - "@opentelemetry/semantic-conventions": "^1.28.0", - "@prisma/instrumentation": "5.22.0", - "@sentry/core": "8.51.0", - "@sentry/opentelemetry": "8.51.0", - "import-in-the-middle": "^1.11.2" + "@smithy/core": "^3.20.5", + "@smithy/middleware-endpoint": "^4.4.6", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } } }, - "@sentry/opentelemetry": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-8.51.0.tgz", - "integrity": "sha512-SvH/rl/P+S7EKXIZA6kq2HzFYfXKQx8Ytgx4WZJV+katsdaDay24QtycYE+PaqbotAkV6MOMECEb8a9XXttQcg==", + "@smithy/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", "requires": { - "@sentry/core": "8.51.0" + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } } }, - "@sinclair/typebox": { - "version": "0.34.25", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.25.tgz", - "integrity": "sha512-gu+tdy9WZIRulrR4CAcGXZAAixwakKszkUXudMJ4EhtNflBEify5Pm5vnVEVqdmMkxnT4tcdfJps5XYqaNeF9Q==" + "@smithy/url-parser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", + "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", + "requires": { + "@smithy/querystring-parser": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } }, - "@sindresorhus/is": { - "version": "4.6.0" + "@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "requires": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } }, - "@sinonjs/commons": { - "version": "1.8.6", - "dev": true, + "@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", "requires": { - "type-detect": "4.0.8" + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } } }, - "@sinonjs/fake-timers": { - "version": "8.1.0", - "dev": true, + "@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", "requires": { - "@sinonjs/commons": "^1.7.0" + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "requires": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/util-defaults-mode-browser": { + "version": "4.3.21", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.21.tgz", + "integrity": "sha512-DtmVJarzqtjghtGjCw/PFJolcJkP7GkZgy+hWTAN3YLXNH+IC82uMoMhFoC3ZtIz5mOgCm5+hOGi1wfhVYgrxw==", + "requires": { + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/util-defaults-mode-node": { + "version": "4.2.24", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.24.tgz", + "integrity": "sha512-JelBDKPAVswVY666rezBvY6b0nF/v9TXjUbNwDNAyme7qqKYEX687wJv0uze8lBIZVbg30wlWnlYfVSjjpKYFA==", + "requires": { + "@smithy/config-resolver": "^4.4.6", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.10.7", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/util-endpoints": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", + "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", + "requires": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/util-middleware": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", + "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", + "requires": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/util-retry": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", + "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", + "requires": { + "@smithy/service-error-classification": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/util-stream": { + "version": "4.5.10", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.10.tgz", + "integrity": "sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g==", + "requires": { + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "requires": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } } }, "@szmarczak/http-timer": { @@ -21117,6 +24048,11 @@ "version": "3.2.0", "optional": true }, + "bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==" + }, "brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -23683,6 +26619,14 @@ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true }, + "fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "requires": { + "strnum": "^2.1.0" + } + }, "fastest-levenshtein": { "version": "1.0.16", "dev": true @@ -28516,6 +31460,11 @@ "version": "3.1.1", "dev": true }, + "strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==" + }, "style-loader": { "version": "3.3.3", "dev": true, diff --git a/package.json b/package.json index 9180b752..500fb147 100644 --- a/package.json +++ b/package.json @@ -254,6 +254,7 @@ "rimraf": "^3.0.2", "sass": "^1.42.1", "sass-loader": "^12.2.0", + "source-map-support": "^0.5.21", "style-loader": "^3.3.0", "terser-webpack-plugin": "^5.2.4", "ts-loader": "^9.2.6", @@ -263,10 +264,10 @@ "webpack-bundle-analyzer": "^4.5.0", "webpack-cli": "^4.9.0", "webpack-dev-server": "^4.3.1", - "webpack-merge": "^5.8.0", - "source-map-support": "^0.5.21" + "webpack-merge": "^5.8.0" }, "dependencies": { + "@aws-sdk/client-secrets-manager": "^3.969.0", "@devicefarmer/adbkit": "^3.2.6", "@electron/remote": "^2.1.2", "@requestly/requestly-core": "1.1.1", diff --git a/release/app/package-lock.json b/release/app/package-lock.json index d0eec57f..691bf8e1 100644 --- a/release/app/package-lock.json +++ b/release/app/package-lock.json @@ -982,14 +982,6 @@ "version": "1.2.0", "license": "ISC" }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, "node_modules/ini": { "version": "1.3.8", "license": "ISC" @@ -1709,14 +1701,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, "node_modules/object-assign": { "version": "4.1.1", "license": "MIT", @@ -2938,11 +2922,6 @@ "ansi-regex": { "version": "5.0.1" }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, "string-width": { "version": "4.2.3", "requires": { @@ -4121,11 +4100,6 @@ "ansi-regex": { "version": "5.0.1" }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, "string-width": { "version": "4.2.3", "requires": { diff --git a/src/lib/secretsManager/providerService/AbstractSecretProvider.ts b/src/lib/secretsManager/providerService/AbstractSecretProvider.ts index b58b49a2..6ee59c58 100644 --- a/src/lib/secretsManager/providerService/AbstractSecretProvider.ts +++ b/src/lib/secretsManager/providerService/AbstractSecretProvider.ts @@ -1,27 +1,98 @@ -import { CachedSecret, ProviderSpecificConfig, SecretProviderType, SecretReference } from "../types"; +import { + ProviderSpecificConfig, + SecretProviderType, + SecretReference, + SecretValue, +} from "../types"; + +const DEFAULT_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour +const DEFAULT_MAX_CACHE_SIZE = 100; export abstract class AbstractSecretProvider { - protected cache: Map = new Map(); + protected cache: Map = new Map(); + + /** Cache TTL in milliseconds. Subclasses can override. */ + protected cacheTtlMs: number = DEFAULT_CACHE_TTL_MS; + + /** Maximum cache size (Size of the map). Subclasses can override. */ + protected maxCacheSize: number = DEFAULT_MAX_CACHE_SIZE; abstract readonly type: SecretProviderType; abstract readonly id: string; - protected config: ProviderSpecificConfig; + protected abstract config: ProviderSpecificConfig; - protected abstract getSecretIdentfier(ref: SecretReference): string; + protected abstract getCacheKey(_ref: SecretReference): string; abstract testConnection(): Promise; - abstract getSecret(ref: SecretReference): Promise; + abstract getSecret(_ref: SecretReference): Promise; - abstract getSecrets(): Promise; + abstract getSecrets( + _refs: SecretReference[] + ): Promise<(SecretValue | null)[]>; abstract setSecret(): Promise; abstract setSecrets(): Promise; + abstract removeSecret(): Promise; + + abstract removeSecrets(): Promise; + + protected invalidateCache(): void { + this.cache.clear(); + } + + protected getCachedSecret(key: string): SecretValue | null { + const cached = this.cache.get(key); + if (cached && cached.fetchedAt + this.cacheTtlMs > Date.now()) { + return cached; + } + return null; + } + + protected setCacheEntry(key: string, value: SecretValue): void { + if (this.maxCacheSize <= 0) { + return; + } + + this.evictExpiredEntries(); + + while (this.cache.size >= this.maxCacheSize) { + const oldestKey = this.cache.keys().next().value; + if (!oldestKey) { + break; + } + this.cache.delete(oldestKey); + } + + this.cache.set(key, value); + } + + protected evictExpiredEntries(): void { + const now = Date.now(); + const keysToDelete: string[] = []; + + this.cache.forEach((value, key) => { + if (value.fetchedAt + this.cacheTtlMs <= now) { + keysToDelete.push(key); + } + }); + + keysToDelete.forEach((key) => this.cache.delete(key)); + } + + abstract refreshSecrets(): Promise<(SecretValue | null)[]>; + static validateConfig(config: any): boolean { - throw new Error("Not implemented"); + // Base implementation rejects all configs as a fail-safe. + // Provider implementations must override with specific validation. + if (!config) { + return false; + } + + return false; } } diff --git a/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts b/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts index a20629c4..32a2f9f2 100644 --- a/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts +++ b/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts @@ -1,4 +1,156 @@ -/* eslint-disable class-methods-use-this */ +import { + AwsSecretReference, + AWSSecretsManagerConfig, + AwsSecretValue, + SecretProviderConfig, + SecretProviderType, +} from "../types"; import { AbstractSecretProvider } from "./AbstractSecretProvider"; +import { + GetSecretValueCommand, + ListSecretsCommand, + SecretsManagerClient, +} from "@aws-sdk/client-secrets-manager"; -export class AWSSecretsManagerProvider extends AbstractSecretProvider {} +export class AWSSecretsManagerProvider extends AbstractSecretProvider { + readonly type = SecretProviderType.AWS_SECRETS_MANAGER; + + readonly id: string; + + protected config: AWSSecretsManagerConfig; + + private client: SecretsManagerClient; + + constructor(providerConfig: SecretProviderConfig) { + super(); + this.id = providerConfig.id; + this.config = providerConfig.config as AWSSecretsManagerConfig; + this.client = new SecretsManagerClient({ + region: this.config.region, + credentials: { + accessKeyId: this.config.accessKeyId, + secretAccessKey: this.config.secretAccessKey, + sessionToken: this.config.sessionToken, + }, + }); + } + + protected getCacheKey(ref: AwsSecretReference): string { + return `name:${ref.identifier};version:${ref.version ?? "latest"}`; + } + + async testConnection(): Promise { + if (!AWSSecretsManagerProvider.validateConfig(this.config)) { + return false; + } + + try { + const listSecretsCommand = new ListSecretsCommand({ MaxResults: 1 }); + const res = await this.client.send(listSecretsCommand); + console.log("!!!debug", "aws result", res); + + if (res.$metadata.httpStatusCode !== 200) { + return false; + } + + return true; + } catch (err) { + console.error( + "!!!debug", + "aws secrets manager test connection error", + err + ); + return false; + } + } + + async getSecret(ref: AwsSecretReference): Promise { + if (!this.client) { + throw new Error("AWS Secrets Manager client is not initialized."); + } + + const cacheKey = this.getCacheKey(ref); + const cachedSecret = this.getCachedSecret(cacheKey) as AwsSecretValue | null; + + if (cachedSecret) { + console.log("!!!debug", "returning from cache", cachedSecret); + return cachedSecret; + } + + const getSecretCommand = new GetSecretValueCommand({ + SecretId: ref.identifier, + VersionId: ref.version, + }); + + const secretResponse = await this.client.send(getSecretCommand); + + if (secretResponse.$metadata.httpStatusCode !== 200) { + console.error("!!!debug", "Failed to fetch secret", secretResponse); + return null; + } + + if (!secretResponse.SecretString) { + console.error("!!!debug", "SecretString is empty", secretResponse); + return null; + } + + const awsSecret: AwsSecretValue = { + providerId: this.id, + secretReference: ref, + fetchedAt: Date.now(), + name: secretResponse.Name, + value: secretResponse.SecretString, + ARN: secretResponse.ARN, + versionId: secretResponse.VersionId, + }; + + console.log("!!!debug", "returning after fetching", awsSecret); + + this.setCacheEntry(cacheKey, awsSecret); + + return awsSecret; + } + + async getSecrets( + refs: AwsSecretReference[] + ): Promise<(AwsSecretValue | null)[]> { + if (!this.client) { + throw new Error("AWS Secrets Manager client is not initialized."); + } + + // Not using BatchGetSecretValueCommand as it would require additional permissions + return Promise.all(refs.map((ref) => this.getSecret(ref))); + } + + async setSecret(): Promise { + throw new Error("Method not implemented."); + } + + async setSecrets(): Promise { + throw new Error("Method not implemented."); + } + + async removeSecret(): Promise { + throw new Error("Method not implemented."); + } + + async removeSecrets(): Promise { + throw new Error("Method not implemented."); + } + + async refreshSecrets(): Promise<(AwsSecretValue | null)[]> { + const allSecretRefs = Array.from(this.cache.values()).map( + (secret) => secret.secretReference + ); + + this.invalidateCache(); + + return this.getSecrets(allSecretRefs); + } + + static validateConfig(config: AWSSecretsManagerConfig): boolean { + return Boolean( + config.accessKeyId && config.secretAccessKey && config.region + ); + } +} diff --git a/src/lib/secretsManager/secretsManager.ts b/src/lib/secretsManager/secretsManager.ts index b92f9192..93706efc 100644 --- a/src/lib/secretsManager/secretsManager.ts +++ b/src/lib/secretsManager/secretsManager.ts @@ -1,18 +1,65 @@ -import { SecretProviderConfig } from "./types"; +import { SecretProviderConfig, SecretReference, SecretValue } from "./types"; import { AbstractProviderRegistry } from "./providerRegistry/AbstractProviderRegistry"; export class SecretsManager { + // eslint-disable-next-line no-use-before-define + private static instance: SecretsManager | null = null; + + private static initPromise: Promise | null = null; + private registry: AbstractProviderRegistry; - constructor(registry: AbstractProviderRegistry) { + private constructor(registry: AbstractProviderRegistry) { this.registry = registry; } - async initialize(): Promise { - await this.registry.initialize(); + /** + * Initialize the SecretsManager singleton. Must be called once at app startup. + */ + static async initialize(registry: AbstractProviderRegistry): Promise { + if (this.instance) { + // Already initialized, return completed promise + return this.initPromise!; + } + + if (!this.initPromise) { + this.initPromise = (async () => { + try { + this.instance = new SecretsManager(registry); + await this.instance.registry.initialize(); + } catch (err) { + this.instance = null; + this.initPromise = null; + throw err; + } + })(); + } + + return this.initPromise; } - async addProviderConfig(config: SecretProviderConfig) { + /** + * Get the initialized SecretsManager instance. + */ + static getInstance(): SecretsManager { + if (!this.instance) { + throw new Error( + "SecretsManager not initialized. Call and await SecretsManager.initialize() first." + ); + } + return this.instance; + } + + static isInitialized(): boolean { + return this.instance !== null; + } + + static reset(): void { + this.instance = null; + this.initPromise = null; + } + + async setProviderConfig(config: SecretProviderConfig) { console.log("!!!debug", "addconfig", config); await this.registry.setProviderConfig(config); } @@ -24,4 +71,84 @@ export class SecretsManager { async getProviderConfig(id: string): Promise { return this.registry.getProviderConfig(id); } + + async testProviderConnection(id: string): Promise { + const provider = this.registry.getProvider(id); + + if (!provider) { + throw new Error(`Provider with id ${id} not found`); + } + + const isConnected = await provider.testConnection(); + + return isConnected ?? false; + } + + async getSecret( + providerId: string, + ref: SecretReference + ): Promise { + const provider = this.registry.getProvider(providerId); + if (!provider) { + throw new Error(`Provider with id ${providerId} not found`); + } + + const secretValue = await provider.getSecret(ref); + + return secretValue; + } + + async getSecrets( + secrets: Array<{ providerId: string; ref: SecretReference }> + ): Promise { + const providerMap: Map = new Map(); + + for (const s of secrets) { + if (!providerMap.has(s.providerId)) { + providerMap.set(s.providerId, []); + } + providerMap.get(s.providerId)?.push(s.ref); + } + + const results: SecretValue[] = []; + + for (const [providerId, refs] of providerMap.entries()) { + const provider = this.registry.getProvider(providerId); + + if (!provider) { + // TODO: Error to be handled properly + continue; + } + + const secretValues = await provider.getSecrets(refs); + + results.push( + ...secretValues.filter((sv): sv is SecretValue => sv !== null) + ); + } + + return results; + } + + async refreshSecrets(providerId: string): Promise<(SecretValue | null)[]> { + const provider = this.registry.getProvider(providerId); + if (!provider) { + throw new Error(`Provider with id ${providerId} not found`); + } + + return provider.refreshSecrets(); + } +} + +/** + * // At app startup (once): + * await SecretsManager.initialize(registry); + * + * // Everywhere else: + * import { getSecretsManager } from "./secretsManager"; + * const secretsManager = getSecretsManager(); + * await secretsManager.getSecret(...); + */ +export function getSecretsManager(): SecretsManager { + return SecretsManager.getInstance(); } diff --git a/src/lib/secretsManager/types.ts b/src/lib/secretsManager/types.ts index de87cae7..ee012e86 100644 --- a/src/lib/secretsManager/types.ts +++ b/src/lib/secretsManager/types.ts @@ -1,3 +1,5 @@ +import { GetSecretValueCommandOutput } from "@aws-sdk/client-secrets-manager"; + export enum SecretProviderType { AWS_SECRETS_MANAGER = "aws", } @@ -22,19 +24,23 @@ export interface SecretProviderConfig { export type AwsSecretReference = { type: SecretProviderType.AWS_SECRETS_MANAGER; - nameOrArn: string; + identifier: string; // ARN or Name version?: string; }; export type SecretReference = AwsSecretReference; // | VaultSecretReference; // | OtherProviderSecretReference; -export interface CachedSecret { - id: string; // Unique identifier - identifier: string; // Secret identifier (name, ARN, or path) - value: string; // The actual secret value +interface BaseSecretValue { providerId: string; - providerType: SecretProviderType; + secretReference: SecretReference; fetchedAt: number; - expiresAt: number; - version?: string; } + +export interface AwsSecretValue extends BaseSecretValue { + name: GetSecretValueCommandOutput["Name"]; + value: GetSecretValueCommandOutput["SecretString"]; + ARN: GetSecretValueCommandOutput["ARN"]; + versionId: GetSecretValueCommandOutput["VersionId"]; +} + +export type SecretValue = AwsSecretValue; // | VaultSecretValue; // | OtherProviderSecretValue; diff --git a/src/main/events.js b/src/main/events.js index a1ac4107..4b7ea513 100644 --- a/src/main/events.js +++ b/src/main/events.js @@ -23,9 +23,12 @@ import { createOrUpdateAxiosInstance } from "./actions/getProxiedAxios"; // and then build these utilites elsewhere // eslint-disable-next-line import/no-cycle import createTrayMenu from "./main"; -import { SecretsManager } from "../lib/secretsManager/secretsManager"; +import { SecretsManagerEncryptedStorage } from "../lib/secretsManager/encryptedStorage/SecretsManagerEncryptedStorage"; import { FileBasedProviderRegistry } from "../lib/secretsManager/providerRegistry/FileBasedProviderRegistry"; -import { SecretsManagerEncryptedStorage } from "lib/secretsManager/encryptedStorage/SecretsManagerEncryptedStorage"; +import { + getSecretsManager, + SecretsManager, +} from "../lib/secretsManager/secretsManager"; const getFileCategory = (fileExtension) => { switch (fileExtension) { @@ -275,30 +278,58 @@ export const registerMainProcessEventsForWebAppWindow = (webAppWindow) => { let secretsManager = null; - ipcMain.handle("init-secretsManager", () => { - const secretsStore = new SecretsManagerEncryptedStorage("providers"); - const registry = new FileBasedProviderRegistry(secretsStore); - secretsManager = new SecretsManager(registry); + ipcMain.handle("init-secretsManager", async () => { + const secretsStorage = new SecretsManagerEncryptedStorage("providers"); + const registry = new FileBasedProviderRegistry(secretsStorage); - try { - secretsManager.initialize(); - } catch (err) { - console.error("Error initializing Secrets Manager", err); - } + await SecretsManager.initialize(registry); + secretsManager = getSecretsManager(); + return true; }); - ipcMain.handle("secretsManager:addProviderConfig", async (event, config) => { - await secretsManager.addProviderConfig(config); - }); + ipcMain.handle( + "secretsManager:addProviderConfig", + async (event, { config }) => { + await secretsManager.setProviderConfig(config); + } + ); - ipcMain.handle("secretsManager:getProviderConfig", async (event, id) => { + ipcMain.handle("secretsManager:getProviderConfig", async (event, { id }) => { const providerConfig = await secretsManager.getProviderConfig(id); console.log("!!!debug", "getConfig", providerConfig); + return providerConfig; }); - ipcMain.handle("secretsManager:removeProviderConfig", async (event, id) => { - await secretsManager.removeProviderConfig(id); - }); + ipcMain.handle( + "secretsManager:removeProviderConfig", + async (event, { id }) => { + await secretsManager.removeProviderConfig(id); + } + ); + + ipcMain.handle( + "secretsManager:testConnection", + async (event, { providerId }) => { + const providerConnection = await secretsManager.testProviderConnection( + providerId + ); + + return providerConnection; + } + ); + + ipcMain.handle( + "secretsManager:resolveSecret", + async (event, { providerId, ref }) => { + console.log("!!!debug", "resolve", { + providerId, + ref, + }); + const secretValue = await secretsManager.getSecret(providerId, ref); + console.log("!!!debug", "resolveSecret value", secretValue); + return secretValue; + } + ); }; export const registerMainProcessCommonEvents = () => { From e95012db1dbee5b6d437ac10e37e6da5c2069ddc Mon Sep 17 00:00:00 2001 From: Sahil Gupta Date: Fri, 30 Jan 2026 10:50:04 +0530 Subject: [PATCH 35/37] convert into generic types --- src/lib/secretsManager/USAGE_EXAMPLES.ts | 366 ++++++++++++++++++ src/lib/secretsManager/baseTypes.ts | 29 ++ .../AbstractProviderRegistry.ts | 28 +- .../FileBasedProviderRegistry.ts | 4 +- .../providerService/AbstractSecretProvider.ts | 52 +-- .../awsSecretManagerProvider.ts | 63 ++- .../providerService/hashicorpVaultProvider.ts | 93 +++++ .../providerService/providerFactory.ts | 33 +- src/lib/secretsManager/types.ts | 94 +++-- 9 files changed, 676 insertions(+), 86 deletions(-) create mode 100644 src/lib/secretsManager/USAGE_EXAMPLES.ts create mode 100644 src/lib/secretsManager/baseTypes.ts create mode 100644 src/lib/secretsManager/providerService/hashicorpVaultProvider.ts diff --git a/src/lib/secretsManager/USAGE_EXAMPLES.ts b/src/lib/secretsManager/USAGE_EXAMPLES.ts new file mode 100644 index 00000000..bc807757 --- /dev/null +++ b/src/lib/secretsManager/USAGE_EXAMPLES.ts @@ -0,0 +1,366 @@ +/** + * Usage Examples for Type-Safe Secrets Manager + * + * This file demonstrates how TypeScript automatically infers types + * throughout the secrets manager system. + */ + +import { + SecretProviderType, + AWSSecretProviderConfig, + HashicorpVaultProviderConfig, + AwsSecretReference, + VaultSecretReference, +} from "./types"; +import { createProviderInstance, createTypedProviderInstance } from "./providerService/providerFactory"; +import { AWSSecretsManagerProvider } from "./providerService/awsSecretManagerProvider"; +import { HashicorpVaultProvider } from "./providerService/hashicorpVaultProvider"; + +// ============================================================================ +// Example 1: Creating Provider Configurations (Type-Safe) +// ============================================================================ + +// AWS Provider Config - TypeScript enforces correct config structure +const awsConfig: AWSSecretProviderConfig = { + id: "aws-prod", + type: SecretProviderType.AWS_SECRETS_MANAGER, + name: "AWS Production", + createdAt: Date.now(), + updatedAt: Date.now(), + credentials: { + accessKeyId: "AKIA...", + secretAccessKey: "...", + region: "us-east-1", + sessionToken: "...", // optional + }, +}; + +// HashiCorp Vault Config - TypeScript enforces correct config structure +const vaultConfig: HashicorpVaultProviderConfig = { + id: "vault-dev", + type: SecretProviderType.HASHICORP_VAULT, + name: "Vault Development", + createdAt: Date.now(), + updatedAt: Date.now(), + credentials: { + address: "https://vault.example.com", + token: "s.xyz...", + namespace: "admin", // optional + }, +}; + +// ❌ This will cause a TypeScript error - wrong config type for provider type +// const invalidConfig: AWSSecretProviderConfig = { +// id: "invalid", +// type: SecretProviderType.HASHICORP_VAULT, // ❌ Error: type mismatch +// ... +// }; + +// ============================================================================ +// Example 2: Creating Provider Instances (Type-Safe Factory) +// ============================================================================ + +async function example2() { + // Generic factory - returns AbstractSecretProvider + const awsProvider = createProviderInstance(awsConfig); + const vaultProvider = createProviderInstance(vaultConfig); + + // TypeScript knows the provider type from the instance + console.log(awsProvider.type); // SecretProviderType.AWS_SECRETS_MANAGER + console.log(vaultProvider.type); // SecretProviderType.HASHICORP_VAULT + + // Strongly-typed factory - returns specific provider type + const typedAwsProvider = createTypedProviderInstance(awsConfig); + // typedAwsProvider is AbstractSecretProvider + + const typedVaultProvider = createTypedProviderInstance(vaultConfig); + // typedVaultProvider is AbstractSecretProvider +} + +// ============================================================================ +// Example 3: Working with Secret References (Type-Safe) +// ============================================================================ + +async function example3() { + const awsProvider = new AWSSecretsManagerProvider(awsConfig); + const vaultProvider = new HashicorpVaultProvider(vaultConfig); + + // AWS Secret Reference - TypeScript enforces correct structure + const awsRef: AwsSecretReference = { + type: SecretProviderType.AWS_SECRETS_MANAGER, + identifier: "arn:aws:secretsmanager:us-east-1:123456789:secret:myapp/config", + version: "AWSCURRENT", // optional + }; + + // Vault Secret Reference - TypeScript enforces correct structure + const vaultRef: VaultSecretReference = { + type: SecretProviderType.HASHICORP_VAULT, + path: "secret/data/myapp/config", + version: 2, // optional - KV v2 version number + }; + + // TypeScript ensures you pass the correct reference type to each provider + const awsSecret = await awsProvider.getSecret(awsRef); // ✅ Correct + const vaultSecret = await vaultProvider.getSecret(vaultRef); // ✅ Correct + + // ❌ These would cause TypeScript errors: + // await awsProvider.getSecret(vaultRef); // ❌ Error: wrong reference type + // await vaultProvider.getSecret(awsRef); // ❌ Error: wrong reference type + + // TypeScript knows the exact return types + if (awsSecret) { + console.log(awsSecret.ARN); // ✅ ARN exists on AwsSecretValue + console.log(awsSecret.value); // ✅ value is string | undefined + // console.log(awsSecret.data); // ❌ Error: data doesn't exist on AwsSecretValue + } + + if (vaultSecret) { + console.log(vaultSecret.data); // ✅ data exists on VaultSecretValue + console.log(vaultSecret.metadata?.version); // ✅ metadata is optional + // console.log(vaultSecret.ARN); // ❌ Error: ARN doesn't exist on VaultSecretValue + } +} + +// ============================================================================ +// Example 4: Batch Operations (Type-Safe) +// ============================================================================ + +async function example4() { + const awsProvider = new AWSSecretsManagerProvider(awsConfig); + const vaultProvider = new HashicorpVaultProvider(vaultConfig); + + // Get multiple secrets - types are preserved + const awsRefs: AwsSecretReference[] = [ + { + type: SecretProviderType.AWS_SECRETS_MANAGER, + identifier: "secret-1", + }, + { + type: SecretProviderType.AWS_SECRETS_MANAGER, + identifier: "secret-2", + }, + ]; + + const vaultRefs: VaultSecretReference[] = [ + { + type: SecretProviderType.HASHICORP_VAULT, + path: "secret/data/app1", + }, + { + type: SecretProviderType.HASHICORP_VAULT, + path: "secret/data/app2", + }, + ]; + + const awsSecrets = await awsProvider.getSecrets(awsRefs); + // TypeScript knows: awsSecrets is (AwsSecretValue | null)[] + + const vaultSecrets = await vaultProvider.getSecrets(vaultRefs); + // TypeScript knows: vaultSecrets is (VaultSecretValue | null)[] + + // Type-safe iteration + awsSecrets.forEach((secret) => { + if (secret) { + console.log(secret.ARN); // ✅ ARN exists + console.log(secret.versionId); // ✅ versionId exists + } + }); + + vaultSecrets.forEach((secret) => { + if (secret) { + console.log(secret.path); // ✅ path exists + console.log(secret.data); // ✅ data exists + } + }); +} + +// ============================================================================ +// Example 5: Setting Secrets (Type-Safe) +// ============================================================================ + +async function example5() { + const awsProvider = new AWSSecretsManagerProvider(awsConfig); + const vaultProvider = new HashicorpVaultProvider(vaultConfig); + + const awsRef: AwsSecretReference = { + type: SecretProviderType.AWS_SECRETS_MANAGER, + identifier: "my-secret", + }; + + const vaultRef: VaultSecretReference = { + type: SecretProviderType.HASHICORP_VAULT, + path: "secret/data/myapp/config", + }; + + // Set a single secret - both string and object values are supported + await awsProvider.setSecret(awsRef, "my-secret-value"); + await vaultProvider.setSecret(vaultRef, { + database: "postgres://...", + apiKey: "xyz...", + }); + + // Batch set + await awsProvider.setSecrets([ + { ref: awsRef, value: "value1" }, + ]); + + await vaultProvider.setSecrets([ + { + ref: { type: SecretProviderType.HASHICORP_VAULT, path: "secret/data/app1" }, + value: { key1: "value1" }, + }, + { + ref: { type: SecretProviderType.HASHICORP_VAULT, path: "secret/data/app2" }, + value: { key2: "value2" }, + }, + ]); +} + +// ============================================================================ +// Example 6: Using the Registry (Type-Safe) +// ============================================================================ + +async function example6() { + // Assume we have a registry instance + const registry: any = null; // FileBasedProviderRegistry instance + + // Get a provider without knowing its type + const provider = registry.getProvider("aws-prod"); + if (provider) { + // provider is AbstractSecretProvider + console.log(provider.type); + } + + // Get a provider with a specific type (type-safe) + const awsProvider = registry.getTypedProvider( + "aws-prod", + SecretProviderType.AWS_SECRETS_MANAGER + ); + + if (awsProvider) { + // TypeScript knows: awsProvider is AbstractSecretProvider + const ref: AwsSecretReference = { + type: SecretProviderType.AWS_SECRETS_MANAGER, + identifier: "my-secret", + }; + + const secret = await awsProvider.getSecret(ref); + // TypeScript knows: secret is AwsSecretValue | null + + if (secret) { + console.log(secret.ARN); // ✅ Type-safe access to AWS-specific fields + } + } +} + +// ============================================================================ +// Example 7: Type Guards and Runtime Checks +// ============================================================================ + +async function example7() { + const registry: any = null; // FileBasedProviderRegistry instance + + // Get a provider and use type guards + const provider = registry.getProvider("some-provider-id"); + + if (provider) { + // Runtime check with type narrowing + if (provider.type === SecretProviderType.AWS_SECRETS_MANAGER) { + // TypeScript narrows the type here + const awsProvider = provider as AWSSecretsManagerProvider; + // Now you have full access to AWS-specific methods if any + } else if (provider.type === SecretProviderType.HASHICORP_VAULT) { + const vaultProvider = provider as HashicorpVaultProvider; + // Now you have full access to Vault-specific methods if any + } + } +} + +// ============================================================================ +// Example 8: Adding a New Provider (Easy Extension) +// ============================================================================ + +/** + * To add a new provider (e.g., Azure Key Vault): + * + * 1. Add the provider type to the enum in types.ts: + * ``` + * export enum SecretProviderType { + * AWS_SECRETS_MANAGER = "aws", + * HASHICORP_VAULT = "vault", + * AZURE_KEY_VAULT = "azure", // ← Add this + * } + * ``` + * + * 2. Add the config interface in types.ts: + * ``` + * export interface AzureKeyVaultConfig { + * vaultUrl: string; + * tenantId: string; + * clientId: string; + * clientSecret: string; + * } + * ``` + * + * 3. Add to the discriminated unions in types.ts: + * ``` + * export type AzureKeyVaultProviderConfig = ProviderConfig< + * SecretProviderType.AZURE_KEY_VAULT, + * AzureKeyVaultConfig + * >; + * + * export type SecretProviderConfig = + * | AWSSecretProviderConfig + * | HashicorpVaultProviderConfig + * | AzureKeyVaultProviderConfig; // ← Add this + * ``` + * + * 4. Add reference and value types in types.ts: + * ``` + * export interface AzureSecretReference extends BaseSecretReference<...> { + * name: string; + * version?: string; + * } + * + * export interface AzureSecretValue extends BaseSecretValue<...> { + * value: string; + * id: string; + * // ... other Azure-specific fields + * } + * ``` + * + * 5. Add to the type map in types.ts: + * ``` + * export interface ProviderTypeMap { + * [SecretProviderType.AWS_SECRETS_MANAGER]: { ... }; + * [SecretProviderType.HASHICORP_VAULT]: { ... }; + * [SecretProviderType.AZURE_KEY_VAULT]: { // ← Add this + * config: AzureKeyVaultConfig; + * providerConfig: AzureKeyVaultProviderConfig; + * reference: AzureSecretReference; + * value: AzureSecretValue; + * }; + * } + * ``` + * + * 6. Create the provider class (azureKeyVaultProvider.ts): + * ``` + * export class AzureKeyVaultProvider extends AbstractSecretProvider< + * SecretProviderType.AZURE_KEY_VAULT + * > { + * readonly type = SecretProviderType.AZURE_KEY_VAULT as const; + * // ... implement abstract methods + * } + * ``` + * + * 7. Add to the factory in providerFactory.ts: + * ``` + * case SecretProviderType.AZURE_KEY_VAULT: + * return new AzureKeyVaultProvider(config as AzureKeyVaultProviderConfig); + * ``` + * + * That's it! TypeScript will now enforce type safety for your new provider + * throughout the entire system. + */ + +export {}; diff --git a/src/lib/secretsManager/baseTypes.ts b/src/lib/secretsManager/baseTypes.ts new file mode 100644 index 00000000..748d0cb7 --- /dev/null +++ b/src/lib/secretsManager/baseTypes.ts @@ -0,0 +1,29 @@ +export enum SecretProviderType { + AWS_SECRETS_MANAGER = "aws", + HASHICORP_VAULT = "vault", +} + +/** + * Generic provider configuration wrapper that adds metadata to credentials. + * + * @template T - The provider type + * @template C - The provider-specific credentials type + */ +export interface ProviderConfig { + id: string; + type: T; + name: string; + createdAt: number; + updatedAt: number; + credentials: C; +} + +/** + * Base secret reference interface. + * Provider-specific implementations extend this with additional fields. + * + * @template T - The provider type + */ +export interface SecretReference { + type: T; +} diff --git a/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts index 3d7850df..c493ef11 100644 --- a/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts @@ -1,11 +1,18 @@ -import { SecretProviderConfig } from "../types"; +import { SecretProviderConfig, SecretProviderType } from "../types"; import { AbstractSecretsManagerStorage } from "../encryptedStorage/AbstractSecretsManagerStorage"; import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider"; +/** + * Abstract registry for managing secret providers. + * + * The registry stores provider configurations and maintains instances of providers. + * Providers are stored with type erasure but maintain their full type safety + * when accessed through type-aware methods. + */ export abstract class AbstractProviderRegistry { protected store: AbstractSecretsManagerStorage; - protected providers: Map = new Map(); + protected providers: Map> = new Map(); constructor(store: AbstractSecretsManagerStorage) { this.store = store; @@ -21,5 +28,20 @@ export abstract class AbstractProviderRegistry { abstract deleteProviderConfig(_id: string): Promise; - abstract getProvider(_providerId: string): AbstractSecretProvider | null; + abstract getProvider(_providerId: string): AbstractSecretProvider | null; + + /** + * Type-safe method to get a provider with a specific type. + * Returns the provider cast to the correct generic type. + */ + getTypedProvider( + providerId: string, + expectedType: T + ): AbstractSecretProvider | null { + const provider = this.getProvider(providerId); + if (provider && provider.type === expectedType) { + return provider as AbstractSecretProvider; + } + return null; + } } diff --git a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts index 31a41a6d..7c23159b 100644 --- a/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts +++ b/src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts @@ -1,4 +1,4 @@ -import { SecretProviderConfig } from "../types"; +import { SecretProviderConfig, SecretProviderType } from "../types"; import { createProviderInstance } from "../providerService/providerFactory"; import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider"; import { AbstractProviderRegistry } from "./AbstractProviderRegistry"; @@ -49,7 +49,7 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry { this.providers.delete(id); } - getProvider(providerId: string): AbstractSecretProvider | null { + getProvider(providerId: string): AbstractSecretProvider | null { return this.providers.get(providerId) ?? null; } } diff --git a/src/lib/secretsManager/providerService/AbstractSecretProvider.ts b/src/lib/secretsManager/providerService/AbstractSecretProvider.ts index 6ee59c58..0a91ad4d 100644 --- a/src/lib/secretsManager/providerService/AbstractSecretProvider.ts +++ b/src/lib/secretsManager/providerService/AbstractSecretProvider.ts @@ -1,51 +1,57 @@ +import { SecretProviderType } from "../baseTypes"; import { - ProviderSpecificConfig, - SecretProviderType, - SecretReference, - SecretValue, + CredentialsForProvider, + ReferenceForProvider, + ValueForProvider, } from "../types"; const DEFAULT_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour const DEFAULT_MAX_CACHE_SIZE = 100; -export abstract class AbstractSecretProvider { - protected cache: Map = new Map(); - - /** Cache TTL in milliseconds. Subclasses can override. */ +/** + * Generic abstract base class for secret providers. + * + * @template T - The provider type + */ +export abstract class AbstractSecretProvider { + protected cache: Map> = new Map(); protected cacheTtlMs: number = DEFAULT_CACHE_TTL_MS; - - /** Maximum cache size (Size of the map). Subclasses can override. */ protected maxCacheSize: number = DEFAULT_MAX_CACHE_SIZE; - abstract readonly type: SecretProviderType; + abstract readonly type: T; abstract readonly id: string; - protected abstract config: ProviderSpecificConfig; + protected abstract config: CredentialsForProvider; - protected abstract getCacheKey(_ref: SecretReference): string; + protected abstract getCacheKey(_ref: ReferenceForProvider): string; abstract testConnection(): Promise; - abstract getSecret(_ref: SecretReference): Promise; + abstract getSecret(_ref: ReferenceForProvider): Promise | null>; abstract getSecrets( - _refs: SecretReference[] - ): Promise<(SecretValue | null)[]>; + _refs: ReferenceForProvider[] + ): Promise<(ValueForProvider | null)[]>; - abstract setSecret(): Promise; + abstract setSecret( + _ref: ReferenceForProvider, + _value: string | Record + ): Promise; - abstract setSecrets(): Promise; + abstract setSecrets( + _entries: Array<{ ref: ReferenceForProvider; value: string | Record }> + ): Promise; - abstract removeSecret(): Promise; + abstract removeSecret(_ref: ReferenceForProvider): Promise; - abstract removeSecrets(): Promise; + abstract removeSecrets(_refs: ReferenceForProvider[]): Promise; protected invalidateCache(): void { this.cache.clear(); } - protected getCachedSecret(key: string): SecretValue | null { + protected getCachedSecret(key: string): ValueForProvider | null { const cached = this.cache.get(key); if (cached && cached.fetchedAt + this.cacheTtlMs > Date.now()) { return cached; @@ -53,7 +59,7 @@ export abstract class AbstractSecretProvider { return null; } - protected setCacheEntry(key: string, value: SecretValue): void { + protected setCacheEntry(key: string, value: ValueForProvider): void { if (this.maxCacheSize <= 0) { return; } @@ -84,7 +90,7 @@ export abstract class AbstractSecretProvider { keysToDelete.forEach((key) => this.cache.delete(key)); } - abstract refreshSecrets(): Promise<(SecretValue | null)[]>; + abstract refreshSecrets(): Promise<(ValueForProvider | null)[]>; static validateConfig(config: any): boolean { // Base implementation rejects all configs as a fail-safe. diff --git a/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts b/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts index 32a2f9f2..f6d5af74 100644 --- a/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts +++ b/src/lib/secretsManager/providerService/awsSecretManagerProvider.ts @@ -1,30 +1,53 @@ -import { - AwsSecretReference, - AWSSecretsManagerConfig, - AwsSecretValue, - SecretProviderConfig, - SecretProviderType, -} from "../types"; +import { SecretProviderType, ProviderConfig, SecretReference } from "../baseTypes"; import { AbstractSecretProvider } from "./AbstractSecretProvider"; import { GetSecretValueCommand, + GetSecretValueCommandOutput, ListSecretsCommand, SecretsManagerClient, } from "@aws-sdk/client-secrets-manager"; -export class AWSSecretsManagerProvider extends AbstractSecretProvider { - readonly type = SecretProviderType.AWS_SECRETS_MANAGER; +export interface AWSSecretsManagerCredentials { + accessKeyId: string; + secretAccessKey: string; + region: string; + sessionToken?: string; +} + +export type AWSSecretProviderConfig = ProviderConfig< + SecretProviderType.AWS_SECRETS_MANAGER, + AWSSecretsManagerCredentials +>; + +export interface AwsSecretReference extends SecretReference { + identifier: string; + version?: string; +} + +export interface AwsSecretValue { + type: SecretProviderType.AWS_SECRETS_MANAGER; + providerId: string; + secretReference: AwsSecretReference; + fetchedAt: number; + name: GetSecretValueCommandOutput["Name"]; + value: GetSecretValueCommandOutput["SecretString"]; + ARN: GetSecretValueCommandOutput["ARN"]; + versionId: GetSecretValueCommandOutput["VersionId"]; +} + +export class AWSSecretsManagerProvider extends AbstractSecretProvider { + readonly type = SecretProviderType.AWS_SECRETS_MANAGER as const; readonly id: string; - protected config: AWSSecretsManagerConfig; + protected config: AWSSecretsManagerCredentials; private client: SecretsManagerClient; - constructor(providerConfig: SecretProviderConfig) { + constructor(providerConfig: AWSSecretProviderConfig) { super(); this.id = providerConfig.id; - this.config = providerConfig.config as AWSSecretsManagerConfig; + this.config = providerConfig.credentials; this.client = new SecretsManagerClient({ region: this.config.region, credentials: { @@ -95,6 +118,7 @@ export class AWSSecretsManagerProvider extends AbstractSecretProvider { } const awsSecret: AwsSecretValue = { + type: SecretProviderType.AWS_SECRETS_MANAGER, providerId: this.id, secretReference: ref, fetchedAt: Date.now(), @@ -122,19 +146,24 @@ export class AWSSecretsManagerProvider extends AbstractSecretProvider { return Promise.all(refs.map((ref) => this.getSecret(ref))); } - async setSecret(): Promise { + async setSecret( + _ref: AwsSecretReference, + _value: string | Record + ): Promise { throw new Error("Method not implemented."); } - async setSecrets(): Promise { + async setSecrets( + _entries: Array<{ ref: AwsSecretReference; value: string | Record }> + ): Promise { throw new Error("Method not implemented."); } - async removeSecret(): Promise { + async removeSecret(_ref: AwsSecretReference): Promise { throw new Error("Method not implemented."); } - async removeSecrets(): Promise { + async removeSecrets(_refs: AwsSecretReference[]): Promise { throw new Error("Method not implemented."); } @@ -148,7 +177,7 @@ export class AWSSecretsManagerProvider extends AbstractSecretProvider { return this.getSecrets(allSecretRefs); } - static validateConfig(config: AWSSecretsManagerConfig): boolean { + static validateConfig(config: AWSSecretsManagerCredentials): boolean { return Boolean( config.accessKeyId && config.secretAccessKey && config.region ); diff --git a/src/lib/secretsManager/providerService/hashicorpVaultProvider.ts b/src/lib/secretsManager/providerService/hashicorpVaultProvider.ts new file mode 100644 index 00000000..d23275fd --- /dev/null +++ b/src/lib/secretsManager/providerService/hashicorpVaultProvider.ts @@ -0,0 +1,93 @@ +import { SecretProviderType, ProviderConfig, SecretReference } from "../baseTypes"; +import { AbstractSecretProvider } from "./AbstractSecretProvider"; + +export interface HashicorpVaultCredentials { + address: string; + token?: string; + namespace?: string; + apiVersion?: string; +} + +export type HashicorpVaultProviderConfig = ProviderConfig< + SecretProviderType.HASHICORP_VAULT, + HashicorpVaultCredentials +>; + +export interface VaultSecretReference extends SecretReference { + path: string; + version?: number; +} + +export interface VaultSecretValue { + type: SecretProviderType.HASHICORP_VAULT; + providerId: string; + secretReference: VaultSecretReference; + fetchedAt: number; + path: string; + data: Record; + metadata?: { + version: number; + created_time: string; + deletion_time?: string; + destroyed?: boolean; + }; +} + +export class HashicorpVaultProvider extends AbstractSecretProvider { + readonly type = SecretProviderType.HASHICORP_VAULT as const; + + readonly id: string; + + protected config: HashicorpVaultCredentials; + + constructor(providerConfig: HashicorpVaultProviderConfig) { + super(); + this.id = providerConfig.id; + this.config = providerConfig.credentials; + } + + protected getCacheKey(ref: VaultSecretReference): string { + return `path:${ref.path};version:${ref.version ?? "latest"}`; + } + + async testConnection(): Promise { + throw new Error("Method not implemented."); + } + + async getSecret(_ref: VaultSecretReference): Promise { + throw new Error("Method not implemented."); + } + + async getSecrets(_refs: VaultSecretReference[]): Promise<(VaultSecretValue | null)[]> { + throw new Error("Method not implemented."); + } + + async setSecret( + _ref: VaultSecretReference, + _value: string | Record + ): Promise { + throw new Error("Method not implemented."); + } + + async setSecrets( + _entries: Array<{ ref: VaultSecretReference; value: string | Record }> + ): Promise { + throw new Error("Method not implemented."); + } + + async removeSecret(_ref: VaultSecretReference): Promise { + throw new Error("Method not implemented."); + } + + async removeSecrets(_refs: VaultSecretReference[]): Promise { + throw new Error("Method not implemented."); + } + + async refreshSecrets(): Promise<(VaultSecretValue | null)[]> { + throw new Error("Method not implemented."); + } + + static validateConfig(config: HashicorpVaultCredentials): boolean { + return Boolean(config.address); + } +} diff --git a/src/lib/secretsManager/providerService/providerFactory.ts b/src/lib/secretsManager/providerService/providerFactory.ts index 00e108ed..ac2c2f38 100644 --- a/src/lib/secretsManager/providerService/providerFactory.ts +++ b/src/lib/secretsManager/providerService/providerFactory.ts @@ -1,14 +1,37 @@ import { SecretProviderConfig, SecretProviderType } from "../types"; -import { AWSSecretsManagerProvider } from "./awsSecretManagerProvider"; +import { + AWSSecretsManagerProvider, + AWSSecretProviderConfig, +} from "./awsSecretManagerProvider"; +import { + HashicorpVaultProvider, + HashicorpVaultProviderConfig, +} from "./hashicorpVaultProvider"; import { AbstractSecretProvider } from "./AbstractSecretProvider"; +/** + * Type-safe provider factory that creates the appropriate provider instance + * based on the configuration type. + * + * TypeScript will narrow the config type within each case, ensuring type safety. + */ export function createProviderInstance( config: SecretProviderConfig -): AbstractSecretProvider { +): AbstractSecretProvider { switch (config.type) { - case SecretProviderType.AWS_SECRETS_MANAGER: + case SecretProviderType.AWS_SECRETS_MANAGER: { + // TypeScript knows config is AWSSecretProviderConfig here return new AWSSecretsManagerProvider(config); - default: - throw new Error(`Unknown provider type: ${config.type}`); + } + case SecretProviderType.HASHICORP_VAULT: { + // TypeScript knows config is HashicorpVaultProviderConfig here + return new HashicorpVaultProvider(config); + } + default: { + // Exhaustiveness check - TypeScript will error if we miss a case + const _exhaustive: never = config; + throw new Error(`Unknown provider type: ${(_exhaustive as any).type}`); + } } } + diff --git a/src/lib/secretsManager/types.ts b/src/lib/secretsManager/types.ts index ee012e86..dc62f163 100644 --- a/src/lib/secretsManager/types.ts +++ b/src/lib/secretsManager/types.ts @@ -1,46 +1,68 @@ -import { GetSecretValueCommandOutput } from "@aws-sdk/client-secrets-manager"; +import type { SecretProviderType, ProviderConfig, SecretReference as BaseSecretReference } from "./baseTypes"; -export enum SecretProviderType { - AWS_SECRETS_MANAGER = "aws", -} +import type { + AWSSecretsManagerCredentials, + AWSSecretProviderConfig, + AwsSecretReference, + AwsSecretValue, +} from "./providerService/awsSecretManagerProvider"; -export interface AWSSecretsManagerConfig { - accessKeyId: string; - secretAccessKey: string; - region: string; - sessionToken?: string; -} +import type { + HashicorpVaultCredentials, + HashicorpVaultProviderConfig, + VaultSecretReference, + VaultSecretValue, +} from "./providerService/hashicorpVaultProvider"; -export type ProviderSpecificConfig = AWSSecretsManagerConfig; // | HashicorpVaultConfig | OtherProviderConfig; +export { SecretProviderType, ProviderConfig, SecretReference as BaseSecretReference } from "./baseTypes"; -export interface SecretProviderConfig { - id: string; - type: SecretProviderType; - name: string; - createdAt: number; - updatedAt: number; - config: ProviderSpecificConfig; -} +export type ProviderCredentials = + | AWSSecretsManagerCredentials + | HashicorpVaultCredentials; -export type AwsSecretReference = { - type: SecretProviderType.AWS_SECRETS_MANAGER; - identifier: string; // ARN or Name - version?: string; -}; +export type SecretProviderConfig = + | AWSSecretProviderConfig + | HashicorpVaultProviderConfig; -export type SecretReference = AwsSecretReference; // | VaultSecretReference; // | OtherProviderSecretReference; +export type SecretReference = + | AwsSecretReference + | VaultSecretReference; -interface BaseSecretValue { - providerId: string; - secretReference: SecretReference; - fetchedAt: number; -} +export type SecretValue = + | AwsSecretValue + | VaultSecretValue; + +export type { + AWSSecretsManagerCredentials, + AWSSecretProviderConfig, + AwsSecretReference, + AwsSecretValue, + HashicorpVaultCredentials, + HashicorpVaultProviderConfig, + VaultSecretReference, + VaultSecretValue, +}; -export interface AwsSecretValue extends BaseSecretValue { - name: GetSecretValueCommandOutput["Name"]; - value: GetSecretValueCommandOutput["SecretString"]; - ARN: GetSecretValueCommandOutput["ARN"]; - versionId: GetSecretValueCommandOutput["VersionId"]; +/** + * Type map for compile-time type lookup. + * Enables AbstractSecretProvider to infer correct types automatically. + */ +export interface ProviderTypeMap { + [SecretProviderType.AWS_SECRETS_MANAGER]: { + credentials: AWSSecretsManagerCredentials; + providerConfig: AWSSecretProviderConfig; + reference: AwsSecretReference; + value: AwsSecretValue; + }; + [SecretProviderType.HASHICORP_VAULT]: { + credentials: HashicorpVaultCredentials; + providerConfig: HashicorpVaultProviderConfig; + reference: VaultSecretReference; + value: VaultSecretValue; + }; } -export type SecretValue = AwsSecretValue; // | VaultSecretValue; // | OtherProviderSecretValue; +export type CredentialsForProvider = ProviderTypeMap[T]["credentials"]; +export type ProviderConfigForProvider = ProviderTypeMap[T]["providerConfig"]; +export type ReferenceForProvider = ProviderTypeMap[T]["reference"]; +export type ValueForProvider = ProviderTypeMap[T]["value"]; From caaf624d4cb1ac01b46131a3e5f833a76f20a676 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Fri, 30 Jan 2026 11:58:17 +0530 Subject: [PATCH 36/37] fix: types --- src/lib/secretsManager/USAGE_EXAMPLES.ts | 40 ++++---- src/lib/secretsManager/baseTypes.ts | 5 +- .../providerService/hashicorpVaultProvider.ts | 93 ------------------- .../providerService/providerFactory.ts | 24 +---- src/lib/secretsManager/types.ts | 56 ++++------- 5 files changed, 45 insertions(+), 173 deletions(-) delete mode 100644 src/lib/secretsManager/providerService/hashicorpVaultProvider.ts diff --git a/src/lib/secretsManager/USAGE_EXAMPLES.ts b/src/lib/secretsManager/USAGE_EXAMPLES.ts index bc807757..c672669b 100644 --- a/src/lib/secretsManager/USAGE_EXAMPLES.ts +++ b/src/lib/secretsManager/USAGE_EXAMPLES.ts @@ -1,6 +1,6 @@ /** * Usage Examples for Type-Safe Secrets Manager - * + * * This file demonstrates how TypeScript automatically infers types * throughout the secrets manager system. */ @@ -12,9 +12,9 @@ import { AwsSecretReference, VaultSecretReference, } from "./types"; -import { createProviderInstance, createTypedProviderInstance } from "./providerService/providerFactory"; +import { createProviderInstance } from "./providerService/providerFactory"; import { AWSSecretsManagerProvider } from "./providerService/awsSecretManagerProvider"; -import { HashicorpVaultProvider } from "./providerService/hashicorpVaultProvider"; +// import { HashicorpVaultProvider } from "./providerService/hashicorpVaultProvider"; // ============================================================================ // Example 1: Creating Provider Configurations (Type-Safe) @@ -72,7 +72,7 @@ async function example2() { // Strongly-typed factory - returns specific provider type const typedAwsProvider = createTypedProviderInstance(awsConfig); // typedAwsProvider is AbstractSecretProvider - + const typedVaultProvider = createTypedProviderInstance(vaultConfig); // typedVaultProvider is AbstractSecretProvider } @@ -243,10 +243,10 @@ async function example6() { type: SecretProviderType.AWS_SECRETS_MANAGER, identifier: "my-secret", }; - + const secret = await awsProvider.getSecret(ref); // TypeScript knows: secret is AwsSecretValue | null - + if (secret) { console.log(secret.ARN); // ✅ Type-safe access to AWS-specific fields } @@ -259,10 +259,10 @@ async function example6() { async function example7() { const registry: any = null; // FileBasedProviderRegistry instance - + // Get a provider and use type guards const provider = registry.getProvider("some-provider-id"); - + if (provider) { // Runtime check with type narrowing if (provider.type === SecretProviderType.AWS_SECRETS_MANAGER) { @@ -282,7 +282,7 @@ async function example7() { /** * To add a new provider (e.g., Azure Key Vault): - * + * * 1. Add the provider type to the enum in types.ts: * ``` * export enum SecretProviderType { @@ -291,7 +291,7 @@ async function example7() { * AZURE_KEY_VAULT = "azure", // ← Add this * } * ``` - * + * * 2. Add the config interface in types.ts: * ``` * export interface AzureKeyVaultConfig { @@ -301,34 +301,34 @@ async function example7() { * clientSecret: string; * } * ``` - * + * * 3. Add to the discriminated unions in types.ts: * ``` * export type AzureKeyVaultProviderConfig = ProviderConfig< * SecretProviderType.AZURE_KEY_VAULT, * AzureKeyVaultConfig * >; - * - * export type SecretProviderConfig = - * | AWSSecretProviderConfig + * + * export type SecretProviderConfig = + * | AWSSecretProviderConfig * | HashicorpVaultProviderConfig * | AzureKeyVaultProviderConfig; // ← Add this * ``` - * + * * 4. Add reference and value types in types.ts: * ``` * export interface AzureSecretReference extends BaseSecretReference<...> { * name: string; * version?: string; * } - * + * * export interface AzureSecretValue extends BaseSecretValue<...> { * value: string; * id: string; * // ... other Azure-specific fields * } * ``` - * + * * 5. Add to the type map in types.ts: * ``` * export interface ProviderTypeMap { @@ -342,7 +342,7 @@ async function example7() { * }; * } * ``` - * + * * 6. Create the provider class (azureKeyVaultProvider.ts): * ``` * export class AzureKeyVaultProvider extends AbstractSecretProvider< @@ -352,13 +352,13 @@ async function example7() { * // ... implement abstract methods * } * ``` - * + * * 7. Add to the factory in providerFactory.ts: * ``` * case SecretProviderType.AZURE_KEY_VAULT: * return new AzureKeyVaultProvider(config as AzureKeyVaultProviderConfig); * ``` - * + * * That's it! TypeScript will now enforce type safety for your new provider * throughout the entire system. */ diff --git a/src/lib/secretsManager/baseTypes.ts b/src/lib/secretsManager/baseTypes.ts index 748d0cb7..8d7368b9 100644 --- a/src/lib/secretsManager/baseTypes.ts +++ b/src/lib/secretsManager/baseTypes.ts @@ -1,11 +1,10 @@ export enum SecretProviderType { AWS_SECRETS_MANAGER = "aws", - HASHICORP_VAULT = "vault", } /** * Generic provider configuration wrapper that adds metadata to credentials. - * + * * @template T - The provider type * @template C - The provider-specific credentials type */ @@ -21,7 +20,7 @@ export interface ProviderConfig { /** * Base secret reference interface. * Provider-specific implementations extend this with additional fields. - * + * * @template T - The provider type */ export interface SecretReference { diff --git a/src/lib/secretsManager/providerService/hashicorpVaultProvider.ts b/src/lib/secretsManager/providerService/hashicorpVaultProvider.ts deleted file mode 100644 index d23275fd..00000000 --- a/src/lib/secretsManager/providerService/hashicorpVaultProvider.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { SecretProviderType, ProviderConfig, SecretReference } from "../baseTypes"; -import { AbstractSecretProvider } from "./AbstractSecretProvider"; - -export interface HashicorpVaultCredentials { - address: string; - token?: string; - namespace?: string; - apiVersion?: string; -} - -export type HashicorpVaultProviderConfig = ProviderConfig< - SecretProviderType.HASHICORP_VAULT, - HashicorpVaultCredentials ->; - -export interface VaultSecretReference extends SecretReference { - path: string; - version?: number; -} - -export interface VaultSecretValue { - type: SecretProviderType.HASHICORP_VAULT; - providerId: string; - secretReference: VaultSecretReference; - fetchedAt: number; - path: string; - data: Record; - metadata?: { - version: number; - created_time: string; - deletion_time?: string; - destroyed?: boolean; - }; -} - -export class HashicorpVaultProvider extends AbstractSecretProvider { - readonly type = SecretProviderType.HASHICORP_VAULT as const; - - readonly id: string; - - protected config: HashicorpVaultCredentials; - - constructor(providerConfig: HashicorpVaultProviderConfig) { - super(); - this.id = providerConfig.id; - this.config = providerConfig.credentials; - } - - protected getCacheKey(ref: VaultSecretReference): string { - return `path:${ref.path};version:${ref.version ?? "latest"}`; - } - - async testConnection(): Promise { - throw new Error("Method not implemented."); - } - - async getSecret(_ref: VaultSecretReference): Promise { - throw new Error("Method not implemented."); - } - - async getSecrets(_refs: VaultSecretReference[]): Promise<(VaultSecretValue | null)[]> { - throw new Error("Method not implemented."); - } - - async setSecret( - _ref: VaultSecretReference, - _value: string | Record - ): Promise { - throw new Error("Method not implemented."); - } - - async setSecrets( - _entries: Array<{ ref: VaultSecretReference; value: string | Record }> - ): Promise { - throw new Error("Method not implemented."); - } - - async removeSecret(_ref: VaultSecretReference): Promise { - throw new Error("Method not implemented."); - } - - async removeSecrets(_refs: VaultSecretReference[]): Promise { - throw new Error("Method not implemented."); - } - - async refreshSecrets(): Promise<(VaultSecretValue | null)[]> { - throw new Error("Method not implemented."); - } - - static validateConfig(config: HashicorpVaultCredentials): boolean { - return Boolean(config.address); - } -} diff --git a/src/lib/secretsManager/providerService/providerFactory.ts b/src/lib/secretsManager/providerService/providerFactory.ts index ac2c2f38..794c44fb 100644 --- a/src/lib/secretsManager/providerService/providerFactory.ts +++ b/src/lib/secretsManager/providerService/providerFactory.ts @@ -1,20 +1,7 @@ import { SecretProviderConfig, SecretProviderType } from "../types"; -import { - AWSSecretsManagerProvider, - AWSSecretProviderConfig, -} from "./awsSecretManagerProvider"; -import { - HashicorpVaultProvider, - HashicorpVaultProviderConfig, -} from "./hashicorpVaultProvider"; +import { AWSSecretsManagerProvider } from "./awsSecretManagerProvider"; import { AbstractSecretProvider } from "./AbstractSecretProvider"; -/** - * Type-safe provider factory that creates the appropriate provider instance - * based on the configuration type. - * - * TypeScript will narrow the config type within each case, ensuring type safety. - */ export function createProviderInstance( config: SecretProviderConfig ): AbstractSecretProvider { @@ -23,15 +10,10 @@ export function createProviderInstance( // TypeScript knows config is AWSSecretProviderConfig here return new AWSSecretsManagerProvider(config); } - case SecretProviderType.HASHICORP_VAULT: { - // TypeScript knows config is HashicorpVaultProviderConfig here - return new HashicorpVaultProvider(config); - } + default: { // Exhaustiveness check - TypeScript will error if we miss a case - const _exhaustive: never = config; - throw new Error(`Unknown provider type: ${(_exhaustive as any).type}`); + throw new Error(`Unknown provider type: ${(config as any).type}`); } } } - diff --git a/src/lib/secretsManager/types.ts b/src/lib/secretsManager/types.ts index dc62f163..8089ea75 100644 --- a/src/lib/secretsManager/types.ts +++ b/src/lib/secretsManager/types.ts @@ -1,5 +1,4 @@ -import type { SecretProviderType, ProviderConfig, SecretReference as BaseSecretReference } from "./baseTypes"; - +import { SecretProviderType } from "./baseTypes"; import type { AWSSecretsManagerCredentials, AWSSecretProviderConfig, @@ -7,40 +6,27 @@ import type { AwsSecretValue, } from "./providerService/awsSecretManagerProvider"; -import type { - HashicorpVaultCredentials, - HashicorpVaultProviderConfig, - VaultSecretReference, - VaultSecretValue, -} from "./providerService/hashicorpVaultProvider"; - -export { SecretProviderType, ProviderConfig, SecretReference as BaseSecretReference } from "./baseTypes"; +export { + SecretProviderType, + ProviderConfig, + SecretReference as BaseSecretReference, +} from "./baseTypes"; -export type ProviderCredentials = - | AWSSecretsManagerCredentials - | HashicorpVaultCredentials; +export type ProviderCredentials = AWSSecretsManagerCredentials; +// | HashicorpVaultCredentials; -export type SecretProviderConfig = - | AWSSecretProviderConfig - | HashicorpVaultProviderConfig; +export type SecretProviderConfig = AWSSecretProviderConfig; +// | HashicorpVaultProviderConfig; -export type SecretReference = - | AwsSecretReference - | VaultSecretReference; +export type SecretReference = AwsSecretReference; // | VaultSecretReference; -export type SecretValue = - | AwsSecretValue - | VaultSecretValue; +export type SecretValue = AwsSecretValue; // | VaultSecretValue; export type { AWSSecretsManagerCredentials, AWSSecretProviderConfig, AwsSecretReference, AwsSecretValue, - HashicorpVaultCredentials, - HashicorpVaultProviderConfig, - VaultSecretReference, - VaultSecretValue, }; /** @@ -54,15 +40,13 @@ export interface ProviderTypeMap { reference: AwsSecretReference; value: AwsSecretValue; }; - [SecretProviderType.HASHICORP_VAULT]: { - credentials: HashicorpVaultCredentials; - providerConfig: HashicorpVaultProviderConfig; - reference: VaultSecretReference; - value: VaultSecretValue; - }; } -export type CredentialsForProvider = ProviderTypeMap[T]["credentials"]; -export type ProviderConfigForProvider = ProviderTypeMap[T]["providerConfig"]; -export type ReferenceForProvider = ProviderTypeMap[T]["reference"]; -export type ValueForProvider = ProviderTypeMap[T]["value"]; +export type CredentialsForProvider = + ProviderTypeMap[T]["credentials"]; +export type ProviderConfigForProvider = + ProviderTypeMap[T]["providerConfig"]; +export type ReferenceForProvider = + ProviderTypeMap[T]["reference"]; +export type ValueForProvider = + ProviderTypeMap[T]["value"]; From 7bce33945c4131f9d7ad08c2fa091812597f8875 Mon Sep 17 00:00:00 2001 From: nafees87n Date: Fri, 30 Jan 2026 13:19:23 +0530 Subject: [PATCH 37/37] readded vault types --- src/lib/secretsManager/baseTypes.ts | 1 + .../providerService/AbstractSecretProvider.ts | 4 +- .../providerService/hashicorpVaultProvider.ts | 93 +++++++++++++++++++ src/lib/secretsManager/types.ts | 31 +++++-- 4 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 src/lib/secretsManager/providerService/hashicorpVaultProvider.ts diff --git a/src/lib/secretsManager/baseTypes.ts b/src/lib/secretsManager/baseTypes.ts index 8d7368b9..01a8f015 100644 --- a/src/lib/secretsManager/baseTypes.ts +++ b/src/lib/secretsManager/baseTypes.ts @@ -1,5 +1,6 @@ export enum SecretProviderType { AWS_SECRETS_MANAGER = "aws", + HASHICORP_VAULT = "hashicorp_vault", } /** diff --git a/src/lib/secretsManager/providerService/AbstractSecretProvider.ts b/src/lib/secretsManager/providerService/AbstractSecretProvider.ts index 0a91ad4d..ecc7b827 100644 --- a/src/lib/secretsManager/providerService/AbstractSecretProvider.ts +++ b/src/lib/secretsManager/providerService/AbstractSecretProvider.ts @@ -10,12 +10,14 @@ const DEFAULT_MAX_CACHE_SIZE = 100; /** * Generic abstract base class for secret providers. - * + * * @template T - The provider type */ export abstract class AbstractSecretProvider { protected cache: Map> = new Map(); + protected cacheTtlMs: number = DEFAULT_CACHE_TTL_MS; + protected maxCacheSize: number = DEFAULT_MAX_CACHE_SIZE; abstract readonly type: T; diff --git a/src/lib/secretsManager/providerService/hashicorpVaultProvider.ts b/src/lib/secretsManager/providerService/hashicorpVaultProvider.ts new file mode 100644 index 00000000..b7017cf6 --- /dev/null +++ b/src/lib/secretsManager/providerService/hashicorpVaultProvider.ts @@ -0,0 +1,93 @@ +import { SecretProviderType, ProviderConfig, SecretReference } from "../baseTypes"; +import { AbstractSecretProvider } from "./AbstractSecretProvider"; + +export interface HashicorpVaultCredentials { + address: string; + token?: string; + namespace?: string; + apiVersion?: string; +} + +export type HashicorpVaultProviderConfig = ProviderConfig< + SecretProviderType.HASHICORP_VAULT, + HashicorpVaultCredentials +>; + +export interface VaultSecretReference extends SecretReference { + path: string; + version?: number; +} + +export interface VaultSecretValue { + type: SecretProviderType.HASHICORP_VAULT; + providerId: string; + secretReference: VaultSecretReference; + fetchedAt: number; + path: string; + data: Record; + metadata?: { + version: number; + created_time: string; + deletion_time?: string; + destroyed?: boolean; + }; +} + +export class HashicorpVaultProvider extends AbstractSecretProvider { + readonly type = SecretProviderType.HASHICORP_VAULT as const; + + readonly id: string; + + protected config: HashicorpVaultCredentials; + + constructor(providerConfig: HashicorpVaultProviderConfig) { + super(); + this.id = providerConfig.id; + this.config = providerConfig.credentials; + } + + protected getCacheKey(ref: VaultSecretReference): string { + return `path:${ref.path};version:${ref.version ?? "latest"}`; + } + + async testConnection(): Promise { + throw new Error("Method not implemented."); + } + + async getSecret(_ref: VaultSecretReference): Promise { + throw new Error("Method not implemented."); + } + + async getSecrets(_refs: VaultSecretReference[]): Promise<(VaultSecretValue | null)[]> { + throw new Error("Method not implemented."); + } + + async setSecret( + _ref: VaultSecretReference, + _value: string | Record + ): Promise { + throw new Error("Method not implemented."); + } + + async setSecrets( + _entries: Array<{ ref: VaultSecretReference; value: string | Record }> + ): Promise { + throw new Error("Method not implemented."); + } + + async removeSecret(_ref: VaultSecretReference): Promise { + throw new Error("Method not implemented."); + } + + async removeSecrets(_refs: VaultSecretReference[]): Promise { + throw new Error("Method not implemented."); + } + + async refreshSecrets(): Promise<(VaultSecretValue | null)[]> { + throw new Error("Method not implemented."); + } + + static validateConfig(config: HashicorpVaultCredentials): boolean { + return Boolean(config.address); + } +} \ No newline at end of file diff --git a/src/lib/secretsManager/types.ts b/src/lib/secretsManager/types.ts index 8089ea75..8664ab17 100644 --- a/src/lib/secretsManager/types.ts +++ b/src/lib/secretsManager/types.ts @@ -6,27 +6,40 @@ import type { AwsSecretValue, } from "./providerService/awsSecretManagerProvider"; +import type { + HashicorpVaultCredentials, + HashicorpVaultProviderConfig, + VaultSecretReference, + VaultSecretValue, +} from "./providerService/hashicorpVaultProvider"; + export { SecretProviderType, ProviderConfig, SecretReference as BaseSecretReference, } from "./baseTypes"; -export type ProviderCredentials = AWSSecretsManagerCredentials; -// | HashicorpVaultCredentials; +export type ProviderCredentials = + | AWSSecretsManagerCredentials + | HashicorpVaultCredentials; -export type SecretProviderConfig = AWSSecretProviderConfig; -// | HashicorpVaultProviderConfig; +export type SecretProviderConfig = + | AWSSecretProviderConfig + | HashicorpVaultProviderConfig; -export type SecretReference = AwsSecretReference; // | VaultSecretReference; +export type SecretReference = AwsSecretReference | VaultSecretReference; -export type SecretValue = AwsSecretValue; // | VaultSecretValue; +export type SecretValue = AwsSecretValue | VaultSecretValue; export type { AWSSecretsManagerCredentials, AWSSecretProviderConfig, AwsSecretReference, AwsSecretValue, + HashicorpVaultCredentials, + HashicorpVaultProviderConfig, + VaultSecretReference, + VaultSecretValue, }; /** @@ -40,6 +53,12 @@ export interface ProviderTypeMap { reference: AwsSecretReference; value: AwsSecretValue; }; + [SecretProviderType.HASHICORP_VAULT]: { + credentials: HashicorpVaultCredentials; + providerConfig: HashicorpVaultProviderConfig; + reference: VaultSecretReference; + value: VaultSecretValue; + }; } export type CredentialsForProvider =