diff --git a/lib/redis.ts b/lib/redis.ts index 9adef177..6f520983 100644 --- a/lib/redis.ts +++ b/lib/redis.ts @@ -1,5 +1,8 @@ import Redis from 'ioredis'; import CryptoJS from 'crypto-js'; +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); // Initialize Redis client let redisClient: Redis | null = null; @@ -90,18 +93,20 @@ export const TTL = { }; interface EmailCredentials { + id: string; + userId: string; email: string; - password?: string; + password: string; host: string; port: number; - secure?: boolean; - encryptedPassword?: string; - smtp_host?: string; - smtp_port?: number; - smtp_secure?: boolean; - display_name?: string; - color?: string; - id?: string; // Add ID field to identify accounts + secure: boolean; + smtp_host: string | null; + smtp_port: number | null; + smtp_secure: boolean | null; + display_name: string | null; + color: string | null; + createdAt: Date; + updatedAt: Date; } interface ImapSessionData { @@ -114,131 +119,61 @@ interface ImapSessionData { /** * Cache email credentials in Redis */ -export async function cacheEmailCredentials( - userId: string, - credentials: EmailCredentials | EmailCredentials[] -): Promise { - const redis = getRedisClient(); - const key = KEYS.CREDENTIALS(userId); - - try { - console.log(`Caching credentials for user ${userId}`); - - // Handle both single account and array of accounts - const accountsToCache = Array.isArray(credentials) ? credentials : [credentials]; - - 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; - } - - // 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(validAccounts), 'EX', TTL.CREDENTIALS); - console.log(`Cached ${validAccounts.length} accounts for user ${userId}`); - } catch (error) { - console.error(`Error caching credentials for user ${userId}:`, error); - } +export async function cacheEmailCredentials(userId: string, credentials: EmailCredentials): Promise { + const client = await getRedisClient(); + const key = `email_credentials:${userId}`; + await client.set(key, JSON.stringify(credentials), 'EX', 3600); // 1 hour expiry } /** - * Get email credentials from Redis + * Get cached email credentials from Redis */ -export async function getEmailCredentials(userId: string): Promise { - const redis = getRedisClient(); - const key = KEYS.CREDENTIALS(userId); - - try { - const credStr = await redis.get(key); - - if (!credStr) { - return null; +export async function getCachedEmailCredentials(userId: string): Promise { + const client = await getRedisClient(); + const key = `email_credentials:${userId}`; + const data = await client.get(key); + return data ? JSON.parse(data) : null; +} + +/** + * Get email credentials from Redis or database + */ +export async function getEmailCredentials(userId: string): Promise { + // Try to get from cache first + const cached = await getCachedEmailCredentials(userId); + if (cached) { + return cached; + } + + // If not in cache, get from database + const credentials = await prisma.mailCredentials.findFirst({ + where: { userId }, + select: { + id: true, + userId: true, + email: true, + password: true, + host: true, + port: true, + secure: true, + smtp_host: true, + smtp_port: true, + smtp_secure: true, + display_name: true, + color: true, + createdAt: true, + updatedAt: true } - - const creds = JSON.parse(credStr) as EmailCredentials[]; - - // Handle both single account (backward compatibility) and array of accounts - const accounts = Array.isArray(creds) ? creds : [creds]; - - const decryptedAccounts = await Promise.all(accounts.map(async (account) => { - if (!account.encryptedPassword) { - console.warn(`No encrypted password found for account ${account.email}`); - 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 getting credentials for user ${userId}:`, error); + }); + + if (!credentials) { return null; } + + // Cache the credentials + await cacheEmailCredentials(userId, credentials); + + return credentials; } /** @@ -435,14 +370,4 @@ export async function invalidateUserEmailCache( } } while (cursor !== '0'); } -} - -/** - * Get cached email credentials from Redis - * @deprecated Use getEmailCredentials instead - */ -export async function getCachedEmailCredentials( - userId: string -): Promise { - return getEmailCredentials(userId); } \ No newline at end of file diff --git a/lib/services/email-service.ts b/lib/services/email-service.ts index 7e9134cd..20955608 100644 --- a/lib/services/email-service.ts +++ b/lib/services/email-service.ts @@ -67,7 +67,7 @@ export async function getImapConnection(userId: string): Promise { } // Cache credentials for future use - await cacheEmailCredentials(userId, credentials); + await cacheEmailCredentials(userId, [credentials]); } // Validate credentials @@ -170,19 +170,24 @@ export async function getUserEmailCredentials(userId: string): Promise