From 6b37f0062dfdc366973550e17bf143b429fe42d6 Mon Sep 17 00:00:00 2001 From: alma Date: Mon, 28 Apr 2025 12:08:58 +0200 Subject: [PATCH] courrier multi account restore compose --- lib/redis.ts | 152 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 89 insertions(+), 63 deletions(-) diff --git a/lib/redis.ts b/lib/redis.ts index 3ed63293..9adef177 100644 --- a/lib/redis.ts +++ b/lib/redis.ts @@ -101,6 +101,7 @@ interface EmailCredentials { smtp_secure?: boolean; display_name?: string; color?: string; + id?: string; // Add ID field to identify accounts } interface ImapSessionData { @@ -115,52 +116,67 @@ interface ImapSessionData { */ export async function cacheEmailCredentials( userId: string, - credentials: EmailCredentials + credentials: EmailCredentials | EmailCredentials[] ): Promise { const redis = getRedisClient(); const key = KEYS.CREDENTIALS(userId); - // Validate credentials before caching - if (!credentials.email || !credentials.host || !credentials.password) { - console.error(`Cannot cache incomplete credentials for user ${userId}`); - return; - } - try { console.log(`Caching credentials for user ${userId}`); - // Create a copy without the password to store - const secureCredentials: EmailCredentials = { - email: credentials.email, - host: credentials.host, - port: credentials.port, - secure: credentials.secure ?? true, - // Include the extended fields - ...(credentials.smtp_host && { smtp_host: credentials.smtp_host }), - ...(credentials.smtp_port && { smtp_port: credentials.smtp_port }), - ...(credentials.smtp_secure !== undefined && { smtp_secure: credentials.smtp_secure }), - ...(credentials.display_name && { display_name: credentials.display_name }), - ...(credentials.color && { color: credentials.color }) - }; + // Handle both single account and array of accounts + const accountsToCache = Array.isArray(credentials) ? credentials : [credentials]; - // Encrypt password - if (credentials.password) { - try { - const encrypted = encryptData(credentials.password); - console.log(`Successfully encrypted password for user ${userId}`); - secureCredentials.encryptedPassword = encrypted; - } catch (encryptError) { - console.error(`Failed to encrypt password for user ${userId}:`, encryptError); - // Don't proceed with caching if encryption fails - return; + const secureAccounts = await Promise.all(accountsToCache.map(async (account) => { + // Validate credentials before caching + if (!account.email || !account.host || !account.password) { + console.error(`Cannot cache incomplete credentials for account ${account.email}`); + return null; } - } else { - console.warn(`No password provided for user ${userId}, skipping credential caching`); + + // Create a copy without the password to store + const secureCredentials: EmailCredentials = { + id: account.id, + email: account.email, + host: account.host, + port: account.port, + secure: account.secure ?? true, + // Include the extended fields + ...(account.smtp_host && { smtp_host: account.smtp_host }), + ...(account.smtp_port && { smtp_port: account.smtp_port }), + ...(account.smtp_secure !== undefined && { smtp_secure: account.smtp_secure }), + ...(account.display_name && { display_name: account.display_name }), + ...(account.color && { color: account.color }) + }; + + // Encrypt password + if (account.password) { + try { + const encrypted = encryptData(account.password); + console.log(`Successfully encrypted password for account ${account.email}`); + secureCredentials.encryptedPassword = encrypted; + } catch (encryptError) { + console.error(`Failed to encrypt password for account ${account.email}:`, encryptError); + return null; + } + } else { + console.warn(`No password provided for account ${account.email}, skipping credential caching`); + return null; + } + + return secureCredentials; + })); + + // Filter out any null values from failed encryption + const validAccounts = secureAccounts.filter(acc => acc !== null); + + if (validAccounts.length === 0) { + console.warn(`No valid accounts to cache for user ${userId}`); return; } - await redis.set(key, JSON.stringify(secureCredentials), 'EX', TTL.CREDENTIALS); - console.log(`Credentials cached for user ${userId}`); + await redis.set(key, JSON.stringify(validAccounts), 'EX', TTL.CREDENTIALS); + console.log(`Cached ${validAccounts.length} accounts for user ${userId}`); } catch (error) { console.error(`Error caching credentials for user ${userId}:`, error); } @@ -169,7 +185,7 @@ export async function cacheEmailCredentials( /** * Get email credentials from Redis */ -export async function getEmailCredentials(userId: string): Promise { +export async function getEmailCredentials(userId: string): Promise { const redis = getRedisClient(); const key = KEYS.CREDENTIALS(userId); @@ -180,37 +196,47 @@ export async function getEmailCredentials(userId: string): Promise { + if (!account.encryptedPassword) { + console.warn(`No encrypted password found for account ${account.email}`); + return null; + } - // Return the full credentials with decrypted password - return { - email: creds.email, - password, - host: creds.host, - port: creds.port, - secure: creds.secure ?? true, - // Include the extended fields if they exist in the cache - ...(creds.smtp_host && { smtp_host: creds.smtp_host }), - ...(creds.smtp_port && { smtp_port: creds.smtp_port }), - ...(creds.smtp_secure !== undefined && { smtp_secure: creds.smtp_secure }), - ...(creds.display_name && { display_name: creds.display_name }), - ...(creds.color && { color: creds.color }) - }; - } catch (decryptError) { - console.error(`Failed to decrypt password for user ${userId}:`, decryptError); - return null; - } + try { + // Decrypt the password + const password = decryptData(account.encryptedPassword); + + // Return the full credentials with decrypted password + return { + id: account.id, + email: account.email, + password, + host: account.host, + port: account.port, + secure: account.secure ?? true, + smtp_host: account.smtp_host, + smtp_port: account.smtp_port, + smtp_secure: account.smtp_secure, + display_name: account.display_name, + color: account.color + }; + } catch (error) { + console.error(`Failed to decrypt password for account ${account.email}:`, error); + return null; + } + })); + + // Filter out any null values from failed decryption + const validAccounts = decryptedAccounts.filter(acc => acc !== null); + + return validAccounts.length > 0 ? validAccounts : null; } catch (error) { - console.error(`Error retrieving credentials for user ${userId}:`, error); + console.error(`Error getting credentials for user ${userId}:`, error); return null; } } @@ -417,6 +443,6 @@ export async function invalidateUserEmailCache( */ export async function getCachedEmailCredentials( userId: string -): Promise { +): Promise { return getEmailCredentials(userId); } \ No newline at end of file