I am using the validate-slack-request package to validate my incoming slack requests are from slack. This works fine for Slash commands and Interactive Components (buttons, etc.). However it is not working for the Events API
I noticed that the POST request body has a different format for the Events API. There's no payload. But it's not clear to me what slack is giving me to use to verify. Code is below
//This WORKS
app.post("/interactiveCommand", async (req, res) => {
const legit = validateSlackRequest(process.env.SLACK_SIGNING_SECRET, req, false);
if (!legit) {
console.log("UNAUTHORIZED ACCESS ", req.headers, req.body);
return res.status(403).send("Unauthorized");
}
await interactiveCommand(...);
return;
});
//This does NOT WORK
app.post("/slackEvents", parser, json, async (req, res) => {
const legit = validateSlackRequest(process.env.SLACK_SIGNING_SECRET, req, false);
if (!legit) {
console.log("UNAUTHORIZED ACCESS ", req.headers, req.body);
res.status(403).send("Unauthorized");
} else {
try {
switch (req.body.event.type) {
case "message":
await handleMessageEvent(...);
break;
case "app_home_opened":
res.status(200).send();
await updateUserHomePage(...);
break;
default:
res.status(200).send();
return;
}
} catch(e) {
console.log("Error with event handling! ", e);
}
}
});
const crypto = require('crypto')
const querystring = require('querystring')
// Adhering to RFC 3986
// Inspired from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
function fixedEncodeURIComponent (str) {
return str.replace(/[!'()*~]/g, function (c) {
return '%' + c.charCodeAt(0).toString(16).toUpperCase()
})
}
/**
* Validate incoming Slack request
*
* @param {string} slackAppSigningSecret - Slack application signing secret
* @param {object} httpReq - Express request object
* @param {boolean} [logging=false] - Enable logging to console
*
* @returns {boolean} Result of vlaidation
*/
function validateSlackRequest (slackAppSigningSecret, httpReq, logging) {
logging = logging || false
if (typeof logging !== 'boolean') {
throw new Error('Invalid type for logging. Provided ' + typeof logging + ', expected boolean')
}
if (!slackAppSigningSecret || typeof slackAppSigningSecret !== 'string' || slackAppSigningSecret === '') {
throw new Error('Invalid slack app signing secret')
}
const xSlackRequestTimeStamp = httpReq.get('X-Slack-Request-Timestamp')
const SlackSignature = httpReq.get('X-Slack-Signature')
const bodyPayload = fixedEncodeURIComponent(querystring.stringify(httpReq.body).replace(/%20/g, '+')) // Fix for #1
if (!(xSlackRequestTimeStamp && SlackSignature && bodyPayload)) {
if (logging) { console.log('Missing part in Slack\'s request') }
return false
}
const baseString = 'v0:' + xSlackRequestTimeStamp + ':' + bodyPayload
const hash = 'v0=' + crypto.createHmac('sha256', slackAppSigningSecret)
.update(baseString)
.digest('hex')
if (logging) {
console.log('Slack verifcation:\n Request body: ' + bodyPayload + '\n Calculated Hash: ' + hash + '\n Slack-Signature: ' + SlackSignature)
}
return (SlackSignature === hash)
}