Explorar o código

Merge pull request #3620 from weseek/feat/move-verifyingIsSlackRequest-to-slack-package

Feat/move verifying is slack request to slack package
Sizma yosimaz %!s(int64=5) %!d(string=hai) anos
pai
achega
b3496987cd

+ 1 - 0
.github/workflows/ci.yml

@@ -273,6 +273,7 @@ jobs:
         npx lerna bootstrap
         npx lerna bootstrap
         yarn lerna add growi-plugin-lsx growi-plugin-pukiwiki-like-linker growi-plugin-attachment-refs --scope @growi/app --scope @growi/app-for-hoisting
         yarn lerna add growi-plugin-lsx growi-plugin-pukiwiki-like-linker growi-plugin-attachment-refs --scope @growi/app --scope @growi/app-for-hoisting
         yarn lerna add -D react-images@1.0.0 react-motion --scope @growi/app --scope @growi/app-for-hoisting
         yarn lerna add -D react-images@1.0.0 react-motion --scope @growi/app --scope @growi/app-for-hoisting
+        yarn lerna run build --scope @growi/slack
     - name: Print dependencies
     - name: Print dependencies
       run: |
       run: |
         echo -n "node " && node -v
         echo -n "node " && node -v

+ 1 - 0
packages/slack/src/index.ts

@@ -10,4 +10,5 @@ export const supportedGrowiCommands: string[] = [
 export * from './interfaces/growi-command';
 export * from './interfaces/growi-command';
 export * from './models/errors';
 export * from './models/errors';
 export * from './utils/slash-command-parser';
 export * from './utils/slash-command-parser';
+export * from './middlewares/verification-slack-request';
 export * from './utils/block-creater';
 export * from './utils/block-creater';

+ 47 - 0
packages/slack/src/middlewares/verification-slack-request.ts

@@ -0,0 +1,47 @@
+import { createHmac, timingSafeEqual } from 'crypto';
+import { stringify } from 'qs';
+import { Request, Response, NextFunction } from 'express';
+/**
+   * Verify if the request came from slack
+   * See: https://api.slack.com/authentication/verifying-requests-from-slack
+   */
+
+type signingSecretType = {
+  signingSecret?:string; headers:{'x-slack-signature'?:string, 'x-slack-request-timestamp':number}
+}
+
+// eslint-disable-next-line max-len
+export const verificationSlackRequest = (req : Request & signingSecretType, res:Response, next:NextFunction):Record<string, any>| void => {
+  if (req.signingSecret == null) {
+    return res.send('No signing secret.');
+  }
+
+  // take out slackSignature and timestamp from header
+  const slackSignature = req.headers['x-slack-signature'];
+  const timestamp = req.headers['x-slack-request-timestamp'];
+
+  if (slackSignature == null || timestamp == null) {
+    return res.status(403).send({ message: 'Forbidden. Enter from Slack workspace' });
+  }
+
+  // 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}:${stringify(req.body, { format: 'RFC1738' })}`;
+  const hasher = createHmac('sha256', req.signingSecret);
+  hasher.update(sigBaseString, 'utf8');
+  const hashedSigningSecret = hasher.digest('hex');
+  const growiSignature = `v0=${hashedSigningSecret}`;
+
+  // compare growiSignature and slackSignature
+  if (timingSafeEqual(Buffer.from(growiSignature, 'utf8'), Buffer.from(slackSignature, 'utf8'))) {
+    return next();
+
+  }
+
+  return res.send('Verification failed.');
+};

+ 1 - 1
packages/slackbot-proxy/package.json

@@ -17,7 +17,7 @@
     "test:lint:fix": "eslint src --ext .ts --fix"
     "test:lint:fix": "eslint src --ext .ts --fix"
   },
   },
   "dependencies": {
   "dependencies": {
-    "@growi/slack": "0.9.0-RC",
+    "@growi/slack": "^0.9.0-RC",
     "@slack/oauth": "^2.0.1",
     "@slack/oauth": "^2.0.1",
     "@slack/web-api": "^6.1.0",
     "@slack/web-api": "^6.1.0",
     "@tsed/common": "^6.43.0",
     "@tsed/common": "^6.43.0",

+ 6 - 38
src/server/routes/apiv3/slack-bot.js

@@ -1,14 +1,11 @@
-
 const express = require('express');
 const express = require('express');
 
 
-const crypto = require('crypto');
-const qs = require('qs');
-
 const loggerFactory = require('@alias/logger');
 const loggerFactory = require('@alias/logger');
 
 
 const logger = loggerFactory('growi:routes:apiv3:slack-bot');
 const logger = loggerFactory('growi:routes:apiv3:slack-bot');
 
 
 const router = express.Router();
 const router = express.Router();
+const { verificationSlackRequest } = require('@growi/slack');
 
 
 module.exports = (crowi) => {
 module.exports = (crowi) => {
   this.app = crowi.express;
   this.app = crowi.express;
@@ -34,41 +31,12 @@ module.exports = (crowi) => {
     return next();
     return next();
   }
   }
 
 
-  /**
-   * 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
+  const addSlackBotSigningSecret = (req, res, next) => {
     req.signingSecret = crowi.configManager.getConfig('crowi', 'slackbot:signingSecret');
     req.signingSecret = crowi.configManager.getConfig('crowi', 'slackbot:signingSecret');
+    return next();
+  };
 
 
-    // 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) => {
+  router.post('/', verificationRequestUrl, addSlackBotSigningSecret, verificationSlackRequest, verificationAccessToken, async(req, res) => {
 
 
     // Send response immediately to avoid opelation_timeout error
     // Send response immediately to avoid opelation_timeout error
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
@@ -130,7 +98,7 @@ module.exports = (crowi) => {
     }
     }
   };
   };
 
 
-  router.post('/interactive', verificationRequestUrl, verifyingIsSlackRequest, async(req, res) => {
+  router.post('/interactive', verificationRequestUrl, addSlackBotSigningSecret, verificationSlackRequest, async(req, res) => {
 
 
     // Send response immediately to avoid opelation_timeout error
     // Send response immediately to avoid opelation_timeout error
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events