import 'server-only'; import crypto from 'crypto'; import { wwwUrl } from '@/lib/hosts'; const TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 365; function getSigningSecret(): string { const secret = process.env.EMAIL_UNSUBSCRIBE_SECRET || process.env.NEXTAUTH_SECRET; if (!secret) { throw new Error('Set EMAIL_UNSUBSCRIBE_SECRET before sending marketing email.'); } return secret; } function sign(payload: string): string { return crypto.createHmac('sha256', getSigningSecret()).update(payload).digest('base64url'); } function normalizeEmail(email: string): string { return email.trim().toLowerCase(); } export function createMarketingUnsubscribeUrl(email: string): string { const payload = Buffer.from( JSON.stringify({ email: normalizeEmail(email), expiresAt: Date.now() + TOKEN_TTL_MS }) ).toString('base64url'); const token = `${payload}.${sign(payload)}`; return wwwUrl(`/unsubscribe?token=${encodeURIComponent(token)}`); } export function getUnsubscribeEmail(token: string | null | undefined): string | null { if (!token) return null; const separator = token.lastIndexOf('.'); if (separator <= 0 || separator === token.length - 1) return null; const payload = token.slice(0, separator); const providedSignature = token.slice(separator + 1); const expectedSignature = sign(payload); const providedBuffer = Buffer.from(providedSignature); const expectedBuffer = Buffer.from(expectedSignature); if ( providedBuffer.length !== expectedBuffer.length || !crypto.timingSafeEqual(providedBuffer, expectedBuffer) ) { return null; } try { const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')) as { email?: unknown; expiresAt?: unknown; }; if ( typeof decoded.email !== 'string' || typeof decoded.expiresAt !== 'number' || decoded.expiresAt < Date.now() ) { return null; } return normalizeEmail(decoded.email); } catch { return null; } }