const crypto = require('crypto') function base64UrlDecode (input) { const normalized = String(input).replace(/-/g, '+').replace(/_/g, '/') const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4) return Buffer.from(padded, 'base64') } /** * Verifies + decodes a Meta `signed_request` (used by the Instagram deauthorize * and data-deletion callbacks). Returns the decoded payload, or null if the * signature doesn't match / the value is malformed — callers must treat null * as "reject the request", never fall back to trusting the unverified payload. */ function parseSignedRequest (signedRequest, appSecret) { if (!signedRequest || typeof signedRequest !== 'string' || !appSecret) return null const parts = signedRequest.split('.') if (parts.length !== 2) return null const [encodedSig, encodedPayload] = parts let providedSig try { providedSig = base64UrlDecode(encodedSig) } catch { return null } const expectedSig = crypto.createHmac('sha256', appSecret).update(encodedPayload).digest() if (providedSig.length !== expectedSig.length || !crypto.timingSafeEqual(providedSig, expectedSig)) { return null } try { const data = JSON.parse(base64UrlDecode(encodedPayload).toString('utf8')) if (data.algorithm !== 'HMAC-SHA256') return null return data } catch { return null } } module.exports = { parseSignedRequest }