courrier multi account restore compose

This commit is contained in:
alma 2025-04-28 12:08:58 +02:00
parent 3e6953a4a4
commit 6b37f0062d

View File

@ -101,6 +101,7 @@ interface EmailCredentials {
smtp_secure?: boolean; smtp_secure?: boolean;
display_name?: string; display_name?: string;
color?: string; color?: string;
id?: string; // Add ID field to identify accounts
} }
interface ImapSessionData { interface ImapSessionData {
@ -115,52 +116,67 @@ interface ImapSessionData {
*/ */
export async function cacheEmailCredentials( export async function cacheEmailCredentials(
userId: string, userId: string,
credentials: EmailCredentials credentials: EmailCredentials | EmailCredentials[]
): Promise<void> { ): Promise<void> {
const redis = getRedisClient(); const redis = getRedisClient();
const key = KEYS.CREDENTIALS(userId); 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 { try {
console.log(`Caching credentials for user ${userId}`); 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 // Create a copy without the password to store
const secureCredentials: EmailCredentials = { const secureCredentials: EmailCredentials = {
email: credentials.email, id: account.id,
host: credentials.host, email: account.email,
port: credentials.port, host: account.host,
secure: credentials.secure ?? true, port: account.port,
secure: account.secure ?? true,
// Include the extended fields // Include the extended fields
...(credentials.smtp_host && { smtp_host: credentials.smtp_host }), ...(account.smtp_host && { smtp_host: account.smtp_host }),
...(credentials.smtp_port && { smtp_port: credentials.smtp_port }), ...(account.smtp_port && { smtp_port: account.smtp_port }),
...(credentials.smtp_secure !== undefined && { smtp_secure: credentials.smtp_secure }), ...(account.smtp_secure !== undefined && { smtp_secure: account.smtp_secure }),
...(credentials.display_name && { display_name: credentials.display_name }), ...(account.display_name && { display_name: account.display_name }),
...(credentials.color && { color: credentials.color }) ...(account.color && { color: account.color })
}; };
// Encrypt password // Encrypt password
if (credentials.password) { if (account.password) {
try { try {
const encrypted = encryptData(credentials.password); const encrypted = encryptData(account.password);
console.log(`Successfully encrypted password for user ${userId}`); console.log(`Successfully encrypted password for account ${account.email}`);
secureCredentials.encryptedPassword = encrypted; secureCredentials.encryptedPassword = encrypted;
} catch (encryptError) { } catch (encryptError) {
console.error(`Failed to encrypt password for user ${userId}:`, encryptError); console.error(`Failed to encrypt password for account ${account.email}:`, encryptError);
// Don't proceed with caching if encryption fails return null;
return;
} }
} else { } else {
console.warn(`No password provided for user ${userId}, skipping credential caching`); 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; return;
} }
await redis.set(key, JSON.stringify(secureCredentials), 'EX', TTL.CREDENTIALS); await redis.set(key, JSON.stringify(validAccounts), 'EX', TTL.CREDENTIALS);
console.log(`Credentials cached for user ${userId}`); console.log(`Cached ${validAccounts.length} accounts for user ${userId}`);
} catch (error) { } catch (error) {
console.error(`Error caching credentials for user ${userId}:`, error); console.error(`Error caching credentials for user ${userId}:`, error);
} }
@ -169,7 +185,7 @@ export async function cacheEmailCredentials(
/** /**
* Get email credentials from Redis * Get email credentials from Redis
*/ */
export async function getEmailCredentials(userId: string): Promise<EmailCredentials | null> { export async function getEmailCredentials(userId: string): Promise<EmailCredentials[] | null> {
const redis = getRedisClient(); const redis = getRedisClient();
const key = KEYS.CREDENTIALS(userId); const key = KEYS.CREDENTIALS(userId);
@ -180,37 +196,47 @@ export async function getEmailCredentials(userId: string): Promise<EmailCredenti
return null; return null;
} }
const creds = JSON.parse(credStr) as EmailCredentials; const creds = JSON.parse(credStr) as EmailCredentials[];
if (!creds.encryptedPassword) { // Handle both single account (backward compatibility) and array of accounts
console.warn(`No encrypted password found for user ${userId}`); 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; return null;
} }
try { try {
// Decrypt the password // Decrypt the password
const password = decryptData(creds.encryptedPassword); const password = decryptData(account.encryptedPassword);
// Return the full credentials with decrypted password // Return the full credentials with decrypted password
return { return {
email: creds.email, id: account.id,
email: account.email,
password, password,
host: creds.host, host: account.host,
port: creds.port, port: account.port,
secure: creds.secure ?? true, secure: account.secure ?? true,
// Include the extended fields if they exist in the cache smtp_host: account.smtp_host,
...(creds.smtp_host && { smtp_host: creds.smtp_host }), smtp_port: account.smtp_port,
...(creds.smtp_port && { smtp_port: creds.smtp_port }), smtp_secure: account.smtp_secure,
...(creds.smtp_secure !== undefined && { smtp_secure: creds.smtp_secure }), display_name: account.display_name,
...(creds.display_name && { display_name: creds.display_name }), color: account.color
...(creds.color && { color: creds.color })
}; };
} catch (decryptError) { } catch (error) {
console.error(`Failed to decrypt password for user ${userId}:`, decryptError); console.error(`Failed to decrypt password for account ${account.email}:`, error);
return null; 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) { } catch (error) {
console.error(`Error retrieving credentials for user ${userId}:`, error); console.error(`Error getting credentials for user ${userId}:`, error);
return null; return null;
} }
} }
@ -417,6 +443,6 @@ export async function invalidateUserEmailCache(
*/ */
export async function getCachedEmailCredentials( export async function getCachedEmailCredentials(
userId: string userId: string
): Promise<EmailCredentials | null> { ): Promise<EmailCredentials[] | null> {
return getEmailCredentials(userId); return getEmailCredentials(userId);
} }