phosphat-report-app/app/utils/encryption.server.ts
2025-08-01 05:00:14 +03:00

152 lines
3.9 KiB
TypeScript

import crypto from 'crypto';
// Get encryption key from environment or generate a default one
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY || 'phosphat-report-default-key-32b'; // Must be 32 characters
const ALGORITHM = 'aes-256-cbc';
const IV_LENGTH = 16; // For AES, this is always 16
// Ensure the key is exactly 32 bytes
function getEncryptionKey(): Buffer {
const key = ENCRYPTION_KEY.padEnd(32, '0').substring(0, 32);
return Buffer.from(key, 'utf8');
}
/**
* Encrypts a plain text string
* @param text - The plain text to encrypt
* @returns Encrypted string in format: iv:encryptedData
*/
export function encrypt(text: string): string {
if (!text) return '';
try {
const key = getEncryptionKey();
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipher(ALGORITHM, key);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
// Return iv and encrypted data separated by ':'
return iv.toString('hex') + ':' + encrypted;
} catch (error) {
console.error('Encryption error:', error);
throw new Error('Failed to encrypt data');
}
}
/**
* Decrypts an encrypted string
* @param encryptedText - The encrypted text in format: iv:encryptedData
* @returns Decrypted plain text string
*/
export function decrypt(encryptedText: string): string {
if (!encryptedText) return '';
try {
const key = getEncryptionKey();
const textParts = encryptedText.split(':');
if (textParts.length !== 2) {
throw new Error('Invalid encrypted text format');
}
const iv = Buffer.from(textParts[0], 'hex');
const encryptedData = textParts[1];
const decipher = crypto.createDecipher(ALGORITHM, key);
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch (error) {
console.error('Decryption error:', error);
throw new Error('Failed to decrypt data');
}
}
/**
* Encrypts sensitive mail settings
* @param settings - Mail settings object
* @returns Mail settings with encrypted password
*/
export function encryptMailSettings(settings: {
host: string;
port: number;
secure: boolean;
username: string;
password: string;
fromName: string;
fromEmail: string;
}) {
return {
...settings,
password: encrypt(settings.password)
};
}
/**
* Decrypts sensitive mail settings
* @param settings - Mail settings object with encrypted password
* @returns Mail settings with decrypted password
*/
export function decryptMailSettings(settings: {
host: string;
port: number;
secure: boolean;
username: string;
password: string;
fromName: string;
fromEmail: string;
}) {
return {
...settings,
password: decrypt(settings.password)
};
}
/**
* Validates if a string is encrypted (contains ':' separator)
* @param text - Text to validate
* @returns True if text appears to be encrypted
*/
export function isEncrypted(text: string): boolean {
return text.includes(':') && text.split(':').length === 2;
}
/**
* Safely encrypts a password only if it's not already encrypted
* @param password - Password to encrypt
* @returns Encrypted password
*/
export function safeEncryptPassword(password: string): string {
if (isEncrypted(password)) {
return password; // Already encrypted
}
return encrypt(password);
}
/**
* Test encryption/decryption functionality
* @param testText - Text to test with
* @returns Test results
*/
export function testEncryption(testText: string = 'test-password-123') {
try {
const encrypted = encrypt(testText);
const decrypted = decrypt(encrypted);
return {
success: decrypted === testText,
original: testText,
encrypted: encrypted,
decrypted: decrypted,
isValid: decrypted === testText
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}