courrier multi account restore compose
This commit is contained in:
parent
f1adfca1d9
commit
6ac1f8c5af
193
lib/redis.ts
193
lib/redis.ts
@ -1,5 +1,8 @@
|
|||||||
import Redis from 'ioredis';
|
import Redis from 'ioredis';
|
||||||
import CryptoJS from 'crypto-js';
|
import CryptoJS from 'crypto-js';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
// Initialize Redis client
|
// Initialize Redis client
|
||||||
let redisClient: Redis | null = null;
|
let redisClient: Redis | null = null;
|
||||||
@ -90,18 +93,20 @@ export const TTL = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface EmailCredentials {
|
interface EmailCredentials {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
password?: string;
|
password: string;
|
||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
secure?: boolean;
|
secure: boolean;
|
||||||
encryptedPassword?: string;
|
smtp_host: string | null;
|
||||||
smtp_host?: string;
|
smtp_port: number | null;
|
||||||
smtp_port?: number;
|
smtp_secure: boolean | null;
|
||||||
smtp_secure?: boolean;
|
display_name: string | null;
|
||||||
display_name?: string;
|
color: string | null;
|
||||||
color?: string;
|
createdAt: Date;
|
||||||
id?: string; // Add ID field to identify accounts
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImapSessionData {
|
interface ImapSessionData {
|
||||||
@ -114,131 +119,61 @@ interface ImapSessionData {
|
|||||||
/**
|
/**
|
||||||
* Cache email credentials in Redis
|
* Cache email credentials in Redis
|
||||||
*/
|
*/
|
||||||
export async function cacheEmailCredentials(
|
export async function cacheEmailCredentials(userId: string, credentials: EmailCredentials): Promise<void> {
|
||||||
userId: string,
|
const client = await getRedisClient();
|
||||||
credentials: EmailCredentials | EmailCredentials[]
|
const key = `email_credentials:${userId}`;
|
||||||
): Promise<void> {
|
await client.set(key, JSON.stringify(credentials), 'EX', 3600); // 1 hour expiry
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get email credentials from Redis
|
* Get cached email credentials from Redis
|
||||||
*/
|
*/
|
||||||
export async function getEmailCredentials(userId: string): Promise<EmailCredentials[] | null> {
|
export async function getCachedEmailCredentials(userId: string): Promise<EmailCredentials | null> {
|
||||||
const redis = getRedisClient();
|
const client = await getRedisClient();
|
||||||
const key = KEYS.CREDENTIALS(userId);
|
const key = `email_credentials:${userId}`;
|
||||||
|
const data = await client.get(key);
|
||||||
|
return data ? JSON.parse(data) : null;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
/**
|
||||||
const credStr = await redis.get(key);
|
* Get email credentials from Redis or database
|
||||||
|
*/
|
||||||
|
export async function getEmailCredentials(userId: string): Promise<EmailCredentials | null> {
|
||||||
|
// Try to get from cache first
|
||||||
|
const cached = await getCachedEmailCredentials(userId);
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
if (!credStr) {
|
// 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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!credentials) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const creds = JSON.parse(credStr) as EmailCredentials[];
|
// Cache the credentials
|
||||||
|
await cacheEmailCredentials(userId, credentials);
|
||||||
|
|
||||||
// Handle both single account (backward compatibility) and array of accounts
|
return credentials;
|
||||||
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);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -436,13 +371,3 @@ export async function invalidateUserEmailCache(
|
|||||||
} while (cursor !== '0');
|
} while (cursor !== '0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get cached email credentials from Redis
|
|
||||||
* @deprecated Use getEmailCredentials instead
|
|
||||||
*/
|
|
||||||
export async function getCachedEmailCredentials(
|
|
||||||
userId: string
|
|
||||||
): Promise<EmailCredentials[] | null> {
|
|
||||||
return getEmailCredentials(userId);
|
|
||||||
}
|
|
||||||
@ -67,7 +67,7 @@ export async function getImapConnection(userId: string): Promise<ImapFlow> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cache credentials for future use
|
// Cache credentials for future use
|
||||||
await cacheEmailCredentials(userId, credentials);
|
await cacheEmailCredentials(userId, [credentials]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate credentials
|
// Validate credentials
|
||||||
@ -170,19 +170,24 @@ export async function getUserEmailCredentials(userId: string): Promise<EmailCred
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return only the fields that exist in credentials
|
// Create the credentials object
|
||||||
return {
|
const emailCredentials: EmailCredentials = {
|
||||||
email: credentials.email,
|
email: credentials.email,
|
||||||
password: credentials.password,
|
password: credentials.password,
|
||||||
host: credentials.host,
|
host: credentials.host,
|
||||||
port: credentials.port,
|
port: credentials.port,
|
||||||
...(credentials.secure !== undefined && { secure: credentials.secure }),
|
secure: credentials.secure ?? false,
|
||||||
...(credentials.smtp_host && { smtp_host: credentials.smtp_host }),
|
smtp_host: credentials.smtp_host ?? undefined,
|
||||||
...(credentials.smtp_port && { smtp_port: credentials.smtp_port }),
|
smtp_port: credentials.smtp_port ?? undefined,
|
||||||
...(credentials.smtp_secure !== undefined && { smtp_secure: credentials.smtp_secure }),
|
smtp_secure: credentials.smtp_secure ?? undefined,
|
||||||
...(credentials.display_name && { display_name: credentials.display_name }),
|
display_name: credentials.display_name ?? undefined,
|
||||||
...(credentials.color && { color: credentials.color })
|
color: credentials.color ?? undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Cache the credentials
|
||||||
|
await cacheEmailCredentials(userId, [emailCredentials]);
|
||||||
|
|
||||||
|
return emailCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -213,7 +218,7 @@ export async function saveUserEmailCredentials(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Cache the full credentials object in Redis (with all fields)
|
// Cache the full credentials object in Redis (with all fields)
|
||||||
await cacheEmailCredentials(userId, credentials);
|
await cacheEmailCredentials(userId, [credentials]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper type for IMAP fetch options
|
// Helper type for IMAP fetch options
|
||||||
@ -889,7 +894,7 @@ export async function forceRecacheUserCredentials(userId: string): Promise<boole
|
|||||||
// Try to cache the credentials
|
// Try to cache the credentials
|
||||||
try {
|
try {
|
||||||
const { cacheEmailCredentials } = await import('@/lib/redis');
|
const { cacheEmailCredentials } = await import('@/lib/redis');
|
||||||
await cacheEmailCredentials(userId, credentials);
|
await cacheEmailCredentials(userId, [credentials]);
|
||||||
console.log(`[CREDENTIAL FIX] Successfully cached credentials for user ${userId}`);
|
console.log(`[CREDENTIAL FIX] Successfully cached credentials for user ${userId}`);
|
||||||
|
|
||||||
// Now verify the credentials were cached correctly
|
// Now verify the credentials were cached correctly
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user