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;
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<void> {
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<EmailCredentials | null> {
export async function getEmailCredentials(userId: string): Promise<EmailCredentials[] | null> {
const redis = getRedisClient();
const key = KEYS.CREDENTIALS(userId);
@ -180,37 +196,47 @@ export async function getEmailCredentials(userId: string): Promise<EmailCredenti
return null;
}
const creds = JSON.parse(credStr) as EmailCredentials;
const creds = JSON.parse(credStr) as EmailCredentials[];
if (!creds.encryptedPassword) {
console.warn(`No encrypted password found for user ${userId}`);
return null;
}
// Handle both single account (backward compatibility) and array of accounts
const accounts = Array.isArray(creds) ? creds : [creds];
try {
// Decrypt the password
const password = decryptData(creds.encryptedPassword);
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 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<EmailCredentials | null> {
): Promise<EmailCredentials[] | null> {
return getEmailCredentials(userId);
}