import crypto from 'crypto'; const algorithm = 'aes-256-gcm'; const saltLength = 64; const tagLength = 16; const ivLength = 16; const iterations = 100000; const keyLength = 32; function getKey(): Buffer { const key = process.env.NUXT_EMAIL_ENCRYPTION_KEY; if (!key || key.length < 32) { throw new Error('NUXT_EMAIL_ENCRYPTION_KEY must be at least 32 characters long'); } // Ensure key is exactly 32 bytes return Buffer.from(key.substring(0, 32).padEnd(32, '0')); } export function encryptCredentials(email: string, password: string): string { try { const key = getKey(); const iv = crypto.randomBytes(ivLength); const salt = crypto.randomBytes(saltLength); const derivedKey = crypto.pbkdf2Sync(key, salt, iterations, keyLength, 'sha256'); const cipher = crypto.createCipheriv(algorithm, derivedKey, iv); const data = JSON.stringify({ email, password }); const encrypted = Buffer.concat([ cipher.update(data, 'utf8'), cipher.final() ]); const tag = cipher.getAuthTag(); // Combine salt, iv, tag, and encrypted data const combined = Buffer.concat([salt, iv, tag, encrypted]); return combined.toString('base64'); } catch (error) { throw new Error('Failed to encrypt credentials'); } } export function decryptCredentials(encryptedData: string): { email: string; password: string } { try { const key = getKey(); const combined = Buffer.from(encryptedData, 'base64'); // Extract components const salt = combined.slice(0, saltLength); const iv = combined.slice(saltLength, saltLength + ivLength); const tag = combined.slice(saltLength + ivLength, saltLength + ivLength + tagLength); const encrypted = combined.slice(saltLength + ivLength + tagLength); const derivedKey = crypto.pbkdf2Sync(key, salt, iterations, keyLength, 'sha256'); const decipher = crypto.createDecipheriv(algorithm, derivedKey, iv); decipher.setAuthTag(tag); const decrypted = Buffer.concat([ decipher.update(encrypted), decipher.final() ]); return JSON.parse(decrypted.toString('utf8')); } catch (error) { throw new Error('Failed to decrypt credentials'); } } // In-memory session storage for credentials (cleared on server restart) const credentialCache = new Map(); const CACHE_TTL = 30 * 60 * 1000; // 30 minutes export function storeCredentialsInSession(sessionId: string, encryptedCredentials: string): void { credentialCache.set(sessionId, { credentials: encryptedCredentials, timestamp: Date.now() }); // Clean up expired sessions cleanupExpiredSessions(); } export function getCredentialsFromSession(sessionId: string): string | null { const session = credentialCache.get(sessionId); if (!session) { return null; } // Check if session is expired if (Date.now() - session.timestamp > CACHE_TTL) { credentialCache.delete(sessionId); return null; } // Update timestamp on access session.timestamp = Date.now(); return session.credentials; } export function clearCredentialsFromSession(sessionId: string): void { credentialCache.delete(sessionId); } function cleanupExpiredSessions(): void { const now = Date.now(); for (const [sessionId, session] of credentialCache.entries()) { if (now - session.timestamp > CACHE_TTL) { credentialCache.delete(sessionId); } } } // Cleanup expired sessions every 5 minutes setInterval(cleanupExpiredSessions, 5 * 60 * 1000);