import { createHmac } from 'crypto' interface ApprovePayload { channels: { id: string; name: string }[] email: string exp: number } const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000 function getSecret(): string { const secret = process.env.TOKEN_SECRET if (!secret) throw new Error('TOKEN_SECRET env var is not set') return secret } export function createApproveToken( channels: { id: string; name: string }[], email: string, ): string { const payload: ApprovePayload = { channels, email, exp: Date.now() + SEVEN_DAYS_MS, } const data = Buffer.from(JSON.stringify(payload)).toString('base64url') const sig = createHmac('sha256', getSecret()).update(data).digest('hex') return `${data}.${sig}` } export function verifyApproveToken(token: string): ApprovePayload | null { const parts = token.split('.') if (parts.length !== 2) return null const [data, sig] = parts const expected = createHmac('sha256', getSecret()).update(data).digest('hex') // Constant-time comparison if (sig.length !== expected.length) return null let mismatch = 0 for (let i = 0; i < sig.length; i++) { mismatch |= sig.charCodeAt(i) ^ expected.charCodeAt(i) } if (mismatch !== 0) return null try { const payload: ApprovePayload = JSON.parse( Buffer.from(data, 'base64url').toString('utf-8'), ) if (!payload.exp || !payload.channels || !payload.email) return null if (Date.now() > payload.exp) return null return payload } catch { return null } }