64 lines
1.7 KiB
JavaScript
64 lines
1.7 KiB
JavaScript
const crypto = require("node:crypto");
|
|
const { tokenSecret } = require("./config.cjs");
|
|
|
|
function nowIso() {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
function addYears(date, years) {
|
|
const next = new Date(date);
|
|
next.setFullYear(next.getFullYear() + years);
|
|
return next;
|
|
}
|
|
|
|
function addHours(date, hours) {
|
|
return new Date(date.getTime() + Number(hours || 0) * 3600000);
|
|
}
|
|
|
|
function sign(value) {
|
|
return crypto.createHmac("sha256", tokenSecret).update(value).digest("base64url");
|
|
}
|
|
|
|
function createToken(userId) {
|
|
const payload = Buffer.from(JSON.stringify({ userId, issuedAt: Date.now() })).toString("base64url");
|
|
return `${payload}.${sign(payload)}`;
|
|
}
|
|
|
|
function verifyToken(token) {
|
|
if (!token || typeof token !== "string" || !token.includes(".")) return null;
|
|
const [payload, signature] = token.split(".");
|
|
if (!payload || !signature || sign(payload) !== signature) return null;
|
|
|
|
try {
|
|
return JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function passwordHash(password, salt = crypto.randomBytes(16).toString("hex")) {
|
|
const hash = crypto.pbkdf2Sync(String(password), salt, 120000, 32, "sha256").toString("hex");
|
|
return `${salt}:${hash}`;
|
|
}
|
|
|
|
function checkPassword(password, stored) {
|
|
const [salt] = String(stored || "").split(":");
|
|
if (!salt) return false;
|
|
return passwordHash(password, salt) === stored;
|
|
}
|
|
|
|
function daysRemaining(expiresAt) {
|
|
return Math.max(0, Math.ceil((new Date(expiresAt).getTime() - Date.now()) / 86400000));
|
|
}
|
|
|
|
module.exports = {
|
|
nowIso,
|
|
addYears,
|
|
addHours,
|
|
createToken,
|
|
verifyToken,
|
|
passwordHash,
|
|
checkPassword,
|
|
daysRemaining,
|
|
};
|