import { createHmac, randomBytes, timingSafeEqual } from "crypto"; const EMAIL_HMAC_KEY = process.env.EMAIL_HMAC_KEY || "pingql-default-hmac-key"; export function generateKey(): string { return randomBytes(32).toString("base64url"); } export function hashEmail(email: string): string { return createHmac("sha256", EMAIL_HMAC_KEY).update(email.toLowerCase().trim()).digest("hex"); } export function extractAuthKey(headers: Record, cookie: any): string | null { const authHeader = headers["authorization"] ?? ""; const bearer = authHeader.match(/^bearer\s+(.+)$/i)?.[1]?.trim(); return bearer ?? cookie?.pingql_key?.value ?? null; } export async function resolveKey( sql: any, key: string, opts?: { trackUsage?: boolean } ): Promise<{ accountId: string; keyId: string | null; plan: string } | null> { const [account] = await sql`SELECT id, plan FROM accounts WHERE key = ${key}`; if (account) return { accountId: account.id, keyId: null, plan: account.plan }; const [apiKey] = await sql`SELECT k.id, k.account_id, a.plan FROM api_keys k JOIN accounts a ON a.id = k.account_id WHERE k.key = ${key}`; if (apiKey) { if (opts?.trackUsage !== false) { sql`UPDATE api_keys SET last_used_at = now() WHERE id = ${apiKey.id}`.catch(() => {}); } return { accountId: apiKey.account_id, keyId: apiKey.id, plan: apiKey.plan }; } return null; } export const COOKIE_OPTS = { httpOnly: true, secure: process.env.COOKIE_SECURE !== "false", sameSite: "none" as const, path: "/", domain: process.env.COOKIE_DOMAIN ?? ".pingql.com", maxAge: 60 * 60 * 24 * 30, }; export function safeTokenCompare(a: string | undefined, b: string | undefined): boolean { if (!a || !b) return false; const bufA = Buffer.from(a); const bufB = Buffer.from(b); if (bufA.length !== bufB.length) return false; return timingSafeEqual(bufA, bufB); } export const SECURITY_HEADERS = { "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY", "Strict-Transport-Security": "max-age=63072000; includeSubDomains", "X-XSS-Protection": "0", "Referrer-Policy": "strict-origin-when-cross-origin", };