Skip to content

Commit e2d1b2b

Browse files
docs(security): add detailed comments to Slack request verification function
1 parent 74c8f3e commit e2d1b2b

File tree

1 file changed

+19
-10
lines changed

1 file changed

+19
-10
lines changed

functions/slack/index.js

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,11 @@ const formatSlackMessage = (query, response) => {
8686

8787
// [START functions_verify_webhook]
8888
/**
89-
* Verify that the webhook request came from Slack.
89+
* Verify that the webhook request came from Slack by validating its signature.
9090
*
91+
* This function follows the official Slack verification process:
92+
* https://api.slack.com/authentication/verifying-requests-from-slack
93+
*
9194
* @param {object} req Cloud Function request object.
9295
* @param {string} req.headers Headers Slack SDK uses to authenticate request.
9396
* @param {string} req.rawBody Raw body of webhook request to check signature against.
@@ -98,28 +101,34 @@ const verifyWebhook = req => {
98101
const requestTimestamp = req.headers['x-slack-request-timestamp'];
99102

100103
if (!requestSignature || !requestTimestamp) {
101-
throw new Error('Missing slack signature or timestamp headers');
104+
throw new Error('Missing Slack signature or timestamp headers');
102105
}
103106

104-
// Prevent repeat seizures (5 minutes tolerance)
107+
// Protect against replay sttacks by ensuring the request is recent (within 5 minutes)
105108
const fiveMinutesAgo = Math.floor(Date.now() / 1000) - 300;
106-
if (parseInt(requestTime, 10) < fiveMinutesAgo) {
107-
throw new Error('Requested slack timestamp is too old');
109+
if (parseInt(requestTimestamp, 10) < fiveMinutesAgo) {
110+
throw new Error('Slack request timestamp is too old');
108111
}
109112

113+
// Create the base string as Slack expects: version + ':' timestamp + ':' + raw body
110114
const basestring = `v0:${requestTimestamp}:${req.rawBody}`;
115+
116+
// Create a HMAC SHA256 hash using the Slack signing secret
111117
const hmac = crypto.createHmac('sha256', slackSigningSecret);
112118
hmac.update(basestring, 'utf-8');
113119
const digest = `v0=${hmac.digest('hex')}`;
114120

115-
if (!crypto.timingSafeEqual(Buffer.from(digest, 'utf-8'), Buffer.from(requestSignature, 'utf8'))) {
116-
const error = new Error('slack invalid signature');
121+
// Perform a constant-time comparison to prevent timing attacks
122+
if (
123+
!crypto.timingSafeEqual(
124+
Buffer.from(digest, 'utf-8'),
125+
Buffer.from(requestSignature, 'utf8')
126+
)
127+
) {
128+
const error = new Error('Invalid Slack signature');
117129
error.code = 401;
118130
throw error;
119131
}
120-
121-
// This method throws an exception if an incoming request is invalid.
122-
verifyRequestSignature(signature);
123132
};
124133
// [END functions_verify_webhook]
125134

0 commit comments

Comments
 (0)