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 key = KEYS.CREDENTIALS(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 separately if it exists
if (credentials.password) {
secureCredentials.encryptedPassword = encryptData(credentials.password);
// Validate credentials before caching
if (!credentials.email || !credentials.host || !credentials.password) {
console.error(`Cannot cache incomplete credentials for user ${userId}`);
return;
}
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 key = KEYS.CREDENTIALS(userId);
const cachedData = await redis.get(key);
if (!cachedData) return null;
const credentials = JSON.parse(cachedData) as EmailCredentials;
// Decrypt password if it was encrypted
if (credentials.encryptedPassword) {
credentials.password = decryptData(credentials.encryptedPassword);
delete credentials.encryptedPassword;
try {
const cachedData = await redis.get(key);
if (!cachedData) {
console.log(`No cached credentials found for user ${userId}`);
return null;
}
console.log(`Found cached credentials for user ${userId}, attempting to decrypt`);
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
*/
export async function getImapConnection(userId: string): Promise<ImapFlow> {
console.log(`Getting IMAP connection for user ${userId}`);
// First try to get credentials from Redis cache
let credentials = await getCachedEmailCredentials(userId);
// If not in cache, get from database and cache them
if (!credentials) {
console.log(`Credentials not found in cache for ${userId}, attempting database lookup`);
credentials = await getUserEmailCredentials(userId);
if (!credentials) {
throw new Error('No email credentials found');
@ -114,6 +117,17 @@ export async function getImapConnection(userId: string): Promise<ImapFlow> {
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 existingConnection = connectionPool[connectionKey];
@ -125,6 +139,7 @@ export async function getImapConnection(userId: string): Promise<ImapFlow> {
try {
if (existingConnection.client.usable) {
existingConnection.lastUsed = Date.now();
console.log(`Reusing existing IMAP connection for ${connectionKey}`);
// Update session data in Redis
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
const client = new ImapFlow({
host: credentials.host,
@ -160,6 +177,7 @@ export async function getImapConnection(userId: string): Promise<ImapFlow> {
try {
await client.connect();
console.log(`Successfully connected to IMAP server for ${connectionKey}`);
// Store in connection pool
connectionPool[connectionKey] = {
@ -170,6 +188,7 @@ export async function getImapConnection(userId: string): Promise<ImapFlow> {
return client;
} catch (error: unknown) {
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}`);
}
}