UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

246 lines (189 loc) 8.23 kB
import { Alepha } from "alepha"; import { describe, expect, it } from "vitest"; import { CryptoProvider } from "../index.ts"; /** * Bug #2: Password Verification Missing Input Validation * * Issue: The verifyPassword method didn't validate input format before processing, * leading to crashes when: * - stored hash is malformed (missing colon, wrong format) * - stored hash has incorrect length * - timingSafeEqual throws when buffer lengths differ * * Expected: All invalid inputs should return false gracefully instead of throwing. */ describe("Bug #2: Password Verification Missing Input Validation", () => { it("should verify valid password correctly", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); const password = "mySecurePassword123!"; const hashed = await crypto.hashPassword(password); const isValid = await crypto.verifyPassword(password, hashed); expect(isValid).toBe(true); const isInvalid = await crypto.verifyPassword("wrongPassword", hashed); expect(isInvalid).toBe(false); }); it("should return false for malformed hash with no colon", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); const malformed = "notavalidhashwithoutcolon"; // Should not throw, should return false const result = await crypto.verifyPassword("password", malformed); expect(result).toBe(false); }); it("should return false for hash with multiple colons", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); const malformed = "salt:hash:extra"; const result = await crypto.verifyPassword("password", malformed); expect(result).toBe(false); }); it("should return false for empty string", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); const result = await crypto.verifyPassword("password", ""); expect(result).toBe(false); }); it("should return false for hash with empty salt", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); const malformed = ":somehashvalue"; const result = await crypto.verifyPassword("password", malformed); expect(result).toBe(false); }); it("should return false for hash with empty hash value", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); const malformed = "somesalt:"; const result = await crypto.verifyPassword("password", malformed); expect(result).toBe(false); }); it("should return false for just a colon", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); const malformed = ":"; const result = await crypto.verifyPassword("password", malformed); expect(result).toBe(false); }); it("should return false for invalid hex in hash portion", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); // Valid salt, but hash contains non-hex characters const malformed = "validSalt123:notHexChars!@#"; const result = await crypto.verifyPassword("password", malformed); expect(result).toBe(false); }); it("should return false for odd-length hex string", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); // Hex strings must have even length (2 chars per byte) const malformed = "salt:abc"; // 3 chars, odd const result = await crypto.verifyPassword("password", malformed); expect(result).toBe(false); }); it("should return false for hash with wrong length", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); // Valid hex, but wrong length (scrypt produces 64 bytes = 128 hex chars) const malformed = "salt:abcd"; // Only 4 hex chars = 2 bytes const result = await crypto.verifyPassword("password", malformed); expect(result).toBe(false); }); it("should return false for corrupted but well-formed hash", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); const password = "testPassword"; const hashed = await crypto.hashPassword(password); // Corrupt the hash by changing one character const removedChar = hashed.slice(-1); const corrupted = hashed.slice(0, -1) + (removedChar === "0" ? "1" : "0"); const result = await crypto.verifyPassword(password, corrupted); expect(result).toBe(false); }); it("should return false for null or undefined input", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); // @ts-expect-error - testing runtime behavior const resultNull = await crypto.verifyPassword("password", null); expect(resultNull).toBe(false); // @ts-expect-error - testing runtime behavior const resultUndefined = await crypto.verifyPassword("password", undefined); expect(resultUndefined).toBe(false); }); it("should return false for non-string input", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); // @ts-expect-error - testing runtime behavior const resultNumber = await crypto.verifyPassword("password", 12345); expect(resultNumber).toBe(false); // @ts-expect-error - testing runtime behavior const resultObject = await crypto.verifyPassword("password", { hash: "test", }); expect(resultObject).toBe(false); }); it("should handle special characters in salt gracefully", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); // If somehow a salt with special chars got stored const malformed = "salt\nwith\nnewlines:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; // Should not crash, should return false (likely during scrypt or comparison) const result = await crypto.verifyPassword("password", malformed); expect(result).toBe(false); }); it("should validate format before attempting expensive scrypt operation", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); // These should fail fast without calling scrypt const invalidFormats = [ "no-colon-at-all", "too:many:colons:here", ":no-salt", "no-hash:", ":", "", "salt:invalidhex!", "salt:abc", // odd length ]; for (const invalid of invalidFormats) { const result = await crypto.verifyPassword("password", invalid); expect(result).toBe(false); } }); it("should properly handle valid hash format with correct verification", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); // Test multiple passwords const passwords = [ "simple", "with spaces", "special!@#$%^&*()", "emoji🔐🔑", `${"very".repeat(100)}long`, ]; for (const password of passwords) { const hashed = await crypto.hashPassword(password); // Verify format is correct (salt:hash with 128 hex chars for hash) expect(hashed).toMatch(/^[0-9a-f]+:[0-9a-f]{128}$/i); // Correct password should verify expect(await crypto.verifyPassword(password, hashed)).toBe(true); // Wrong password should not verify expect(await crypto.verifyPassword(`${password}wrong`, hashed)).toBe( false, ); } }); it("should prevent timing attacks with constant-time comparison", async () => { const app = Alepha.create(); const crypto = app.inject(CryptoProvider); const password = "testPassword"; const hashed = await crypto.hashPassword(password); // Both wrong passwords should take similar time (constant-time comparison) // This is a behavioral test - we're just checking they both return false const wrongPassword1 = "x"; const wrongPassword2 = "x".repeat(100); expect(await crypto.verifyPassword(wrongPassword1, hashed)).toBe(false); expect(await crypto.verifyPassword(wrongPassword2, hashed)).toBe(false); }); });