From 7ffee910e22611d3d8e32b4f89f1383c518cb394 Mon Sep 17 00:00:00 2001 From: prayansh_chhablani Date: Tue, 29 Oct 2024 12:44:30 +0530 Subject: [PATCH] suggestions --- setup.ts | 3 +++ src/models/User.ts | 13 ++++++++++--- src/utilities/encryption.ts | 14 +++++++------- src/utilities/hashEmail.ts | 22 +++++++++++++++++----- tests/utilities/hashingModule.spec.ts | 16 +++++++++------- 5 files changed, 46 insertions(+), 22 deletions(-) diff --git a/setup.ts b/setup.ts index 6e047da582..32df8cac24 100644 --- a/setup.ts +++ b/setup.ts @@ -388,6 +388,9 @@ export async function redisConfiguration(): Promise { export async function setEncryptionKey(): Promise { try { if (process.env.ENCRYPTION_KEY) { + if (!/^[a-f0-9]{64}$/.test(process.env.ENCRYPTION_KEY)) { + throw new Error("Existing encryption key has invalid format"); + } console.log("\n Encryption Key already present."); } else { const encryptionKey = crypto.randomBytes(32).toString("hex"); diff --git a/src/models/User.ts b/src/models/User.ts index 8fc510ff71..04aa029737 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -248,12 +248,19 @@ userSchema.pre('save', async function (next) { const decrypted = decryptEmail(this.email).decrypted; if(!validator.isEmail(decrypted)) { - throw new Error('Invalid email format'); + throw new Error(`Invalid email format: ${decrypted}`); } + // Generate hashedEmail when email changes + const crypto = require('crypto'); + this.hashedEmail = crypto + .createHmac("sha256", process.env.HASH_PEPPER) + .update(decrypted.toLowerCase()) + .digest("hex"); } - catch(error) + catch(error: unknown) { - throw new Error('Email validation failed'); + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + throw new Error(`Email validation failed: ${errorMessage}`); } } next(); diff --git a/src/utilities/encryption.ts b/src/utilities/encryption.ts index cf07d3ab20..bd97ef976f 100644 --- a/src/utilities/encryption.ts +++ b/src/utilities/encryption.ts @@ -5,10 +5,10 @@ const algorithm = "aes-256-gcm"; const authTagLength = 16; const authTagHexLength = authTagLength * 2; -const saltLength = 16; +const ivLength = 16; export function generateRandomIV(): string { - return crypto.randomBytes(saltLength).toString("hex"); + return crypto.randomBytes(ivLength).toString("hex"); } export function encryptEmail(email: string): string { @@ -52,8 +52,8 @@ export function decryptEmail(encryptedData: string): { if (!isValidHex(encryptedHex)) { throw new Error("Invalid encrypted data: not a hex string"); } -const minLength = saltLength * 2 + authTagHexLength + 2; -const maxLength = saltLength * 2 + authTagHexLength + 1000; +const minLength = ivLength * 2 + authTagHexLength + 2; +const maxLength = ivLength * 2 + authTagHexLength + 1000; if (encryptedData.length < minLength) { throw new Error("Invalid encrypted data: input is too short."); } else if (encryptedData.length > maxLength) { @@ -71,10 +71,10 @@ const maxLength = saltLength * 2 + authTagHexLength + 1000; } const authTag = Buffer.from( - encryptedData.slice(saltLength * 2, saltLength * 2 + authTagHexLength), + encryptedData.slice(ivLength * 2, ivLength * 2 + authTagHexLength), "hex", ); - const encrypted = encryptedData.slice(saltLength * 2 + authTagHexLength); + const encrypted = encryptedData.slice(ivLength * 2 + authTagHexLength); const decipher = crypto.createDecipheriv( algorithm, @@ -90,7 +90,7 @@ const maxLength = saltLength * 2 + authTagHexLength + 1000; decipher.update(Buffer.from(encrypted, "hex")), decipher.final(), ]).toString("utf8"); - } catch (error) { + } catch { throw new Error("Decryption failed: invalid data or authentication tag."); } return {decrypted}; diff --git a/src/utilities/hashEmail.ts b/src/utilities/hashEmail.ts index 8b2a7f2bbb..f79c004278 100644 --- a/src/utilities/hashEmail.ts +++ b/src/utilities/hashEmail.ts @@ -31,8 +31,20 @@ export function hashEmail(email: string) : string { } export function compareHashedEmails(a: string, b: string): boolean { - return crypto.timingSafeEqual( - Buffer.from(a, 'hex'), - Buffer.from(b, 'hex') - ); -} \ No newline at end of file + if (!a || !b || typeof a !== 'string' || typeof b !== 'string') { + return false; + } + + if (!/^[0-9a-f]+$/i.test(a) || !/^[0-9a-f]+$/i.test(b)) { + return false; + } + + try { + return crypto.timingSafeEqual( + Buffer.from(a, 'hex'), + Buffer.from(b, 'hex') + ); + } catch (error) { + return false; + } + } \ No newline at end of file diff --git a/tests/utilities/hashingModule.spec.ts b/tests/utilities/hashingModule.spec.ts index 2719bd8b1d..3fd728b3a9 100644 --- a/tests/utilities/hashingModule.spec.ts +++ b/tests/utilities/hashingModule.spec.ts @@ -22,13 +22,15 @@ describe("hashingModule", () => { }); }); - it("should produce diffrent hashes with diffrent HASH_PEPPER values", () => { - const email = "test@example.com" - process.env.HASH_PEPPER = "pepper1"; - const hash1 = hashEmail(email); - process.env.HASH_PEPPER = "pepper2"; - const hash2 = hashEmail(email); - expect(hash1).not.toEqual(hash2); + it("should produce different hashes with different HASH_PEPPER values", () => { + const email = "test@example.com" + const originalPepper = process.env.HASH_PEPPER; + process.env.HASH_PEPPER = "pepper1"; + const hash1 = hashEmail(email); + process.env.HASH_PEPPER = "pepper2"; + const hash2 = hashEmail(email); + expect(hash1).not.toEqual(hash2); + process.env.HASH_PEPPER = originalPepper; }) }) })