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

Merge pull request #3665 from weseek/imprv/reorganize-endpoints

Imprv/reorganize endpoints
Yuki Takei 5 лет назад
Родитель
Сommit
1eee738931

+ 34 - 21
packages/slackbot-proxy/src/controllers/slack.ts

@@ -1,5 +1,5 @@
 import {
-  BodyParams, Controller, Get, Inject, Post, Req, Res,
+  BodyParams, Controller, Get, Inject, Post, Req, Res, UseBefore,
 } from '@tsed/common';
 import { parseSlashCommand } from '@growi/slack';
 import { Installation } from '~/entities/installation';
@@ -11,6 +11,8 @@ import { InstallerService } from '~/services/InstallerService';
 import { RegisterService } from '~/services/RegisterService';
 
 import loggerFactory from '~/utils/logger';
+import { AuthorizeMiddleware } from '~/middlewares/authorizer';
+import { AuthedReq } from '~/interfaces/authorized-req';
 
 const logger = loggerFactory('slackbot-proxy:controllers:slack');
 
@@ -32,10 +34,6 @@ export class SlackCtrl {
   @Inject()
   registerService: RegisterService;
 
-  growiCommandsMappings = {
-    register: async(body:{[key:string]:string}):Promise<void> => this.registerService.execSlashCommand(body),
-  };
-
   @Get('/testsave')
   testsave(): void {
     const installation = new Installation();
@@ -75,30 +73,26 @@ export class SlackCtrl {
       + '</a>';
   }
 
-  @Post('/events')
-  async handleEvent(@BodyParams() body:{[key:string]:string}, @Res() res: Res): Promise<string> {
-    // eslint-disable-next-line max-len
-    // see: https://api.slack.com/apis/connections/events-api#the-events-api__subscribing-to-event-types__events-api-request-urls__request-url-configuration--verification
-    if (body.type === 'url_verification') {
-      return body.challenge;
-    }
+  @Post('/commands')
+  @UseBefore(AuthorizeMiddleware)
+  async handleCommand(@Req() req: AuthedReq, @Res() res: Res): Promise<void|string> {
+    const { body, authorizeResult } = req;
 
     if (body.text == null) {
       return 'No text.';
     }
 
-    const parsedBody = parseSlashCommand(body);
-    const executeGrowiCommand = this.growiCommandsMappings[parsedBody.growiCommandType];
-
-    if (executeGrowiCommand == null) {
-      return 'No executeGrowiCommand';
-    }
-
     // Send response immediately to avoid opelation_timeout error
     // See https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events
     res.send();
 
-    await executeGrowiCommand(body);
+    const growiCommand = parseSlashCommand(body);
+
+    // register
+    if (growiCommand.growiCommandType === 'register') {
+      await this.registerService.process(growiCommand, authorizeResult, body as {[key:string]:string});
+      return;
+    }
 
     const installation = await this.installationRepository.findByID('1');
     if (installation == null) {
@@ -118,7 +112,26 @@ export class SlackCtrl {
       order = await this.orderRepository.save({ installation: installation.id });
     }
 
-    return 'This action will be handled by bolt service.';
+    return;
+  }
+
+  @Post('/interactions')
+  async handleInteraction(@BodyParams() body:{[key:string]:string}, @Res() res: Res): Promise<void|string> {
+    logger.info('receive interaction', body);
+    return;
+  }
+
+  @Post('/events')
+  async handleEvent(@BodyParams() body:{[key:string]:string}, @Res() res: Res): Promise<void|string> {
+    // eslint-disable-next-line max-len
+    // see: https://api.slack.com/apis/connections/events-api#the-events-api__subscribing-to-event-types__events-api-request-urls__request-url-configuration--verification
+    if (body.type === 'url_verification') {
+      return body.challenge;
+    }
+
+    logger.info('receive event', body);
+
+    return;
   }
 
   @Get('/oauth_redirect')

+ 6 - 0
packages/slackbot-proxy/src/interfaces/authorized-req.ts

@@ -0,0 +1,6 @@
+import { AuthorizeResult } from '@slack/oauth';
+import { Req } from '@tsed/common';
+
+export type AuthedReq = Req & {
+  authorizeResult: AuthorizeResult,
+};

+ 6 - 0
packages/slackbot-proxy/src/interfaces/growi-command-processor.ts

@@ -0,0 +1,6 @@
+import { AuthorizeResult } from '@slack/oauth';
+import { GrowiCommand } from '@growi/slack';
+
+export interface GrowiCommandProcessor {
+  process(growiCommand: GrowiCommand, authorizeResult: AuthorizeResult, body: {[key:string]:string}): Promise<void>
+}

+ 0 - 3
packages/slackbot-proxy/src/interfaces/growi-commands-mappings.ts

@@ -1,3 +0,0 @@
-export interface GrowiCommandsMappings{
-  execSlashCommand(body:{[key:string]:string}):Promise<void>
-}

+ 49 - 0
packages/slackbot-proxy/src/middlewares/authorizer.ts

@@ -0,0 +1,49 @@
+import { InstallationQuery } from '@slack/oauth';
+import {
+  IMiddleware, Inject, Middleware, Req, Res,
+} from '@tsed/common';
+
+import { AuthedReq } from '~/interfaces/authorized-req';
+import { InstallationRepository } from '~/repositories/installation';
+import { InstallerService } from '~/services/InstallerService';
+
+@Middleware()
+export class AuthorizeMiddleware implements IMiddleware {
+
+  @Inject()
+  installerService: InstallerService;
+
+  @Inject()
+  installationRepository: InstallationRepository;
+
+  async use(@Req() req: AuthedReq, @Res() res: Res): Promise<void> {
+    const { body } = req;
+
+    // extract id from body
+    const teamId = body.team_id;
+    const enterpriseId = body.enterprize_id;
+
+    if (teamId == null && enterpriseId == null) {
+      res.writeHead(400);
+      return res.end();
+    }
+
+    // create query from body
+    const query: InstallationQuery<boolean> = {
+      teamId,
+      enterpriseId,
+      isEnterpriseInstall: body.is_enterprise_install === 'true',
+    };
+
+    const result = await this.installerService.installer.authorize(query);
+
+    if (result.botToken == null) {
+      res.writeHead(403);
+      return res.end();
+    }
+
+    // set authorized data
+    req.authorizeResult = result;
+  }
+
+}

+ 10 - 10
packages/slackbot-proxy/src/services/InstallerService.ts

@@ -55,16 +55,16 @@ export class InstallerService {
           return;
         },
         fetchInstallation: async(installQuery: InstallationQuery<boolean>) => {
-          const installation: SlackInstallation<'v1' | 'v2', boolean> = {
-            team: undefined,
-            enterprise: undefined,
-            user: {
-              id: '',
-              token: undefined,
-              scopes: undefined,
-            },
-          };
-          return installation;
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          const id = installQuery.enterpriseId || installQuery.teamId!;
+
+          const installation = await repository.findByTeamIdOrEnterpriseId(id);
+
+          if (installation == null) {
+            throw new Error('Failed fetching installation');
+          }
+
+          return installation.data;
         },
       },
     });

+ 0 - 16
packages/slackbot-proxy/src/services/RecieveService.ts

@@ -1,16 +0,0 @@
-import { Service } from '@tsed/di';
-import { parseSlashCommand } from '@growi/slack';
-
-@Service()
-export class ReceiveService {
-
-  receiveContentsFromSlack(body:{[key:string]:string}) : string {
-    const parseBody = parseSlashCommand(body);
-    if (parseBody.growiCommandType === 'register') {
-      console.log('register action occured');
-      return 'register action occurd';
-    }
-    return 'return receiveContentsFromSlack';
-  }
-
-}

+ 10 - 5
packages/slackbot-proxy/src/services/RegisterService.ts

@@ -1,14 +1,19 @@
 import { Service } from '@tsed/di';
 import { WebClient, LogLevel } from '@slack/web-api';
-import { generateInputSectionBlock } from '@growi/slack';
-import { GrowiCommandsMappings } from '../interfaces/growi-commands-mappings';
+import { generateInputSectionBlock, GrowiCommand } from '@growi/slack';
+import { AuthorizeResult } from '@slack/oauth';
+
+import { GrowiCommandProcessor } from '~/interfaces/growi-command-processor';
 
 @Service()
-export class RegisterService implements GrowiCommandsMappings {
+export class RegisterService implements GrowiCommandProcessor {
+
+  async process(growiCommand: GrowiCommand, authorizeResult: AuthorizeResult, body: {[key:string]:string}): Promise<void> {
+
+    const { botToken } = authorizeResult;
 
-  async execSlashCommand(body:{[key:string]:string}):Promise<void> {
     // tmp use process.env
-    const client = new WebClient(process.env.SLACK_BOT_USER_OAUTH_TOKEN, { logLevel: LogLevel.DEBUG });
+    const client = new WebClient(botToken, { logLevel: LogLevel.DEBUG });
     await client.views.open({
       trigger_id: body.trigger_id,
       view: {

+ 2 - 2
src/server/routes/apiv3/slack-bot.js

@@ -36,7 +36,7 @@ module.exports = (crowi) => {
     return next();
   };
 
-  router.post('/', verificationRequestUrl, addSlackBotSigningSecret, verificationSlackRequest, verificationAccessToken, async(req, res) => {
+  router.post('/commands', verificationRequestUrl, addSlackBotSigningSecret, verificationSlackRequest, 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
@@ -98,7 +98,7 @@ module.exports = (crowi) => {
     }
   };
 
-  router.post('/interactive', verificationRequestUrl, addSlackBotSigningSecret, verificationSlackRequest, async(req, res) => {
+  router.post('/interactions', verificationRequestUrl, addSlackBotSigningSecret, verificationSlackRequest, 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