| 93 | * @param {string} req.rawBody Raw body of webhook request to check signature against. |
| 94 | */ |
| 95 | const verifyWebhook = req => { |
| 96 | const signingSecret = process.env.SLACK_SECRET; |
| 97 | const requestSignature = req.headers['x-slack-signature']; |
| 98 | const requestTimestamp = req.headers['x-slack-request-timestamp']; |
| 99 | const requestBody = req.rawBody; |
| 100 | |
| 101 | if (!requestSignature || !requestTimestamp) { |
| 102 | const err = new Error('Missing Slack validation headers.'); |
| 103 | err.code = 400; |
| 104 | throw err; |
| 105 | } |
| 106 | |
| 107 | if (!signingSecret) { |
| 108 | const err = new Error( |
| 109 | 'Server configuration error: SLACK_SECRET is missing.' |
| 110 | ); |
| 111 | err.code = 500; |
| 112 | throw err; |
| 113 | } |
| 114 | |
| 115 | // Prevent replay attacks by verifying the timestamp is recent |
| 116 | const now = Math.floor(Date.now() / 1000); |
| 117 | if (Math.abs(now - Number(requestTimestamp)) > 60 * 5) { |
| 118 | const err = new Error('Slack request timestamp is too old.'); |
| 119 | err.code = 401; |
| 120 | throw err; |
| 121 | } |
| 122 | |
| 123 | const hmac = crypto.createHmac('sha256', signingSecret); |
| 124 | hmac.update('v0:' + requestTimestamp + ':', 'utf8'); |
| 125 | hmac.update(requestBody || ''); |
| 126 | const expectedSignature = 'v0=' + hmac.digest('hex'); |
| 127 | |
| 128 | const sigBuffer = Buffer.from(requestSignature, 'utf8'); |
| 129 | const expBuffer = Buffer.from(expectedSignature, 'utf8'); |
| 130 | |
| 131 | if ( |
| 132 | sigBuffer.length !== expBuffer.length || |
| 133 | !crypto.timingSafeEqual(sigBuffer, expBuffer) |
| 134 | ) { |
| 135 | const err = new Error('Invalid Slack signature.'); |
| 136 | err.code = 401; |
| 137 | throw err; |
| 138 | } |
| 139 | }; |
| 140 | // [END functions_verify_webhook] |
| 141 | |
| 142 | // [START functions_slack_request] |