import { randomBytes, scrypt as scryptCb, timingSafeEqual, createHash } from "node:crypto"; import { promisify } from "node:util"; const scrypt = promisify(scryptCb); /* * Wachtwoorden: scrypt (in Node ingebouwd, geen extra afhankelijkheid). * Opslagformaat: "scrypt$N$r$p$salt$hash" (salt en hash base64url), zodat de * parameters later verhoogd kunnen worden zonder bestaande hashes te breken. */ const SCRYPT = { N: 16384, r: 8, p: 1, keylen: 32 }; const b64 = (buf) => buf.toString("base64url"); const unb64 = (s) => Buffer.from(s, "base64url"); export async function hashPassword(password, salt = randomBytes(16)) { const { N, r, p, keylen } = SCRYPT; const hash = await scrypt(password.normalize("NFKC"), salt, keylen, { N, r, p }); return `scrypt$${N}$${r}$${p}$${b64(salt)}$${b64(hash)}`; } export async function verifyPassword(password, stored) { const [alg, N, r, p, salt, hash] = (stored ?? "").split("$"); if (alg !== "scrypt") return false; const expected = unb64(hash); const actual = await scrypt(password.normalize("NFKC"), unb64(salt), expected.length, { N: +N, r: +r, p: +p }); return timingSafeEqual(actual, expected); } /* * Sessies: de client krijgt een willekeurig token; in de database staat alleen * de sha256 ervan, zodat een gelekte tabel geen bruikbare tokens oplevert. */ export const newToken = () => b64(randomBytes(32)); export const hashToken = (token) => createHash("sha256").update(token).digest("hex");