68 lines
1.6 KiB
TypeScript
68 lines
1.6 KiB
TypeScript
import { createHash, timingSafeEqual } from "node:crypto";
|
|
|
|
import { db } from "~/utils/db.server";
|
|
|
|
function normalizePassword(password: string) {
|
|
return password.trim();
|
|
}
|
|
|
|
export function hashPassword(password: string) {
|
|
const normalizedPassword = normalizePassword(password);
|
|
return createHash("sha256").update(normalizedPassword).digest("hex");
|
|
}
|
|
|
|
function isHashedPassword(value: string) {
|
|
return /^[a-f0-9]{64}$/i.test(value);
|
|
}
|
|
|
|
export function verifyPassword(password: string, storedPassword: string | null | undefined) {
|
|
if (!storedPassword) {
|
|
return false;
|
|
}
|
|
|
|
const normalizedPassword = normalizePassword(password);
|
|
const hashedPassword = hashPassword(normalizedPassword);
|
|
|
|
if (isHashedPassword(storedPassword)) {
|
|
const storedBuffer = Buffer.from(storedPassword);
|
|
const hashedBuffer = Buffer.from(hashedPassword);
|
|
|
|
if (hashedBuffer.length !== storedBuffer.length) {
|
|
return false;
|
|
}
|
|
|
|
return timingSafeEqual(hashedBuffer, storedBuffer);
|
|
}
|
|
|
|
return normalizePassword(storedPassword) === normalizedPassword;
|
|
}
|
|
|
|
export async function validateCredentials(
|
|
username: string,
|
|
password: string
|
|
) {
|
|
const user = await db.user.findUnique({
|
|
where: { username },
|
|
select: { id: true, password: true },
|
|
});
|
|
|
|
if (!user) {
|
|
return null;
|
|
}
|
|
|
|
const passwordMatches = verifyPassword(password, user.password);
|
|
|
|
if (!passwordMatches) {
|
|
return null;
|
|
}
|
|
|
|
if (!isHashedPassword(user.password ?? "")) {
|
|
await db.user.update({
|
|
where: { id: user.id },
|
|
data: { password: hashPassword(password) },
|
|
});
|
|
}
|
|
|
|
return user.id;
|
|
}
|