2
0
Эх сурвалжийг харах

Merge branch 'feat/growi-bot' into feat/create-endpoint-to-save-slack-bot-tokens

zahmis 5 жил өмнө
parent
commit
75b4ff78d7

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

@@ -12,7 +12,7 @@ export * from './interfaces/request-from-slack';
 export * from './models/errors';
 export * from './middlewares/verify-slack-request';
 export * from './utils/block-creater';
-export * from './utils/check-communicable-to-api-server';
+export * from './utils/check-communicable';
 export * from './utils/post-ephemeral-errors';
 export * from './utils/slash-command-parser';
 export * from './utils/webclient-factory';

+ 4 - 0
packages/slack/src/interfaces/connection-status.ts

@@ -0,0 +1,4 @@
+export type ConnectionStatus = {
+  error?: Error,
+  workspaceName?: string,
+}

+ 0 - 10
packages/slack/src/utils/check-communicable-to-api-server.ts

@@ -1,10 +0,0 @@
-import axios, { AxiosError } from 'axios';
-
-export const checkCommunicableToApiServer = async(serverUri = 'https://slack.com/api/'): Promise<void|AxiosError> => {
-  try {
-    await axios.get(serverUri, { maxRedirects: 0 });
-  }
-  catch (err) {
-    return err as AxiosError;
-  }
-};

+ 88 - 0
packages/slack/src/utils/check-communicable.ts

@@ -0,0 +1,88 @@
+import axios, { AxiosError } from 'axios';
+
+import { WebClient } from '@slack/web-api';
+
+import { generateWebClient } from './webclient-factory';
+import { ConnectionStatus } from '../interfaces/connection-status';
+
+/**
+ * Check whether the HTTP server responds or not.
+ *
+ * @param serverUri Server URI to connect
+ * @returns AxiosError when error is occured
+ */
+export const connectToHttpServer = async(serverUri: string): Promise<void|AxiosError> => {
+  try {
+    await axios.get(serverUri, { maxRedirects: 0, timeout: 3000 });
+  }
+  catch (err) {
+    return err as AxiosError;
+  }
+};
+
+/**
+ * Check whether the Slack API server responds or not.
+ *
+ * @returns AxiosError when error is occured
+ */
+export const connectToSlackApiServer = async(): Promise<void|AxiosError> => {
+  return connectToHttpServer('https://slack.com/api/');
+};
+
+/**
+ * Test Slack API
+ * @param client
+ */
+const testSlackApiServer = async(client: WebClient): Promise<void> => {
+  const result = await client.api.test();
+
+  if (!result.ok) {
+    throw new Error(result.error);
+  }
+};
+
+/**
+ * Retrieve Slack workspace name
+ * @param client
+ */
+const retrieveWorkspaceName = async(client: WebClient): Promise<string> => {
+  const result = await client.team.info();
+
+  if (!result.ok) {
+    throw new Error(result.error);
+  }
+
+  return (result as any).team?.name;
+};
+
+/**
+ * Get token string to ConnectionStatus map
+ * @param tokens Array of bot OAuth token
+ * @returns
+ */
+export const getConnectionStatuses = async(tokens: string[]): Promise<Map<string, ConnectionStatus>> => {
+  return tokens
+    .reduce<Promise<Map<string, ConnectionStatus>>>(
+      async(acc, token) => {
+        const client = generateWebClient(token);
+
+        const status: ConnectionStatus = {};
+        try {
+          // try to connect
+          await testSlackApiServer(client);
+          // retrieve workspace name
+          status.workspaceName = await retrieveWorkspaceName(client);
+        }
+        catch (err) {
+          status.error = err;
+        }
+
+        (await acc).set(token, status);
+
+        return acc;
+
+      },
+      // define initial accumulator
+      Promise.resolve(new Map<string, ConnectionStatus>()),
+    );
+};

+ 1 - 1
packages/slackbot-proxy/src/middlewares/authorizer.ts

@@ -30,7 +30,7 @@ export class AuthorizeCommandMiddleware implements IMiddleware {
 
     // extract id from body
     const teamId = body.team_id;
-    const enterpriseId = body.enterprize_id;
+    const enterpriseId = body.enterprise_id;
     const isEnterpriseInstall = body.is_enterprise_install === 'true';
 
     if (teamId == null && enterpriseId == null) {

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

@@ -4,12 +4,16 @@ import PropTypes from 'prop-types';
 import AppContainer from '../../../services/AppContainer';
 import AdminAppContainer from '../../../services/AdminAppContainer';
 import { withUnstatedContainers } from '../../UnstatedUtils';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
 import CustomBotWithProxyIntegrationCard from './CustomBotWithProxyIntegrationCard';
 import CustomBotWithProxySettingsAccordion from './CustomBotWithProxySettingsAccordion';
+import DeleteSlackBotSettingsModal from './DeleteSlackBotSettingsModal';
 
 const CustomBotWithProxySettings = (props) => {
   // eslint-disable-next-line no-unused-vars
   const { appContainer, adminAppContainer } = props;
+  const [isDeleteConfirmModalShown, setIsDeleteConfirmModalShown] = useState(false);
+
   const { t } = useTranslation();
 
   // TODO: Multiple accordion logic
@@ -26,6 +30,19 @@ const CustomBotWithProxySettings = (props) => {
     );
   };
 
+  const deleteSlackSettingsHandler = async() => {
+    try {
+      // TODO imple delete PtoG and GtoP Token at GW 5861
+      await appContainer.apiv3.put('/slack-integration-settings/custom-bot-with-proxy', {
+      });
+      deleteAccordionHandler();
+      toastSuccess('success');
+    }
+    catch (err) {
+      toastError(err);
+    }
+  };
+
   return (
     <>
       <h2 className="admin-setting-header mb-2">{t('admin:slack_integration.custom_bot_with_proxy_integration')}</h2>
@@ -57,7 +74,7 @@ const CustomBotWithProxySettings = (props) => {
               <button
                 className="my-3 btn btn-outline-danger"
                 type="button"
-                onClick={deleteAccordionHandler}
+                onClick={() => setIsDeleteConfirmModalShown(true)}
               >
                 <i className="icon-trash mr-1" />
                 {t('admin:slack_integration.delete')}
@@ -79,6 +96,11 @@ const CustomBotWithProxySettings = (props) => {
           </button>
         </div>
       </div>
+      <DeleteSlackBotSettingsModal
+        isOpen={isDeleteConfirmModalShown}
+        onClose={() => setIsDeleteConfirmModalShown(false)}
+        onClickDeleteButton={deleteSlackSettingsHandler}
+      />
     </>
   );
 };

+ 36 - 26
src/server/routes/apiv3/slack-integration-settings.js

@@ -1,5 +1,7 @@
 const loggerFactory = require('@alias/logger');
 
+const { getConnectionStatuses } = require('@growi/slack');
+
 const logger = loggerFactory('growi:routes:apiv3:notification-setting');
 const express = require('express');
 const { body } = require('express-validator');
@@ -68,7 +70,6 @@ module.exports = (crowi) => {
     return configManager.updateConfigsInTheSameNamespace('crowi', params, true);
   }
 
-
   // eslint-disable-next-line no-unused-vars
   function generateAccessToken(user) {
     const hasher = crypto.createHash('sha512');
@@ -85,35 +86,44 @@ module.exports = (crowi) => {
    *        tags: [SlackBotSettingParams]
    *        operationId: getSlackBotSettingParams
    *        summary: get /slack-integration
-   *        description: Get slackBot setting params.
+   *        description: Get current settings and connection statuses.
    *        responses:
    *          200:
-   *            description: Succeeded to get slackBot setting params.
+   *            description: Succeeded to get info.
    */
   router.get('/', accessTokenParser, loginRequiredStrictly, adminRequired, async(req, res) => {
-    const slackBotSettingParams = {
-      accessToken: crowi.configManager.getConfig('crowi', 'slackbot:access-token'),
-      currentBotType: crowi.configManager.getConfig('crowi', 'slackbot:currentBotType'),
-      // TODO impl when creating official bot
-      officialBotSettings: {
-        // TODO impl this after GW-4939
-        // AccessToken: "tempaccessdatahogehoge",
-      },
-      customBotWithoutProxySettings: {
-        // TODO impl this after GW-4939
-        // AccessToken: "tempaccessdatahogehoge",
-        slackSigningSecretEnvVars: crowi.configManager.getConfigFromEnvVars('crowi', 'slackbot:signingSecret'),
-        slackBotTokenEnvVars: crowi.configManager.getConfigFromEnvVars('crowi', 'slackbot:token'),
-        slackSigningSecret: crowi.configManager.getConfig('crowi', 'slackbot:signingSecret'),
-        slackBotToken: crowi.configManager.getConfig('crowi', 'slackbot:token'),
-      },
-      // TODO imple when creating with proxy
-      customBotWithProxySettings: {
-        // TODO impl this after GW-4939
-        // AccessToken: "tempaccessdatahogehoge",
-      },
-    };
-    return res.apiv3({ slackBotSettingParams });
+    const { configManager } = crowi;
+    const currentBotType = configManager.getConfig('crowi', 'slackbot:currentBotType');
+
+    // retrieve settings
+    const settings = {};
+    if (currentBotType === 'customBotWithoutProxy') {
+      settings.slackSigningSecretEnvVars = configManager.getConfigFromEnvVars('crowi', 'slackbot:signingSecret');
+      settings.slackBotTokenEnvVars = configManager.getConfigFromEnvVars('crowi', 'slackbot:token');
+      settings.slackSigningSecret = configManager.getConfig('crowi', 'slackbot:signingSecret');
+      settings.slackBotToken = configManager.getConfig('crowi', 'slackbot:token');
+    }
+    else {
+      // settings.proxyUriEnvVars = ;
+      // settings.proxyUri = ;
+      // settings.tokenPtoG = ;
+      // settings.tokenGtoP = ;
+    }
+
+    // retrieve connection statuses
+    let connectionStatuses;
+    if (currentBotType === 'customBotWithoutProxy') {
+      const token = settings.slackBotToken;
+      // check the token is not null
+      if (token != null) {
+        connectionStatuses = Object.fromEntries(await getConnectionStatuses([token]));
+      }
+    }
+    else {
+      // connectionStatuses = getConnectionStatusesFromProxy();
+    }
+
+    return res.apiv3({ currentBotType, settings, connectionStatuses });
   });
 
   /**