import { describe, expect, it } from "vitest"; import { hashArgon2id, verifyArgon2id } from "@/worker/auth/argon2"; const PHC_PATTERN = /^\$argon2id\$v=19\$m=19456,t=2,p=1\$[A-Za-z0-9+/]+\$[A-Za-z0-9+/]+$/; // Argon2id at m=19456,t=2 takes ~1–3s per hash on dev hardware (pure-JS noble // path on workerd). Keep these tests well past Vitest's 5s default. const HASH_TIMEOUT = 30_000; describe("Argon2id hash + verify", () => { it( "emits a PHC string with the expected shape", async () => { const hash = await hashArgon2id("hunter2"); expect(hash).toMatch(PHC_PATTERN); }, HASH_TIMEOUT, ); it( "verifies the correct password", async () => { const password = "correct-horse-battery-staple"; const hash = await hashArgon2id(password); expect(await verifyArgon2id({ hash, password })).toBe(true); }, HASH_TIMEOUT, ); it( "rejects the wrong password", async () => { const hash = await hashArgon2id("right-password"); expect(await verifyArgon2id({ hash, password: "wrong-password" })).toBe(false); }, HASH_TIMEOUT, ); it("rejects malformed PHC strings", async () => { expect(await verifyArgon2id({ hash: "not-a-phc-string", password: "x" })).toBe(false); expect(await verifyArgon2id({ hash: "$argon2id$v=19$m=19456,t=2,p=1$tooshort", password: "x" })).toBe(false); }); // tessera always emits dkLen=32 PHC strings. Anything else is by // definition not produced by hashArgon2id; rejecting non-canonical // lengths removes the trivial truncation collision surface // (1/256 brute-force at dkLen=1) that an attacker with arbitrary D1 // write could otherwise exploit. Non-canonical lengths short-circuit // before invoking argon2id, so this is a fast assertion. it("rejects PHC strings with non-canonical stored-digest lengths", async () => { const encodeBase64Phc = (bytes: Uint8Array): string => { let binary = ""; for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]); return btoa(binary).replace(/=+$/, ""); }; const salt = new Uint8Array(16); const make = (storedLen: number): string => { const stored = new Uint8Array(storedLen); return `$argon2id$v=19$m=19456,t=2,p=1$${encodeBase64Phc(salt)}$${encodeBase64Phc(stored)}`; }; expect(await verifyArgon2id({ hash: make(16), password: "x" })).toBe(false); expect(await verifyArgon2id({ hash: make(40), password: "x" })).toBe(false); expect(await verifyArgon2id({ hash: make(1), password: "x" })).toBe(false); }); it( "produces unique salts so two hashes of the same password differ", async () => { const a = await hashArgon2id("same"); const b = await hashArgon2id("same"); expect(a).not.toBe(b); }, HASH_TIMEOUT, ); });