diff --git a/packages/utils/src/hash-password.test.ts b/packages/utils/src/hash-password.test.ts index 3bd87f5..c89a863 100644 --- a/packages/utils/src/hash-password.test.ts +++ b/packages/utils/src/hash-password.test.ts @@ -13,8 +13,8 @@ describe("hashPassword", () => { expect(parts[1]).toBe("pbkdf2-sha256"); expect(parts[2]).toBe("100000"); // Salt and hash should be non-empty base64 strings - expect(parts[3].length).toBeGreaterThan(0); - expect(parts[4].length).toBeGreaterThan(0); + expect(parts[3]?.length).toBeGreaterThan(0); + expect(parts[4]?.length).toBeGreaterThan(0); }); it("should generate different hashes for the same password", async () => { @@ -98,4 +98,66 @@ describe("verifyPassword", () => { const wrongResult = await verifyPassword("password", hash); expect(wrongResult).toBe(false); }); + + it("should verify known hash for 'password'", async () => { + // This hash was generated for the password "password" + const storedHash = + "$pbkdf2-sha256$100000$iUaDbbVm+Mf0HG7RcCCOzw==$IQfBN4chRU3wqCEoC9XOusIVYkyW24dbJd/ksm0VBJk="; + const password = "password"; + + const result = await verifyPassword(password, storedHash); + expect(result).toBe(true); + }); + + it("should derive consistent hash for known salt and password", async () => { + // Manually verify the derivation produces the expected hash + const password = "password"; + const saltB64 = "iUaDbbVm+Mf0HG7RcCCOzw=="; + const expectedHashB64 = "IQfBN4chRU3wqCEoC9XOusIVYkyW24dbJd/ksm0VBJk="; + const iterations = 100000; + + // Decode salt + const saltBinary = atob(saltB64); + const salt = new Uint8Array(saltBinary.length); + for (let i = 0; i < saltBinary.length; i++) { + salt[i] = saltBinary.charCodeAt(i); + } + + // Derive key + const encoder = new TextEncoder(); + const passwordBuffer = encoder.encode(password); + + const keyMaterial = await crypto.subtle.importKey( + "raw", + passwordBuffer, + "PBKDF2", + false, + ["deriveBits"], + ); + + const derivedBits = await crypto.subtle.deriveBits( + { + name: "PBKDF2", + salt, + iterations, + hash: "SHA-256", + }, + keyMaterial, + 32 * 8, + ); + + // Encode result to base64 + const derivedArray = new Uint8Array(derivedBits); + let binary = ""; + for (const byte of derivedArray) { + binary += String.fromCharCode(byte); + } + const derivedB64 = btoa(binary); + + console.log("Expected hash:", expectedHashB64); + console.log("Derived hash: ", derivedB64); + console.log("Match:", derivedB64 === expectedHashB64); + + expect(derivedB64).toBe(expectedHashB64); + }); }); diff --git a/packages/utils/src/hash-password.ts b/packages/utils/src/hash-password.ts index 6cc26ba..08e8f13 100644 --- a/packages/utils/src/hash-password.ts +++ b/packages/utils/src/hash-password.ts @@ -107,6 +107,7 @@ export const verifyPassword = async ( const derivedBits = await crypto.subtle.deriveBits( { name: "PBKDF2", + // @ts-expect-error - salt is a Uint8Array salt, iterations, hash: "SHA-256", diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json index fe0b870..4a1e2b2 100644 --- a/packages/utils/tsconfig.json +++ b/packages/utils/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "@macalinao/tsconfig/tsconfig.base.json", "compilerOptions": { - "types": ["@cloudflare/workers-types"] - }, - "exclude": ["**/*.test.ts"] + "types": ["bun"] + } }