62 lines
1.3 KiB
TypeScript
62 lines
1.3 KiB
TypeScript
import { randomBytes } from "node:crypto";
|
|
import {
|
|
bcrypt,
|
|
bcryptVerify,
|
|
md5,
|
|
} from "hash-wasm";
|
|
|
|
import { env } from "@/env";
|
|
|
|
export async function hashPassword(password: string): Promise<string> {
|
|
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<string> {
|
|
return await md5(input);
|
|
}
|
|
|
|
export async function isMd5Of(
|
|
password: string,
|
|
stored: string,
|
|
): Promise<boolean> {
|
|
return (
|
|
/^[a-f0-9]{32}$/i.test(stored) &&
|
|
(await md5Hex(password)) === stored.toLowerCase()
|
|
);
|
|
}
|
|
|
|
export async function verifyPassword(
|
|
password: string,
|
|
stored: string,
|
|
): Promise<boolean> {
|
|
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<LoginCheck> {
|
|
if (opts.convertPasswords && (await isMd5Of(password, stored))) {
|
|
return { valid: true, upgradedHash: await hashPassword(password) };
|
|
}
|
|
return { valid: await verifyPassword(password, stored) };
|
|
} |