/** * Pro auth for the AI proxy. * * When the proxy's Pro gate is enabled (`SASQUATCH_REQUIRE_PRO=1`) every * `/api/ai` request must carry: * • `x-sasquatch-auth` — a base64 NIP-98 (kind 27235) event proving the caller; * • `x-sasquatch-pro` — base64 of the Pro zap receipt proving an active pass. * * The NIP-98 event is signed ONCE per session and reused, so an interactive * signer (Amber) doesn't pop on every message. */ import type { NostrEvent } from '@nostrify/nostrify'; type SignEvent = (event: { kind: number; created_at: number; tags: string[][]; content: string; }) => Promise; let cachedAuthHeader: string | null = null; let cachedAuthPubkey: string | null = null; function toBase64(value: string): string { // Event JSON is ASCII (hex pubkeys/ids, tags), so btoa is safe here. return btoa(value); } export async function buildProAuthHeaders(opts: { pubkey?: string; signEvent?: SignEvent; receiptEvent?: NostrEvent | null; }): Promise> { const { pubkey, signEvent, receiptEvent } = opts; const headers: Record = {}; if (!pubkey || !signEvent) return headers; if (cachedAuthPubkey !== pubkey || !cachedAuthHeader) { const event = await signEvent({ kind: 27235, created_at: Math.floor(Date.now() / 1000), tags: [ ['u', 'https://skate-spots.org/api/ai'], ['method', 'POST'], ], content: '', }); cachedAuthHeader = toBase64(JSON.stringify(event)); cachedAuthPubkey = pubkey; } headers['x-sasquatch-auth'] = cachedAuthHeader; if (receiptEvent) headers['x-sasquatch-pro'] = toBase64(JSON.stringify(receiptEvent)); return headers; }