Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/services/auth/password-reset-token.server.ts
T

55 lines
1.6 KiB
TypeScript

import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
export const PASSWORD_RESET_TOKEN_TTL_MS = 1000 * 60 * 60;
const PASSWORD_RESET_TOKEN_HASH_PREFIX = "sha256:";
export function hashPasswordResetToken(verifyToken: string) {
return `${PASSWORD_RESET_TOKEN_HASH_PREFIX}${createHash("sha256").update(verifyToken).digest("hex")}`;
}
export function createPasswordResetTokenUpdate(verifyToken: string, now = new Date()) {
return {
verifyToken: hashPasswordResetToken(verifyToken),
verifyTokenCreatedAt: now,
};
}
export function createPasswordResetVerifyToken() {
return randomBytes(20).toString("hex");
}
export function isPasswordResetTokenValid(
user: { verifyToken?: string | null; verifyTokenCreatedAt?: Date | string | null },
verifyToken: string,
now = new Date(),
) {
if (!user.verifyToken || !verifyToken || !user.verifyTokenCreatedAt) {
return false;
}
const createdAt = new Date(user.verifyTokenCreatedAt).getTime();
if (!Number.isFinite(createdAt) || now.getTime() - createdAt > PASSWORD_RESET_TOKEN_TTL_MS) {
return false;
}
const storedToken = user.verifyToken.startsWith(PASSWORD_RESET_TOKEN_HASH_PREFIX)
? user.verifyToken
: user.verifyToken;
const candidate = user.verifyToken.startsWith(PASSWORD_RESET_TOKEN_HASH_PREFIX)
? hashPasswordResetToken(verifyToken)
: verifyToken;
return timingSafeStringEqual(storedToken, candidate);
}
function timingSafeStringEqual(a: string, b: string) {
const aBuffer = Buffer.from(a);
const bBuffer = Buffer.from(b);
if (aBuffer.byteLength !== bBuffer.byteLength) {
return false;
}
return timingSafeEqual(aBuffer, bBuffer);
}