courrier redis login

This commit is contained in:
alma 2025-04-27 13:55:33 +02:00
parent 6c94b7f8f8
commit 4899bd093b
2 changed files with 91 additions and 23 deletions

View File

@ -114,20 +114,44 @@ export async function cacheEmailCredentials(
const redis = getRedisClient(); const redis = getRedisClient();
const key = KEYS.CREDENTIALS(userId); const key = KEYS.CREDENTIALS(userId);
// Create a copy without the password to store // Validate credentials before caching
const secureCredentials: EmailCredentials = { if (!credentials.email || !credentials.host || !credentials.password) {
email: credentials.email, console.error(`Cannot cache incomplete credentials for user ${userId}`);
host: credentials.host, return;
port: credentials.port,
secure: credentials.secure ?? true
};
// Encrypt password separately if it exists
if (credentials.password) {
secureCredentials.encryptedPassword = encryptData(credentials.password);
} }
await redis.set(key, JSON.stringify(secureCredentials), 'EX', TTL.CREDENTIALS); 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
};
// 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;
}
} else {
console.warn(`No password provided for user ${userId}, skipping credential caching`);
return;
}
await redis.set(key, JSON.stringify(secureCredentials), 'EX', TTL.CREDENTIALS);
console.log(`Credentials cached for user ${userId}`);
} catch (error) {
console.error(`Error caching credentials for user ${userId}:`, error);
}
} }
/** /**
@ -139,18 +163,43 @@ export async function getCachedEmailCredentials(
const redis = getRedisClient(); const redis = getRedisClient();
const key = KEYS.CREDENTIALS(userId); const key = KEYS.CREDENTIALS(userId);
const cachedData = await redis.get(key); try {
if (!cachedData) return null; const cachedData = await redis.get(key);
if (!cachedData) {
const credentials = JSON.parse(cachedData) as EmailCredentials; console.log(`No cached credentials found for user ${userId}`);
return null;
// Decrypt password if it was encrypted }
if (credentials.encryptedPassword) {
credentials.password = decryptData(credentials.encryptedPassword); console.log(`Found cached credentials for user ${userId}, attempting to decrypt`);
delete credentials.encryptedPassword; const credentials = JSON.parse(cachedData) as EmailCredentials;
// Check if we have encrypted password
if (!credentials.encryptedPassword) {
console.warn(`Cached credentials for user ${userId} missing encrypted password`);
return null;
}
try {
// Decrypt password with error handling
credentials.password = decryptData(credentials.encryptedPassword);
delete credentials.encryptedPassword;
// Validate the credentials to ensure they're complete
if (!credentials.password || !credentials.email || !credentials.host) {
console.warn(`Incomplete credentials in cache for user ${userId}, missing required fields`);
return null;
}
console.log(`Successfully retrieved and decrypted credentials for ${userId}`);
return credentials;
} catch (decryptError) {
console.error(`Failed to decrypt password for user ${userId}:`, decryptError);
return null;
}
} catch (error) {
console.error(`Error retrieving credentials from Redis for user ${userId}:`, error);
return null;
} }
return credentials;
} }
/** /**

View File

@ -100,11 +100,14 @@ setInterval(() => {
* Get IMAP connection for a user, reusing existing connections when possible * Get IMAP connection for a user, reusing existing connections when possible
*/ */
export async function getImapConnection(userId: string): Promise<ImapFlow> { export async function getImapConnection(userId: string): Promise<ImapFlow> {
console.log(`Getting IMAP connection for user ${userId}`);
// First try to get credentials from Redis cache // First try to get credentials from Redis cache
let credentials = await getCachedEmailCredentials(userId); let credentials = await getCachedEmailCredentials(userId);
// If not in cache, get from database and cache them // If not in cache, get from database and cache them
if (!credentials) { if (!credentials) {
console.log(`Credentials not found in cache for ${userId}, attempting database lookup`);
credentials = await getUserEmailCredentials(userId); credentials = await getUserEmailCredentials(userId);
if (!credentials) { if (!credentials) {
throw new Error('No email credentials found'); throw new Error('No email credentials found');
@ -114,6 +117,17 @@ export async function getImapConnection(userId: string): Promise<ImapFlow> {
await cacheEmailCredentials(userId, credentials); await cacheEmailCredentials(userId, credentials);
} }
// Validate credentials
if (!credentials.password) {
console.error(`Missing password in credentials for user ${userId}`);
throw new Error('No password configured');
}
if (!credentials.email || !credentials.host) {
console.error(`Incomplete credentials for user ${userId}`);
throw new Error('Invalid email credentials configuration');
}
const connectionKey = `${userId}:${credentials.email}`; const connectionKey = `${userId}:${credentials.email}`;
const existingConnection = connectionPool[connectionKey]; const existingConnection = connectionPool[connectionKey];
@ -125,6 +139,7 @@ export async function getImapConnection(userId: string): Promise<ImapFlow> {
try { try {
if (existingConnection.client.usable) { if (existingConnection.client.usable) {
existingConnection.lastUsed = Date.now(); existingConnection.lastUsed = Date.now();
console.log(`Reusing existing IMAP connection for ${connectionKey}`);
// Update session data in Redis // Update session data in Redis
if (sessionData) { if (sessionData) {
@ -142,6 +157,8 @@ export async function getImapConnection(userId: string): Promise<ImapFlow> {
} }
} }
console.log(`Creating new IMAP connection for ${connectionKey}`);
// Create new connection // Create new connection
const client = new ImapFlow({ const client = new ImapFlow({
host: credentials.host, host: credentials.host,
@ -160,6 +177,7 @@ export async function getImapConnection(userId: string): Promise<ImapFlow> {
try { try {
await client.connect(); await client.connect();
console.log(`Successfully connected to IMAP server for ${connectionKey}`);
// Store in connection pool // Store in connection pool
connectionPool[connectionKey] = { connectionPool[connectionKey] = {
@ -170,6 +188,7 @@ export async function getImapConnection(userId: string): Promise<ImapFlow> {
return client; return client;
} catch (error: unknown) { } catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error'; const errorMessage = error instanceof Error ? error.message : 'Unknown error';
console.error(`IMAP connection error for ${connectionKey}:`, errorMessage);
throw new Error(`Failed to connect to IMAP server: ${errorMessage}`); throw new Error(`Failed to connect to IMAP server: ${errorMessage}`);
} }
} }