import { randomBytes } from "node:crypto"; import { bcrypt, bcryptVerify, md5, } from "hash-wasm"; import { env } from "@/env"; export async function hashPassword(password: string): Promise { const h = await bcrypt({ password, salt: randomBytes(16), costFactor: env.BCRYPT_ROUNDS, outputType: "encoded", }); return h.replace(/^\$2[ab]\$/, "$2y$"); } export async function md5Hex(input: string): Promise { return await md5(input); } export async function isMd5Of( password: string, stored: string, ): Promise { return ( /^[a-f0-9]{32}$/i.test(stored) && (await md5Hex(password)) === stored.toLowerCase() ); } export async function verifyPassword( password: string, stored: string, ): Promise { if (/^\$2[aby]\$/.test(stored)) { try { return await bcryptVerify({ password, hash: stored }); } catch { return false; } } return false; } export interface LoginCheck { valid: boolean; upgradedHash?: string; } export async function checkLogin( password: string, stored: string, opts: { convertPasswords: boolean }, ): Promise { if (opts.convertPasswords && (await isMd5Of(password, stored))) { return { valid: true, upgradedHash: await hashPassword(password) }; } return { valid: await verifyPassword(password, stored) }; }