Files
Reactive-Resume/src/utils/printer-token.ts
T
2026-01-19 23:31:54 +01:00

55 lines
2.0 KiB
TypeScript

import { createHash, timingSafeEqual } from "node:crypto";
import { createIsomorphicFn } from "@tanstack/react-start";
import { env } from "./env";
const PRINTER_TOKEN_TTL_MS = 5 * 60 * 1000; // 5 minutes
/**
* Generates a time-limited token for printer route access.
* Token format: base64(resumeId:timestamp).signature
*/
export const generatePrinterToken = createIsomorphicFn().server((resumeId: string) => {
const timestamp = Date.now();
const payload = `${resumeId}:${timestamp}`;
const payloadBase64 = Buffer.from(payload).toString("base64url");
// Create HMAC signature using AUTH_SECRET
const signature = createHash("sha256").update(`${payloadBase64}.${env.AUTH_SECRET}`).digest("hex");
return `${payloadBase64}.${signature}`;
});
/**
* Verifies a printer token and returns the resume ID if valid.
* Throws an error if the token is invalid or expired.
*/
export const verifyPrinterToken = createIsomorphicFn().server((token: string) => {
const parts = token.split(".");
if (parts.length !== 2) throw new Error("Invalid token format");
const [payloadBase64, signature] = parts;
// Verify signature
const expectedSignature = createHash("sha256").update(`${payloadBase64}.${env.AUTH_SECRET}`).digest("hex");
const signatureBuffer = Buffer.from(signature);
const expectedBuffer = Buffer.from(expectedSignature);
if (signatureBuffer.length !== expectedBuffer.length || !timingSafeEqual(signatureBuffer, expectedBuffer)) {
throw new Error("Invalid token signature");
}
// Decode payload
const payload = Buffer.from(payloadBase64, "base64url").toString("utf-8");
const [resumeId, timestampStr] = payload.split(":");
if (!resumeId || !timestampStr) throw new Error("Invalid token payload");
const timestamp = Number.parseInt(timestampStr, 10);
if (Number.isNaN(timestamp)) throw new Error("Invalid timestamp");
// Check expiration
const age = Date.now() - timestamp;
if (age < 0 || age > PRINTER_TOKEN_TTL_MS) throw new Error("Token expired");
return resumeId;
});