// server/crypto/apiTokens.ts import { createHmac, randomBytes, timingSafeEqual } from "node:crypto"; const TOKEN_BYTES = 32; const TOKEN_PREFIX_LENGTH = 12; export function generateApiToken(): string { return `su_${randomBytes(TOKEN_BYTES).toString("base64url")}`; } export function tokenPrefix(token: string): string { return token.slice(0, TOKEN_PREFIX_LENGTH); } export function hashApiToken(token: string, pepperHex: string): string { const pepper = Buffer.from(pepperHex, "hex"); return createHmac("sha256", pepper).update(token).digest("base64url"); } export function verifyApiToken(token: string, expectedHash: string, pepperHex: string): boolean { const actual = Buffer.from(hashApiToken(token, pepperHex)); const expected = Buffer.from(expectedHash); return actual.length === expected.length && timingSafeEqual(actual, expected); }