Просмотр исходного кода

Merge pull request #3791 from weseek/feat/5782-5788-implement-transparent-api

Feat/5782 5788 implement transparent api
itizawa 4 лет назад
Родитель
Сommit
efa72de954

+ 2 - 2
packages/slack/src/utils/webclient-factory.ts

@@ -7,6 +7,6 @@ const isProduction = process.env.NODE_ENV === 'production';
  * @param token Slack Bot Token or Proxy Server URI
  * @returns
  */
-export const generateWebClient = (token: string, serverUri?: string): WebClient => {
-  return new WebClient(token, { slackApiUrl: serverUri, logLevel: isProduction ? LogLevel.DEBUG : LogLevel.INFO });
+export const generateWebClient = (token: string, serverUri?: string, headers?:{[key:string]:string}): WebClient => {
+  return new WebClient(token, { slackApiUrl: serverUri, logLevel: isProduction ? LogLevel.DEBUG : LogLevel.INFO, headers });
 };

+ 41 - 2
packages/slackbot-proxy/src/controllers/growi-to-slack.ts

@@ -1,11 +1,13 @@
 import {
-  Controller, Get, Inject, Req, Res, UseBefore,
+  Controller, Get, Post, Inject, Req, Res, UseBefore,
 } from '@tsed/common';
 import axios from 'axios';
 
 import { WebAPICallResult } from '@slack/web-api';
 
-import { verifyGrowiToSlackRequest, getConnectionStatuses, relationTestToSlack } from '@growi/slack';
+import {
+  verifyGrowiToSlackRequest, getConnectionStatuses, relationTestToSlack, generateWebClient,
+} from '@growi/slack';
 
 import { GrowiReq } from '~/interfaces/growi-to-slack/growi-req';
 import { InstallationRepository } from '~/repositories/installation';
@@ -148,4 +150,41 @@ export class GrowiToSlackCtrl {
     return res.send({ relation: createdRelation });
   }
 
+  @Post('/*')
+  @UseBefore(verifyGrowiToSlackRequest)
+  async postResult(@Req() req: GrowiReq, @Res() res: Res): Promise<void|string|Res|WebAPICallResult> {
+    const { tokenGtoPs } = req;
+
+    if (tokenGtoPs.length !== 1) {
+      return res.status(400).send({ message: 'tokenGtoPs is invalid' });
+    }
+
+    const tokenGtoP = tokenGtoPs[0];
+
+    // retrieve relation with Installation
+    const relation = await this.relationRepository.createQueryBuilder('relation')
+      .where('tokenGtoP = :token', { token: tokenGtoP })
+      .leftJoinAndSelect('relation.installation', 'installation')
+      .getOne();
+
+    if (relation == null) {
+      return res.status(400).send({ message: 'relation is invalid' });
+    }
+
+    const token = relation.installation.data.bot?.token;
+    if (token == null) {
+      return res.status(400).send({ message: 'installation is invalid' });
+    }
+
+    const client = generateWebClient(token);
+    await client.chat.postMessage({
+      channel: req.body.channel,
+      blocks: req.body.blocks,
+    });
+
+    logger.debug('postMessage is success');
+
+    return res.end();
+  }
+
 }

+ 61 - 11
src/server/routes/apiv3/slack-integration.js

@@ -1,9 +1,10 @@
 const express = require('express');
 const mongoose = require('mongoose');
+const urljoin = require('url-join');
 
 const loggerFactory = require('@alias/logger');
 
-const { verifySlackRequest } = require('@growi/slack');
+const { verifySlackRequest, generateWebClient } = require('@growi/slack');
 
 const logger = loggerFactory('growi:routes:apiv3:slack-integration');
 const router = express.Router();
@@ -24,13 +25,13 @@ module.exports = (crowi) => {
       return res.status(400).send({ message });
     }
 
-    const slackAppIntegration = await SlackAppIntegration.estimatedDocumentCount({ tokenPtoG });
+    const slackAppIntegrationCount = await SlackAppIntegration.estimatedDocumentCount({ tokenPtoG });
 
     logger.debug('verifyAccessTokenFromProxy', {
       tokenPtoG,
     });
 
-    if (slackAppIntegration === 0) {
+    if (slackAppIntegrationCount === 0) {
       return res.status(403).send({
         message: 'The access token that identifies the request source is slackbot-proxy is invalid. Did you setup with `/growi register`?',
       });
@@ -44,6 +45,31 @@ module.exports = (crowi) => {
     return next();
   };
 
+  const generateClientForResponse = (tokenGtoP) => {
+    const currentBotType = crowi.configManager.getConfig('crowi', 'slackbot:currentBotType');
+
+    if (currentBotType == null) {
+      throw new Error('The config \'SLACK_BOT_TYPE\'(ns: \'crowi\', key: \'slackbot:currentBotType\') must be set.');
+    }
+
+    let token;
+
+    // connect directly
+    if (tokenGtoP == null) {
+      token = crowi.configManager.getConfig('crowi', 'slackbot:token');
+      return generateWebClient(token);
+    }
+
+    // connect to proxy
+    const proxyServerUri = crowi.configManager.getConfig('crowi', 'slackbot:proxyServerUri');
+    const serverUri = urljoin(proxyServerUri, '/g2s');
+    const headers = {
+      'x-growi-gtop-tokens': tokenGtoP,
+    };
+
+    return generateWebClient(token, serverUri, headers);
+  };
+
   async function handleCommands(req, res) {
     const { body } = req;
 
@@ -59,19 +85,31 @@ module.exports = (crowi) => {
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
     res.send();
 
+    const tokenPtoG = req.headers['x-growi-ptog-tokens'];
+
+    // generate client
+    let client;
+    if (tokenPtoG == null) {
+      client = generateClientForResponse();
+    }
+    else {
+      const slackAppIntegration = await SlackAppIntegration.findOne({ tokenPtoG });
+      client = generateClientForResponse(slackAppIntegration.tokenGtoP);
+    }
+
     const args = body.text.split(' ');
     const command = args[0];
 
     try {
       switch (command) {
         case 'search':
-          await crowi.slackBotService.showEphemeralSearchResults(body, args);
+          await crowi.slackBotService.showEphemeralSearchResults(client, body, args);
           break;
         case 'create':
-          await crowi.slackBotService.createModal(body);
+          await crowi.slackBotService.createModal(client, body);
           break;
         default:
-          await crowi.slackBotService.notCommand(body);
+          await crowi.slackBotService.notCommand(client, body);
           break;
       }
     }
@@ -98,12 +136,12 @@ module.exports = (crowi) => {
   });
 
 
-  const handleBlockActions = async(payload) => {
+  const handleBlockActions = async(client, payload) => {
     const { action_id: actionId } = payload.actions[0];
 
     switch (actionId) {
       case 'shareSearchResults': {
-        await crowi.slackBotService.shareSearchResults(payload);
+        await crowi.slackBotService.shareSearchResults(client, payload);
         break;
       }
       case 'showNextResults': {
@@ -111,7 +149,7 @@ module.exports = (crowi) => {
 
         const { body, args, offset } = parsedValue;
         const newOffset = offset + 10;
-        await crowi.slackBotService.showEphemeralSearchResults(body, args, newOffset);
+        await crowi.slackBotService.showEphemeralSearchResults(client, body, args, newOffset);
         break;
       }
       default:
@@ -137,16 +175,28 @@ module.exports = (crowi) => {
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
     res.send();
 
+
+    const tokenPtoG = req.headers['x-growi-ptog-tokens'];
+    // generate client
+    let client;
+    if (tokenPtoG == null) {
+      client = generateClientForResponse();
+    }
+    else {
+      const slackAppIntegration = await SlackAppIntegration.findOne({ tokenPtoG });
+      client = generateClientForResponse(slackAppIntegration.tokenGtoP);
+    }
+
     const payload = JSON.parse(req.body.payload);
     const { type } = payload;
 
     try {
       switch (type) {
         case 'block_actions':
-          await handleBlockActions(payload);
+          await handleBlockActions(client, payload);
           break;
         case 'view_submission':
-          await handleViewSubmission(payload);
+          await handleViewSubmission(client, payload);
           break;
         default:
           break;

+ 16 - 50
src/server/service/slackbot.js

@@ -1,8 +1,6 @@
 const logger = require('@alias/logger')('growi:service:SlackBotService');
 const mongoose = require('mongoose');
 
-const { generateWebClient } = require('@growi/slack');
-
 const PAGINGLIMIT = 10;
 
 const S2sMessage = require('../models/vo/s2s-message');
@@ -25,29 +23,6 @@ class SlackBotService extends S2sMessageHandlable {
     this.lastLoadedAt = new Date();
   }
 
-  get client() {
-    const currentBotType = this.crowi.configManager.getConfig('crowi', 'slackbot:currentBotType');
-
-    if (currentBotType == null) {
-      throw new Error('The config \'SLACK_BOT_TYPE\'(ns: \'crowi\', key: \'slackbot:currentBotType\') must be set.');
-    }
-
-    let serverUri;
-    let token;
-
-    // connect to proxy
-    if (currentBotType !== 'customBotWithoutProxy') {
-      // TODO: https://youtrack.weseek.co.jp/issue/GW-5896
-      serverUri = 'http://localhost:8080/slack-api-proxy/';
-    }
-    // connect directly
-    else {
-      token = this.crowi.configManager.getConfig('crowi', 'slackbot:token');
-    }
-
-    return generateWebClient(token, serverUri);
-  }
-
   /**
    * @inheritdoc
    */
@@ -87,13 +62,9 @@ class SlackBotService extends S2sMessageHandlable {
     }
   }
 
-  async sendAuthTest() {
-    await this.client.api.test();
-  }
-
-  notCommand(body) {
+  async notCommand(client, body) {
     logger.error('Invalid first argument');
-    this.client.chat.postEphemeral({
+    client.chat.postEphemeral({
       channel: body.channel_id,
       user: body.user_id,
       blocks: [
@@ -109,10 +80,10 @@ class SlackBotService extends S2sMessageHandlable {
     return keywords;
   }
 
-  async getSearchResultPaths(body, args, offset = 0) {
+  async getSearchResultPaths(client, body, args, offset = 0) {
     const firstKeyword = args[1];
     if (firstKeyword == null) {
-      this.client.chat.postEphemeral({
+      client.chat.postEphemeral({
         channel: body.channel_id,
         user: body.user_id,
         blocks: [
@@ -132,7 +103,7 @@ class SlackBotService extends S2sMessageHandlable {
     // no search results
     if (results.data.length === 0) {
       logger.info(`No page found with "${keywords}"`);
-      this.client.chat.postEphemeral({
+      client.chat.postEphemeral({
         channel: body.channel_id,
         user: body.user_id,
         blocks: [
@@ -166,22 +137,17 @@ class SlackBotService extends S2sMessageHandlable {
     };
   }
 
-  async getSlackChannelName() {
-    const slackTeamInfo = await this.client.team.info();
-    return slackTeamInfo.team.name;
-  }
-
-  shareSearchResults(payload) {
-    this.client.chat.postMessage({
+  async shareSearchResults(client, payload) {
+    client.chat.postMessage({
       channel: payload.channel.id,
       text: payload.actions[0].value,
     });
   }
 
-  async showEphemeralSearchResults(body, args, offsetNum) {
+  async showEphemeralSearchResults(client, body, args, offsetNum) {
     const {
       resultPaths, offset, resultsTotal,
-    } = await this.getSearchResultPaths(body, args, offsetNum);
+    } = await this.getSearchResultPaths(client, body, args, offsetNum);
 
     const keywords = this.getKeywords(args);
 
@@ -246,7 +212,7 @@ class SlackBotService extends S2sMessageHandlable {
           },
         );
       }
-      await this.client.chat.postEphemeral({
+      await client.chat.postEphemeral({
         channel: body.channel_id,
         user: body.user_id,
         blocks: [
@@ -258,7 +224,7 @@ class SlackBotService extends S2sMessageHandlable {
     }
     catch {
       logger.error('Failed to get search results.');
-      await this.client.chat.postEphemeral({
+      await client.chat.postEphemeral({
         channel: body.channel_id,
         user: body.user_id,
         blocks: [
@@ -269,9 +235,9 @@ class SlackBotService extends S2sMessageHandlable {
     }
   }
 
-  async createModal(body) {
+  async createModal(client, body) {
     try {
-      await this.client.views.open({
+      await client.views.open({
         trigger_id: body.trigger_id,
 
         view: {
@@ -299,7 +265,7 @@ class SlackBotService extends S2sMessageHandlable {
     }
     catch (err) {
       logger.error('Failed to create a page.');
-      await this.client.chat.postEphemeral({
+      await client.chat.postEphemeral({
         channel: body.channel_id,
         user: body.user_id,
         blocks: [
@@ -311,7 +277,7 @@ class SlackBotService extends S2sMessageHandlable {
   }
 
   // Submit action in create Modal
-  async createPageInGrowi(payload) {
+  async createPageInGrowi(client, payload) {
     const Page = this.crowi.model('Page');
     const pathUtils = require('growi-commons').pathUtils;
 
@@ -328,7 +294,7 @@ class SlackBotService extends S2sMessageHandlable {
       await Page.create(path, contentsBody, dummyObjectIdOfUser, {});
     }
     catch (err) {
-      this.client.chat.postMessage({
+      client.chat.postMessage({
         channel: payload.user.id,
         blocks: [
           this.generateMarkdownSectionBlock(`Cannot create new page to existed path\n *Contents* :memo:\n ${contentsBody}`)],