فهرست منبع

Merge pull request #3607 from weseek/feat/4937-5597-verification-request-by-signing-secret

Feat/4937 5597 verification request by signing secret
itizawa 5 سال پیش
والد
کامیت
6c2b46e110
1فایلهای تغییر یافته به همراه39 افزوده شده و 3 حذف شده
  1. 39 3
      src/server/routes/apiv3/slack-bot.js

+ 39 - 3
src/server/routes/apiv3/slack-bot.js

@@ -1,6 +1,9 @@
 
 const express = require('express');
 
+const crypto = require('crypto');
+const qs = require('qs');
+
 const loggerFactory = require('@alias/logger');
 
 const logger = loggerFactory('growi:routes:apiv3:slack-bot');
@@ -10,7 +13,6 @@ const router = express.Router();
 module.exports = (crowi) => {
   this.app = crowi.express;
 
-
   // Check if the access token is correct
   function verificationAccessToken(req, res, next) {
     const slackBotAccessToken = req.body.slack_bot_access_token || null;
@@ -32,7 +34,41 @@ module.exports = (crowi) => {
     return next();
   }
 
-  router.post('/', verificationRequestUrl, verificationAccessToken, async(req, res) => {
+  /**
+   * Verify if the request came from slack
+   * See: https://api.slack.com/authentication/verifying-requests-from-slack
+   */
+  // TODO GW-5628 move this to slack package
+  function verifyingIsSlackRequest(req, res, next) {
+    // Temporary
+    req.signingSecret = crowi.configManager.getConfig('crowi', 'slackbot:signingSecret');
+
+    // take out slackSignature and timestamp from header
+    const slackSignature = req.headers['x-slack-signature'];
+    const timestamp = req.headers['x-slack-request-timestamp'];
+
+    // protect against replay attacks
+    const time = Math.floor(new Date().getTime() / 1000);
+    if (Math.abs(time - timestamp) > 300) {
+      return res.send('Verification failed.');
+    }
+
+    // generate growi signature
+    const sigBaseString = `v0:${timestamp}:${qs.stringify(req.body, { format: 'RFC1738' })}`;
+    const hasher = crypto.createHmac('sha256', req.signingSecret);
+    hasher.update(sigBaseString, 'utf8');
+    const hashedSigningSecret = hasher.digest('hex');
+    const growiSignature = `v0=${hashedSigningSecret}`;
+
+    // compare growiSignature and slackSignature
+    if (crypto.timingSafeEqual(Buffer.from(growiSignature, 'utf8'), Buffer.from(slackSignature, 'utf8'))) {
+      return next();
+    }
+
+    return res.send('Verification failed');
+  }
+
+  router.post('/', verificationRequestUrl, verifyingIsSlackRequest, verificationAccessToken, async(req, res) => {
 
     // Send response immediately to avoid opelation_timeout error
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
@@ -94,7 +130,7 @@ module.exports = (crowi) => {
     }
   };
 
-  router.post('/interactive', verificationRequestUrl, async(req, res) => {
+  router.post('/interactive', verificationRequestUrl, verifyingIsSlackRequest, async(req, res) => {
 
     // Send response immediately to avoid opelation_timeout error
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events