Explorar o código

Merge branch 'feat/growi-bot' into imprv/switch-display-by-passing-test

zahmis %!s(int64=4) %!d(string=hai) anos
pai
achega
e2c3bd9b22

+ 1 - 1
packages/slack/src/utils/post-ephemeral-errors.ts

@@ -27,7 +27,7 @@ export const postEphemeralErrors = async(
 
           let errorMessage = reason;
           if (resDataMessage != null) {
-            errorMessage += ` (${resDataMessage})`;
+            errorMessage += `\n  Cause: ${resDataMessage}`;
           }
 
           return generateMarkdownSectionBlock(errorMessage);

+ 1 - 0
packages/slackbot-proxy/.env.development

@@ -1,3 +1,4 @@
+SERVER_URI=http://localhost:8080
 TYPEORM_CONNECTION=mysql
 TYPEORM_HOST=mysql
 TYPEORM_DATABASE=growi-slackbot-proxy

+ 23 - 7
packages/slackbot-proxy/src/controllers/growi-to-slack.ts

@@ -34,6 +34,19 @@ export class GrowiToSlackCtrl {
   @Inject()
   orderRepository: OrderRepository;
 
+  async requestToGrowi(growiUrl:string, proxyAccessToken:string):Promise<void> {
+    const url = new URL('/_api/v3/slack-integration/proxied/commands', growiUrl);
+    await axios.post(url.toString(), {
+      type: 'url_verification',
+      challenge: 'this_is_my_challenge_token',
+    },
+    {
+      headers: {
+        'x-growi-ptog-tokens': proxyAccessToken,
+      },
+    });
+  }
+
   @Get('/connection-status')
   @UseBefore(verifyGrowiToSlackRequest)
   async getConnectionStatuses(@Req() req: GrowiReq, @Res() res: Res): Promise<void|string|Res|WebAPICallResult> {
@@ -84,6 +97,13 @@ export class GrowiToSlackCtrl {
         return res.status(400).send({ message: 'installation is invalid' });
       }
 
+      try {
+        await this.requestToGrowi(relation.growiUri, relation.tokenPtoG);
+      }
+      catch (err) {
+        logger.error(err);
+        return res.status(400).send({ message: `failed to request to GROWI. err: ${err.message}` });
+      }
       await testToSlack(token);
       return res.send({ relation });
     }
@@ -91,7 +111,7 @@ export class GrowiToSlackCtrl {
     // retrieve latest Order with Installation
     const order = await this.orderRepository.createQueryBuilder('order')
       .orderBy('order.createdAt', 'DESC')
-      .where('growiAccessToken = :token', { token: tokenGtoP })
+      .where('proxyAccessToken = :token', { token: tokenGtoP })
       .leftJoinAndSelect('order.installation', 'installation')
       .getOne();
 
@@ -101,15 +121,11 @@ export class GrowiToSlackCtrl {
 
     // Access the GROWI URL saved in the Order record and check if the GtoP token is valid.
     try {
-      const url = new URL('/_api/v3/slack-integration/proxied/commands', order.growiUrl);
-      await axios.post(url.toString(), {
-        type: 'url_verification',
-        tokenPtoG: order.growiAccessToken,
-        challenge: 'this_is_my_challenge_token',
-      });
+      await this.requestToGrowi(order.growiUrl, order.proxyAccessToken);
     }
     catch (err) {
       logger.error(err);
+      return res.status(400).send({ message: `failed to request to GROWI. err: ${err.message}` });
     }
 
     logger.debug('order found', order);

+ 4 - 1
packages/slackbot-proxy/src/controllers/slack.ts

@@ -170,7 +170,10 @@ export class SlackCtrl {
       const url = new URL('/_api/v3/slack-integration/proxied/interactions', relation.growiUri);
       return axios.post(url.toString(), {
         ...body,
-        tokenPtoG: relation.tokenPtoG,
+      }, {
+        headers: {
+          'x-growi-ptog-tokens': relation.tokenPtoG,
+        },
       });
     });
 

+ 10 - 8
packages/slackbot-proxy/src/entities/order.ts

@@ -21,18 +21,20 @@ export class Order {
   @Column({ nullable: true, default: false })
   isCompleted?: boolean;
 
-  @Column({ nullable: true })
-  growiUrl?: string;
+  @Column()
+  growiUrl: string;
 
-  @Column({ nullable: true })
-  growiAccessToken?: string;
+  @Column()
+  growiAccessToken: string;
 
-  @Column({ nullable: true })
-  proxyAccessToken?: string;
+  @Column()
+  proxyAccessToken: string;
 
   isExpired():boolean {
-    // TODO GW-5555 implement this
-    return false;
+    const now = Date.now();
+    const expiredAt = this.createdAt.getTime() + 600000;
+
+    return expiredAt < now;
   }
 
 }

+ 4 - 4
resource/locales/en_US/admin/admin.json

@@ -295,9 +295,9 @@
       "generate": "Generate"
     },
     "delete": "Delete",
-    "cooperation_procedure": "Cooperation procedure",
-    "official_bot_settings": "Official bot Settings",
+    "integration_procedure": "Integration Procedure",
     "custom_bot_without_proxy_settings": "Custom Bot without proxy Settings",
+    "official_bot_settings": "Official bot Settings",
     "reset": "Reset",
     "reset_all_settings": "Reset all settings",
     "delete_slackbot_settings": "Reset Slack Bot settings",
@@ -331,12 +331,12 @@
       "send_message_to_slack_work_space": "Send message to Slack work space.",
       "add_slack_workspace": "Add a Slack Workspace"
     },
-    "custom_bot_without_proxy_integration": "Custom bot without proxy integration",
+    "custom_bot_without_proxy_integration": "Custom Bot Without Proxy Integration",
     "integration_sentence": {
       "integration_is_not_complete": "Integration is not complete.<br>Proceed with the following integration procedure.",
       "integration_successful": "Integration successful"
     },
-    "custom_bot_with_proxy_integration": "Custom bot with proxy integration",
+    "custom_bot_with_proxy_integration": "Custom Bot With Proxy Integration",
     "official_bot_integration": "Official bot integration"
   },
   "user_management": {

+ 2 - 2
resource/locales/ja_JP/admin/admin.json

@@ -293,7 +293,7 @@
       "generate": "発行"
     },
     "delete": "削除",
-    "cooperation_procedure": "連携手順",
+    "integration_procedure": "連携手順",
     "custom_bot_without_proxy_settings": "Custom Bot (Without-Proxy) 設定",
     "reset": "リセット",
     "reset_all_settings": "全ての設定をリセット",
@@ -301,7 +301,7 @@
     "slackbot_settings_notice": "リセットします",
     "accordion": {
       "create_bot": "Bot を作成する",
-      "how_to_create_a_bot": "作成方法はこちら",
+      "how_to_create_a_bot": "作成手順はこちら",
       "how_to_install": "インストール方法はこちら",
       "install_bot_to_slack": "Bot を Slack にインストールする",
       "install_now": "今すぐインストール",

+ 1 - 1
resource/locales/zh_CN/admin/admin.json

@@ -303,7 +303,7 @@
       "generate": "生成"
     },
     "delete": "取消",
-    "cooperation_procedure": "协作程序",
+    "integration_procedure": "协作程序",
     "custom_bot_without_proxy_settings": "Custom Bot (Without-Proxy) 设置",
     "reset":"重置",
     "reset_all_settings": "重置所有设置",

+ 40 - 7
src/client/js/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx

@@ -14,12 +14,14 @@ import DeleteSlackBotSettingsModal from './DeleteSlackBotSettingsModal';
 const logger = loggerFactory('growi:SlackBotSettings');
 
 const CustomBotWithProxySettings = (props) => {
-  // eslint-disable-next-line no-unused-vars
   const { appContainer } = props;
   const [isDeleteConfirmModalShown, setIsDeleteConfirmModalShown] = useState(false);
   const [proxyUri, setProxyUri] = useState(null);
 
   const { t } = useTranslation();
+  // TODO: Multiple accordion logic
+  const [tokenPtoG, setTokenPtoG] = useState(null);
+  const [tokenGtoP, setTokenGtoP] = useState(null);
 
   const retrieveProxyUri = useCallback(async() => {
     try {
@@ -37,7 +39,6 @@ const CustomBotWithProxySettings = (props) => {
     retrieveProxyUri();
   }, [retrieveProxyUri]);
 
-
   // TODO: Multiple accordion logic
   const [accordionComponentsCount, setAccordionComponentsCount] = useState(0);
   const addAccordionHandler = () => {
@@ -52,6 +53,31 @@ const CustomBotWithProxySettings = (props) => {
     );
   };
 
+  const discardTokenHandler = async() => {
+    try {
+      await appContainer.apiv3.delete('/slack-integration-settings/slack-app-integration', { tokenGtoP, tokenPtoG });
+      setTokenGtoP(null);
+      setTokenPtoG(null);
+    }
+    catch (err) {
+      toastError(err);
+      logger(err);
+    }
+  };
+
+  const generateTokenHandler = async() => {
+    try {
+      const { data: { tokenGtoP, tokenPtoG } } = await appContainer.apiv3.put('/slack-integration-settings/access-tokens');
+      setTokenGtoP(tokenGtoP);
+      setTokenPtoG(tokenPtoG);
+    }
+    catch (err) {
+      toastError(err);
+      logger(err);
+    }
+
+  };
+
   const deleteSlackSettingsHandler = async() => {
     try {
       // TODO imple delete PtoG and GtoP Token at GW 5861
@@ -116,12 +142,13 @@ const CustomBotWithProxySettings = (props) => {
         </div>
       </div>
 
-      <h2 className="admin-setting-header">{t('admin:slack_integration.cooperation_procedure')}</h2>
+      <h2 className="admin-setting-header">{t('admin:slack_integration.integration_procedure')}</h2>
       <div className="mx-3">
 
-        {/* // TODO: Multiple accordion logic */}
+        {/* TODO: Multiple accordion logic */}
+        {/* TODO: Undefined key fix */}
         {Array(...Array(accordionComponentsCount)).map(i => (
-          <>
+          <React.Fragment key={i}>
             <div className="d-flex justify-content-end">
               <button
                 className="my-3 btn btn-outline-danger"
@@ -132,8 +159,14 @@ const CustomBotWithProxySettings = (props) => {
                 {t('admin:slack_integration.delete')}
               </button>
             </div>
-            <WithProxyAccordions botType="customBotWithProxy" key={i} />
-          </>
+            <WithProxyAccordions
+              botType="customBotWithProxy"
+              discardTokenHandler={discardTokenHandler}
+              generateTokenHandler={generateTokenHandler}
+              tokenPtoG={tokenPtoG}
+              tokenGtoP={tokenGtoP}
+            />
+          </React.Fragment>
         ))}
 
         {/* TODO: Disable button when integration is incomplete */}

+ 1 - 1
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx

@@ -37,7 +37,7 @@ const CustomBotWithoutProxySettings = (props) => {
         isIntegrationSuccess={isIntegrationSuccess}
       />
 
-      <h2 className="admin-setting-header">{t('admin:slack_integration.custom_bot_without_proxy_settings')}</h2>
+      <h2 className="admin-setting-header">{t('admin:slack_integration.integration_procedure')}</h2>
 
       {(props.slackSigningSecret || props.slackBotToken) && (
       <button

+ 101 - 63
src/client/js/components/Admin/SlackIntegration/WithProxyAccordions.jsx

@@ -8,7 +8,7 @@ import AppContainer from '../../../services/AppContainer';
 import Accordion from '../Common/Accordion';
 
 
-export const BotCreateProcess = () => {
+const BotCreateProcess = () => {
   const { t } = useTranslation();
   return (
     <div className="my-5 d-flex flex-column align-items-center">
@@ -29,7 +29,7 @@ export const BotCreateProcess = () => {
   );
 };
 
-export const BotInstallProcess = () => {
+const BotInstallProcess = () => {
   const { t } = useTranslation();
   return (
     <div className="my-5 d-flex flex-column align-items-center">
@@ -51,7 +51,7 @@ export const BotInstallProcess = () => {
   );
 };
 
-export const RegisteringProxyUrlProcess = () => {
+const RegisteringProxyUrlProcess = () => {
   const { t } = useTranslation();
   return (
     <div className="container w-75 py-5">
@@ -70,9 +70,21 @@ export const RegisteringProxyUrlProcess = () => {
   );
 };
 
-export const GenelatingTokensAndRegisteringProxyServiceProcess = withUnstatedContainers((props) => {
+const GeneratingTokensAndRegisteringProxyServiceProcess = withUnstatedContainers((props) => {
   const { t } = useTranslation();
-  const growiUrl = props.appContainer.config.crowi.url;
+
+  const generateTokenHandler = () => {
+    if (props.generateTokenHandler != null) {
+      props.generateTokenHandler();
+    }
+  };
+
+  const discardTokenHandler = () => {
+    if (props.discardTokenHandler != null) {
+      props.discardTokenHandler();
+    }
+  };
+
   return (
     <div className="py-4 px-5">
       <p className="font-weight-bold">1. {t('admin:slack_integration.accordion.generate_access_token')}</p>
@@ -81,7 +93,7 @@ export const GenelatingTokensAndRegisteringProxyServiceProcess = withUnstatedCon
         <div className="col-md-6">
           <div className="input-group-prepend mx-1">
             {/* TODO: show tokenPtoG GW-5899 */}
-            <input className="form-control" type="text" value="tokenPtoG" readOnly />
+            <input className="form-control" type="text" value={props.tokenPtoG || ''} readOnly />
             <CopyToClipboard text="tokenPtoG" onCopy={() => toastSuccess(t('admin:slack_integration.copied_to_clipboard'))}>
               <div className="btn input-group-text">
                 <i className="fa fa-clipboard mx-1" aria-hidden="true"></i>
@@ -95,7 +107,7 @@ export const GenelatingTokensAndRegisteringProxyServiceProcess = withUnstatedCon
         <div className="col-md-6">
           <div className="input-group-prepend mx-1">
             {/* TODO: show tokenGtoP GW-5899 */}
-            <input className="form-control" type="text" value="tokenGtoP" readOnly />
+            <input className="form-control" type="text" value={props.tokenGtoP || ''} readOnly />
             <CopyToClipboard text="tokenGtoP" onCopy={() => toastSuccess(t('admin:slack_integration.copied_to_clipboard'))}>
               <div className="btn input-group-text">
                 <i className="fa fa-clipboard mx-1" aria-hidden="true"></i>
@@ -107,8 +119,21 @@ export const GenelatingTokensAndRegisteringProxyServiceProcess = withUnstatedCon
 
       <div className="row my-3">
         <div className="mx-auto">
-          <button type="button" className="btn btn-outline-secondary mx-2">{ t('admin:slack_integration.access_token_settings.discard') }</button>
-          <button type="button" className="btn btn-primary mx-2">{ t('admin:slack_integration.access_token_settings.generate') }</button>
+          <button
+            type="button"
+            className="btn btn-outline-secondary mx-2"
+            onClick={discardTokenHandler}
+            disabled={props.tokenGtoP == null || props.tokenPtoG == null}
+          >
+            { t('admin:slack_integration.access_token_settings.discard') }
+          </button>
+          <button
+            type="button"
+            className="btn btn-primary mx-2"
+            onClick={generateTokenHandler}
+          >
+            { t('admin:slack_integration.access_token_settings.generate') }
+          </button>
         </div>
       </div>
       <p className="font-weight-bold">2. {t('admin:slack_integration.accordion.register_for_growi_official_bot_proxy_service')}</p>
@@ -131,8 +156,8 @@ export const GenelatingTokensAndRegisteringProxyServiceProcess = withUnstatedCon
             />
             <div className="input-group align-items-center ml-2 mb-3">
               <div className="input-group-prepend mx-1">
-                <input className="form-control" type="text" value={growiUrl} readOnly />
-                <CopyToClipboard text={growiUrl} onCopy={() => toastSuccess(t('admin:slack_integration.copied_to_clipboard'))}>
+                <input className="form-control" type="text" value={props.growiUrl} readOnly />
+                <CopyToClipboard text={props.growiUrl} onCopy={() => toastSuccess(t('admin:slack_integration.copied_to_clipboard'))}>
                   <div className="btn input-group-text">
                     <i className="fa fa-clipboard mx-1" aria-hidden="true"></i>
                   </div>
@@ -156,12 +181,7 @@ export const GenelatingTokensAndRegisteringProxyServiceProcess = withUnstatedCon
   );
 }, [AppContainer]);
 
-GenelatingTokensAndRegisteringProxyServiceProcess.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-};
-
-
-export const TestProcess = () => {
+const TestProcess = () => {
   const { t } = useTranslation();
   const [testChannel, setTestChannel] = useState('');
   /* eslint-disable no-unused-vars */
@@ -239,57 +259,72 @@ export const TestProcess = () => {
   );
 };
 
-const CustomBotCooperationProcedure = {
-  '①': {
-    title: 'create_bot',
-    content: <BotCreateProcess />,
-  },
-  '②': {
-    title: 'install_bot_to_slack',
-    content: <BotInstallProcess />,
-  },
-  '③': {
-    title: 'register_for_growi_official_bot_proxy_service',
-    content: <GenelatingTokensAndRegisteringProxyServiceProcess />,
-  },
-  '④': {
-    title: 'set_proxy_url_on_growi',
-    content: <RegisteringProxyUrlProcess />,
-  },
-  '⑤': {
-    title: 'test_connection',
-    content: <TestProcess />,
-  },
-};
-
-const officialBotCooperationProcedure = {
-  '①': {
-    title: 'install_bot_to_slack',
-    content: <BotInstallProcess />,
-  },
-  '②': {
-    title: 'register_for_growi_official_bot_proxy_service',
-    content: <GenelatingTokensAndRegisteringProxyServiceProcess />,
-  },
-  '③': {
-    title: 'set_proxy_url_on_growi',
-    content: <RegisteringProxyUrlProcess />,
-  },
-  '④': {
-    title: 'test_connection',
-    content: <TestProcess />,
-  },
-};
-
 
 const WithProxyAccordions = (props) => {
   const { t } = useTranslation();
-  const cooperationProcedureMapping = props.botType === 'officialBot' ? officialBotCooperationProcedure : CustomBotCooperationProcedure;
 
+  const officialBotIntegrationProcedure = {
+    '①': {
+      title: 'install_bot_to_slack',
+      content: <BotInstallProcess />,
+    },
+    '②': {
+      title: 'register_for_growi_official_bot_proxy_service',
+      content: <GeneratingTokensAndRegisteringProxyServiceProcess
+        growiUrl={props.appContainer.config.crowi.url}
+        discardTokenHandler={props.discardTokenHandler}
+        generateTokenHandler={props.generateTokenHandler}
+        tokenPtoG={props.tokenPtoG}
+        tokenGtoP={props.tokenGtoP}
+      />,
+    },
+    '③': {
+      title: 'set_proxy_url_on_growi',
+      content: <RegisteringProxyUrlProcess />,
+    },
+    '④': {
+      title: 'test_connection',
+      content: <TestProcess />,
+    },
+  };
+
+  const CustomBotIntegrationProcedure = {
+    '①': {
+      title: 'create_bot',
+      content: <BotCreateProcess />,
+    },
+    '②': {
+      title: 'install_bot_to_slack',
+      content: <BotInstallProcess />,
+    },
+    '③': {
+      title: 'register_for_growi_official_bot_proxy_service',
+      content: <GeneratingTokensAndRegisteringProxyServiceProcess
+        growiUrl={props.appContainer.config.crowi.url}
+        discardTokenHandler={props.discardTokenHandler}
+        generateTokenHandler={props.generateTokenHandler}
+        tokenPtoG={props.tokenPtoG}
+        tokenGtoP={props.tokenGtoP}
+      />,
+    },
+    '④': {
+      title: 'set_proxy_url_on_growi',
+      content: <RegisteringProxyUrlProcess />,
+    },
+    '⑤': {
+      title: 'test_connection',
+      content: <TestProcess />,
+    },
+  };
+
+  const integrationProcedureMapping = props.botType === 'officialBot' ? officialBotIntegrationProcedure : CustomBotIntegrationProcedure;
 
   return (
-    <div className="card border-0 rounded-lg shadow overflow-hidden">
-      {Object.entries(cooperationProcedureMapping).map(([key, value]) => {
+    <div
+      className="card border-0 rounded-lg shadow overflow-hidden"
+    >
+      {Object.entries(integrationProcedureMapping).map(([key, value]) => {
+
         return (
           <Accordion
             title={<><span className="mr-2">{key}</span>{t(`admin:slack_integration.accordion.${value.title}`)}</>}
@@ -307,11 +342,14 @@ const WithProxyAccordions = (props) => {
 /**
  * Wrapper component for using unstated
  */
-
 const OfficialBotSettingsAccordionsWrapper = withUnstatedContainers(WithProxyAccordions, [AppContainer]);
 WithProxyAccordions.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   botType: PropTypes.string.isRequired,
+  discardTokenHandler: PropTypes.func,
+  generateTokenHandler: PropTypes.func,
+  tokenPtoG: PropTypes.string,
+  tokenGtoP: PropTypes.string,
 };
 
 export default OfficialBotSettingsAccordionsWrapper;

+ 7 - 6
src/server/routes/apiv3/slack-integration-settings.js

@@ -1,6 +1,6 @@
 const mongoose = require('mongoose');
 const express = require('express');
-const { body } = require('express-validator');
+const { body, query } = require('express-validator');
 const axios = require('axios');
 const urljoin = require('url-join');
 const loggerFactory = require('@alias/logger');
@@ -9,7 +9,7 @@ const { getConnectionStatuses, testToSlack, generateWebClient } = require('@grow
 
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
-const logger = loggerFactory('growi:routes:apiv3:notification-setting');
+const logger = loggerFactory('growi:routes:apiv3:slack-integration-settings');
 
 const router = express.Router();
 
@@ -56,10 +56,10 @@ module.exports = (crowi) => {
         .isIn(['officialBot', 'customBotWithoutProxy', 'customBotWithProxy']),
     ],
     AccessTokens: [
-      body('tokenGtoP').trim().not().isEmpty()
+      query('tokenGtoP').trim().not().isEmpty()
         .isString()
         .isLength({ min: 1 }),
-      body('tokenPtoG').trim().not().isEmpty()
+      query('tokenPtoG').trim().not().isEmpty()
         .isString()
         .isLength({ min: 1 }),
     ],
@@ -404,9 +404,10 @@ module.exports = (crowi) => {
    */
   router.delete('/slack-app-integration', validator.AccessTokens, apiV3FormValidator, async(req, res) => {
     const SlackAppIntegration = mongoose.model('SlackAppIntegration');
-    const { tokenGtoP, tokenPtoG } = req.body;
+    const { tokenGtoP, tokenPtoG } = req.query;
     try {
-      await SlackAppIntegration.findOneAndDelete({ tokenGtoP, tokenPtoG });
+      const response = await SlackAppIntegration.findOneAndDelete({ tokenGtoP, tokenPtoG });
+      return res.apiv3({ response });
     }
     catch (error) {
       const msg = 'Error occured in deleting access token for slack app tokens';

+ 15 - 7
src/server/routes/apiv3/slack-integration.js

@@ -1,4 +1,5 @@
 const express = require('express');
+const mongoose = require('mongoose');
 
 const loggerFactory = require('@alias/logger');
 
@@ -6,6 +7,7 @@ const { verifySlackRequest } = require('@growi/slack');
 
 const logger = loggerFactory('growi:routes:apiv3:slack-integration');
 const router = express.Router();
+const SlackAppIntegration = mongoose.model('SlackAppIntegration');
 
 module.exports = (crowi) => {
   this.app = crowi.express;
@@ -13,19 +15,25 @@ module.exports = (crowi) => {
   const { configManager } = crowi;
 
   // Check if the access token is correct
-  function verifyAccessTokenFromProxy(req, res, next) {
-    const { body } = req;
-    const { tokenPtoG } = body;
+  async function verifyAccessTokenFromProxy(req, res, next) {
+    const tokenPtoG = req.headers['x-growi-ptog-tokens'];
+
+    if (tokenPtoG == null) {
+      const message = 'The value of header \'x-growi-ptog-tokens\' must not be empty.';
+      logger.warn(message, { body: req.body });
+      return res.status(400).send({ message });
+    }
 
-    const correctToken = configManager.getConfig('crowi', 'slackbot:access-token');
+    const slackAppIntegration = await SlackAppIntegration.estimatedDocumentCount({ tokenPtoG });
 
     logger.debug('verifyAccessTokenFromProxy', {
       tokenPtoG,
-      correctToken,
     });
 
-    if (tokenPtoG == null || tokenPtoG !== correctToken) {
-      return res.status(403).send({ message: 'The access token that identifies the request source is slackbot-proxy is invalid.' });
+    if (slackAppIntegration === 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`?',
+      });
     }
 
     next();