import crypto from 'crypto'; /** * Generate share link checksum for location * Uses HMAC-SHA256 for cryptographic integrity * * SECURITY: Prevents location ID enumeration while allowing stateless validation */ export function generateShareChecksum(locationId: string): string { const secret = process.env.SHARE_LINK_SECRET; if (!secret) { throw new Error('SHARE_LINK_SECRET environment variable not configured'); } return crypto .createHmac('sha256', secret) .update(locationId) .digest('hex') .substring(0, 16); // 64 bits of entropy (sufficient for share links) } /** * Validate share link checksum * Uses constant-time comparison to prevent timing attacks * * @param locationId - The location ID from URL * @param providedChecksum - The checksum from URL * @returns true if checksum is valid */ export function validateShareChecksum( locationId: string, providedChecksum: string ): boolean { try { const expectedChecksum = generateShareChecksum(locationId); // Convert to buffers for timing-safe comparison const expected = Buffer.from(expectedChecksum); const provided = Buffer.from(providedChecksum); // Length check (prevents timing attack on different lengths) if (expected.length !== provided.length) { return false; } // Constant-time comparison (prevents timing attacks) return crypto.timingSafeEqual(expected, provided); } catch { return false; } }