42 lines
1.1 KiB
TypeScript
42 lines
1.1 KiB
TypeScript
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
export const recoveryCookie = "mg_password_recovery";
|
|
function signature(value: string, secret: string) {
|
|
return createHmac("sha256", secret)
|
|
.update("minegeo-password-recovery:" + value)
|
|
.digest("hex");
|
|
}
|
|
export function createRecoveryGrant(
|
|
userId: string,
|
|
sessionId: string,
|
|
secret: string,
|
|
now = Date.now(),
|
|
) {
|
|
const value = `${userId}:${sessionId}:${Math.floor(now / 1000) + 600}`;
|
|
return `${value}:${signature(value, secret)}`;
|
|
}
|
|
export function verifyRecoveryGrant(
|
|
token: string | undefined,
|
|
userId: string,
|
|
sessionId: string,
|
|
secret: string,
|
|
now = Date.now(),
|
|
) {
|
|
const parts = token?.split(":");
|
|
if (
|
|
!parts ||
|
|
parts.length !== 4 ||
|
|
parts[0] !== userId ||
|
|
parts[1] !== sessionId ||
|
|
!/^\d+$/.test(parts[2]) ||
|
|
!/^[0-9a-f]{64}$/.test(parts[3])
|
|
)
|
|
return false;
|
|
const expires = Number(parts[2]),
|
|
time = Math.floor(now / 1000);
|
|
if (expires <= time || expires > time + 600) return false;
|
|
return timingSafeEqual(
|
|
Buffer.from(parts[3], "hex"),
|
|
Buffer.from(signature(parts.slice(0, 3).join(":"), secret), "hex"),
|
|
);
|
|
}
|