slack-integration-settings.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. import { SlackbotType, defaultSupportedSlackEventActions } from '@growi/slack';
  2. import loggerFactory from '~/utils/logger';
  3. const mongoose = require('mongoose');
  4. const express = require('express');
  5. const { body, query, param } = require('express-validator');
  6. const axios = require('axios');
  7. const urljoin = require('url-join');
  8. const {
  9. getConnectionStatus, getConnectionStatuses,
  10. sendSuccessMessage,
  11. defaultSupportedCommandsNameForBroadcastUse, defaultSupportedCommandsNameForSingleUse,
  12. REQUEST_TIMEOUT_FOR_GTOP,
  13. } = require('@growi/slack');
  14. const ErrorV3 = require('../../models/vo/error-apiv3');
  15. const logger = loggerFactory('growi:routes:apiv3:slack-integration-settings');
  16. const router = express.Router();
  17. /**
  18. * @swagger
  19. * tags:
  20. * name: SlackIntegrationSettings
  21. */
  22. /**
  23. * @swagger
  24. *
  25. * components:
  26. * schemas:
  27. * BotType:
  28. * description: BotType
  29. * properties:
  30. * currentBotType:
  31. * type: string
  32. * SlackIntegration:
  33. * description: SlackIntegration
  34. * type: object
  35. * properties:
  36. * currentBotType:
  37. * type: string
  38. */
  39. module.exports = (crowi) => {
  40. const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
  41. const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  42. const adminRequired = require('../../middlewares/admin-required')(crowi);
  43. const csrf = require('../../middlewares/csrf')(crowi);
  44. const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
  45. const SlackAppIntegration = crowi.model('SlackAppIntegration');
  46. const validator = {
  47. botType: [
  48. body('currentBotType').isString(),
  49. ],
  50. slackIntegration: [
  51. body('currentBotType')
  52. .isIn(Object.values(SlackbotType)),
  53. ],
  54. proxyUri: [
  55. body('proxyUri').if(value => value !== '').trim().matches(/^(https?:\/\/)/)
  56. .isURL({ require_tld: false }),
  57. ],
  58. makePrimary: [
  59. param('id').isMongoId().withMessage('id is required'),
  60. ],
  61. updatePermissionsWithoutProxy: [
  62. body('commandPermission').exists(),
  63. body('eventActionsPermission').exists(),
  64. param('id').isMongoId().withMessage('id is required'),
  65. ],
  66. updatePermissionsWithProxy: [
  67. body('permissionsForBroadcastUseCommands').exists(),
  68. body('permissionsForSingleUseCommands').exists(),
  69. body('permissionsForSlackEventActions').exists(),
  70. param('id').isMongoId().withMessage('id is required'),
  71. ],
  72. relationTest: [
  73. param('id').isMongoId(),
  74. body('channel').trim().isString(),
  75. ],
  76. regenerateTokens: [
  77. param('id').isMongoId(),
  78. ],
  79. deleteIntegration: [
  80. param('id').isMongoId(),
  81. ],
  82. slackChannel: [
  83. body('channel').trim().not().isEmpty()
  84. .isString(),
  85. ],
  86. };
  87. async function updateSlackBotSettings(params) {
  88. const { configManager } = crowi;
  89. // update config without publishing S2sMessage
  90. return configManager.updateConfigsInTheSameNamespace('crowi', params, true);
  91. }
  92. async function resetAllBotSettings(initializedType) {
  93. await SlackAppIntegration.deleteMany();
  94. const params = {
  95. 'slackbot:currentBotType': initializedType,
  96. 'slackbot:withoutProxy:signingSecret': null,
  97. 'slackbot:withoutProxy:botToken': null,
  98. 'slackbot:proxyUri': null,
  99. 'slackbot:withoutProxy:commandPermission': null,
  100. 'slackbot:withoutProxy:eventActionsPermission': null,
  101. };
  102. return updateSlackBotSettings(params);
  103. }
  104. async function getConnectionStatusesFromProxy(tokens) {
  105. const csv = tokens.join(',');
  106. const proxyUri = crowi.slackIntegrationService.proxyUriForCurrentType;
  107. const result = await axios.get(urljoin(proxyUri, '/g2s/connection-status'), {
  108. headers: {
  109. 'x-growi-gtop-tokens': csv,
  110. timeout: REQUEST_TIMEOUT_FOR_GTOP,
  111. },
  112. });
  113. return result.data;
  114. }
  115. async function requestToProxyServer(token, method, endpoint, body) {
  116. const proxyUri = crowi.slackIntegrationService.proxyUriForCurrentType;
  117. if (proxyUri == null) {
  118. throw new Error('Proxy URL is not registered');
  119. }
  120. try {
  121. const result = await axios[method](
  122. urljoin(proxyUri, endpoint),
  123. body, {
  124. headers: {
  125. 'x-growi-gtop-tokens': token,
  126. },
  127. timeout: REQUEST_TIMEOUT_FOR_GTOP,
  128. },
  129. );
  130. return result.data;
  131. }
  132. catch (err) {
  133. throw new Error(`Requesting to proxy server failed: ${err.message}`);
  134. }
  135. }
  136. /**
  137. * @swagger
  138. *
  139. * /slack-integration-settings/:
  140. * get:
  141. * tags: [SlackBotSettingParams]
  142. * operationId: getSlackBotSettingParams
  143. * summary: get /slack-integration
  144. * description: Get current settings and connection statuses.
  145. * responses:
  146. * 200:
  147. * description: Succeeded to get info.
  148. */
  149. router.get('/', accessTokenParser, loginRequiredStrictly, adminRequired, async(req, res) => {
  150. const { configManager, slackIntegrationService } = crowi;
  151. const currentBotType = configManager.getConfig('crowi', 'slackbot:currentBotType');
  152. // retrieve settings
  153. const settings = {};
  154. if (currentBotType === SlackbotType.CUSTOM_WITHOUT_PROXY) {
  155. settings.slackSigningSecretEnvVars = configManager.getConfigFromEnvVars('crowi', 'slackbot:withoutProxy:signingSecret');
  156. settings.slackBotTokenEnvVars = configManager.getConfigFromEnvVars('crowi', 'slackbot:withoutProxy:botToken');
  157. settings.slackSigningSecret = configManager.getConfig('crowi', 'slackbot:withoutProxy:signingSecret');
  158. settings.slackBotToken = configManager.getConfig('crowi', 'slackbot:withoutProxy:botToken');
  159. settings.commandPermission = configManager.getConfig('crowi', 'slackbot:withoutProxy:commandPermission');
  160. settings.eventActionsPermission = configManager.getConfig('crowi', 'slackbot:withoutProxy:eventActionsPermission');
  161. }
  162. else {
  163. settings.proxyServerUri = slackIntegrationService.proxyUriForCurrentType;
  164. }
  165. // retrieve connection statuses
  166. let connectionStatuses = {};
  167. let errorMsg;
  168. let errorCode;
  169. if (currentBotType == null) {
  170. // no need to do anything
  171. }
  172. else if (currentBotType === SlackbotType.CUSTOM_WITHOUT_PROXY) {
  173. const token = settings.slackBotToken;
  174. // check the token is not null
  175. if (token != null) {
  176. try {
  177. connectionStatuses = await getConnectionStatuses([token]);
  178. }
  179. catch (e) {
  180. errorMsg = 'Error occured in getting connection statuses';
  181. errorCode = 'get-connection-failed';
  182. logger.error(errorMsg, e);
  183. }
  184. }
  185. }
  186. else {
  187. try {
  188. const slackAppIntegrations = await SlackAppIntegration.find();
  189. settings.slackAppIntegrations = slackAppIntegrations;
  190. }
  191. catch (e) {
  192. errorMsg = 'Error occured in finding SlackAppIntegration entities.';
  193. errorCode = 'get-slackappintegration-failed';
  194. logger.error(errorMsg, e);
  195. }
  196. const proxyServerUri = settings.proxyServerUri;
  197. if (proxyServerUri != null && settings.slackAppIntegrations != null && settings.slackAppIntegrations.length > 0) {
  198. try {
  199. // key: slackAppIntegration.tokenGtoP, value: slackAppIntegration._id
  200. const tokenGtoPToSlackAppIntegrationId = {};
  201. settings.slackAppIntegrations.forEach((slackAppIntegration) => {
  202. tokenGtoPToSlackAppIntegrationId[slackAppIntegration.tokenGtoP] = slackAppIntegration._id;
  203. });
  204. const result = (await getConnectionStatusesFromProxy(Object.keys(tokenGtoPToSlackAppIntegrationId)));
  205. Object.entries(result.connectionStatuses).forEach(([tokenGtoP, connectionStatus]) => {
  206. connectionStatuses[tokenGtoPToSlackAppIntegrationId[tokenGtoP]] = connectionStatus;
  207. });
  208. }
  209. catch (e) {
  210. errorMsg = 'Something went wrong when retrieving information from Proxy Server.';
  211. errorCode = 'test-connection-failed';
  212. logger.error(errorMsg, e);
  213. }
  214. }
  215. }
  216. return res.apiv3({
  217. currentBotType, settings, connectionStatuses, errorMsg, errorCode,
  218. });
  219. });
  220. const handleBotTypeChanging = async(req, res, initializedBotType) => {
  221. await resetAllBotSettings(initializedBotType);
  222. crowi.slackIntegrationService.publishUpdatedMessage();
  223. if (initializedBotType === 'customBotWithoutProxy') {
  224. // set without-proxy command permissions at bot type changing
  225. const commandPermission = {};
  226. [...defaultSupportedCommandsNameForBroadcastUse, ...defaultSupportedCommandsNameForSingleUse].forEach((commandName) => {
  227. commandPermission[commandName] = true;
  228. });
  229. // default event actions permission value
  230. const eventActionsPermission = {};
  231. defaultSupportedSlackEventActions.forEach((action) => {
  232. eventActionsPermission[action] = false;
  233. });
  234. const params = {
  235. 'slackbot:withoutProxy:commandPermission': commandPermission,
  236. 'slackbot:withoutProxy:eventActionsPermission': eventActionsPermission,
  237. };
  238. try {
  239. await updateSlackBotSettings(params);
  240. crowi.slackIntegrationService.publishUpdatedMessage();
  241. }
  242. catch (error) {
  243. const msg = 'Error occured in updating command permission settigns';
  244. logger.error('Error', error);
  245. return res.apiv3Err(new ErrorV3(msg, 'update-CustomBotSetting-failed'), 500);
  246. }
  247. }
  248. // TODO Impl to delete AccessToken both of Proxy and GROWI when botType changes.
  249. const slackBotTypeParam = { slackBotType: crowi.configManager.getConfig('crowi', 'slackbot:currentBotType') };
  250. return res.apiv3({ slackBotTypeParam });
  251. };
  252. /**
  253. * @swagger
  254. *
  255. * /slack-integration-settings/bot-type/:
  256. * put:
  257. * tags: [botType]
  258. * operationId: putBotType
  259. * summary: /slack-integration/bot-type
  260. * description: Put botType setting.
  261. * requestBody:
  262. * required: true
  263. * content:
  264. * application/json:
  265. * schema:
  266. * $ref: '#/components/schemas/BotType'
  267. * responses:
  268. * 200:
  269. * description: Succeeded to put botType setting.
  270. */
  271. router.put('/bot-type', accessTokenParser, loginRequiredStrictly, adminRequired, csrf, validator.botType, apiV3FormValidator, async(req, res) => {
  272. const { currentBotType } = req.body;
  273. if (currentBotType == null) {
  274. return res.apiv3Err(new ErrorV3('The param \'currentBotType\' must be specified.', 'update-CustomBotSetting-failed'), 400);
  275. }
  276. try {
  277. await handleBotTypeChanging(req, res, currentBotType);
  278. }
  279. catch (error) {
  280. const msg = 'Error occured in updating Custom bot setting';
  281. logger.error('Error', error);
  282. return res.apiv3Err(new ErrorV3(msg, 'update-CustomBotSetting-failed'), 500);
  283. }
  284. });
  285. /**
  286. * @swagger
  287. *
  288. * /slack-integration/bot-type/:
  289. * delete:
  290. * tags: [botType]
  291. * operationId: deleteBotType
  292. * summary: /slack-integration/bot-type
  293. * description: Delete botType setting.
  294. * requestBody:
  295. * content:
  296. * application/json:
  297. * schema:
  298. * $ref: '#/components/schemas/BotType'
  299. * responses:
  300. * 200:
  301. * description: Succeeded to delete botType setting.
  302. */
  303. router.delete('/bot-type', accessTokenParser, loginRequiredStrictly, adminRequired, csrf, apiV3FormValidator, async(req, res) => {
  304. try {
  305. await handleBotTypeChanging(req, res, null);
  306. }
  307. catch (error) {
  308. const msg = 'Error occured in resetting all';
  309. logger.error('Error', error);
  310. return res.apiv3Err(new ErrorV3(msg, 'resetting-all-failed'), 500);
  311. }
  312. });
  313. /**
  314. * @swagger
  315. *
  316. * /slack-integration-settings/without-proxy/update-settings/:
  317. * put:
  318. * tags: [UpdateWithoutProxySettings]
  319. * operationId: putWithoutProxySettings
  320. * summary: update customBotWithoutProxy settings
  321. * description: Update customBotWithoutProxy setting.
  322. * responses:
  323. * 200:
  324. * description: Succeeded to put CustomBotWithoutProxy setting.
  325. */
  326. router.put('/without-proxy/update-settings', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  327. const currentBotType = crowi.configManager.getConfig('crowi', 'slackbot:currentBotType');
  328. if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
  329. const msg = 'Not CustomBotWithoutProxy';
  330. return res.apiv3Err(new ErrorV3(msg, 'not-customBotWithoutProxy'), 400);
  331. }
  332. const { slackSigningSecret, slackBotToken } = req.body;
  333. const requestParams = {
  334. 'slackbot:withoutProxy:signingSecret': slackSigningSecret,
  335. 'slackbot:withoutProxy:botToken': slackBotToken,
  336. };
  337. try {
  338. await updateSlackBotSettings(requestParams);
  339. crowi.slackIntegrationService.publishUpdatedMessage();
  340. return res.apiv3();
  341. }
  342. catch (error) {
  343. const msg = 'Error occured in updating Custom bot setting';
  344. logger.error('Error', error);
  345. return res.apiv3Err(new ErrorV3(msg, 'update-CustomBotSetting-failed'), 500);
  346. }
  347. });
  348. /**
  349. * @swagger
  350. *
  351. * /slack-integration-settings/without-proxy/update-permissions/:
  352. * put:
  353. * tags: [UpdateWithoutProxyPermissions]
  354. * operationId: putWithoutProxyPermissions
  355. * summary: update customBotWithoutProxy permissions
  356. * description: Update customBotWithoutProxy permissions.
  357. * responses:
  358. * 200:
  359. * description: Succeeded to put CustomBotWithoutProxy permissions.
  360. */
  361. router.put('/without-proxy/update-permissions', loginRequiredStrictly, adminRequired, csrf, validator.updatePermissionsWithoutProxy, async(req, res) => {
  362. const currentBotType = crowi.configManager.getConfig('crowi', 'slackbot:currentBotType');
  363. if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
  364. const msg = 'Not CustomBotWithoutProxy';
  365. return res.apiv3Err(new ErrorV3(msg, 'not-customBotWithoutProxy'), 400);
  366. }
  367. // TODO: look here 78978
  368. const { commandPermission, eventActionsPermission } = req.body;
  369. const params = {
  370. 'slackbot:withoutProxy:commandPermission': commandPermission,
  371. 'slackbot:withoutProxy:eventActionsPermission': eventActionsPermission,
  372. };
  373. try {
  374. await updateSlackBotSettings(params);
  375. crowi.slackIntegrationService.publishUpdatedMessage();
  376. return res.apiv3();
  377. }
  378. catch (error) {
  379. const msg = 'Error occured in updating command permission settigns';
  380. logger.error('Error', error);
  381. return res.apiv3Err(new ErrorV3(msg, 'update-CustomBotSetting-failed'), 500);
  382. }
  383. });
  384. /**
  385. * @swagger
  386. *
  387. * /slack-integration-settings/slack-app-integrations:
  388. * post:
  389. * tags: [SlackIntegration]
  390. * operationId: putSlackAppIntegrations
  391. * summary: /slack-integration
  392. * description: Generate SlackAppIntegrations
  393. * responses:
  394. * 200:
  395. * description: Succeeded to create slack app integration
  396. */
  397. router.post('/slack-app-integrations', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  398. const SlackAppIntegrationRecordsNum = await SlackAppIntegration.countDocuments();
  399. if (SlackAppIntegrationRecordsNum >= 10) {
  400. const msg = 'Not be able to create more than 10 slack workspace integration settings';
  401. logger.error('Error', msg);
  402. return res.apiv3Err(new ErrorV3(msg, 'create-slackAppIntegeration-failed'), 500);
  403. }
  404. const count = await SlackAppIntegration.count();
  405. const { tokenGtoP, tokenPtoG } = await SlackAppIntegration.generateUniqueAccessTokens();
  406. try {
  407. const initialSupportedCommandsForBroadcastUse = new Map(defaultSupportedCommandsNameForBroadcastUse.map(command => [command, true]));
  408. const initialSupportedCommandsForSingleUse = new Map(defaultSupportedCommandsNameForSingleUse.map(command => [command, true]));
  409. const initialPermissionsForSlackEventActions = new Map(defaultSupportedSlackEventActions.map(action => [action, true]));
  410. const slackAppTokens = await SlackAppIntegration.create({
  411. tokenGtoP,
  412. tokenPtoG,
  413. permissionsForBroadcastUseCommands: initialSupportedCommandsForBroadcastUse,
  414. permissionsForSingleUseCommands: initialSupportedCommandsForSingleUse,
  415. permissionsForSlackEvents: initialPermissionsForSlackEventActions,
  416. isPrimary: count === 0,
  417. });
  418. return res.apiv3(slackAppTokens, 200);
  419. }
  420. catch (error) {
  421. const msg = 'Error occurred during creating slack integration settings procedure';
  422. logger.error('Error', error);
  423. return res.apiv3Err(new ErrorV3(msg, 'creating-slack-integration-settings-procedure-failed'), 500);
  424. }
  425. });
  426. /**
  427. * @swagger
  428. *
  429. * /slack-integration-settings/slack-app-integrations/:id:
  430. * delete:
  431. * tags: [SlackIntegration]
  432. * operationId: deleteAccessTokens
  433. * summary: delete accessTokens
  434. * description: Delete accessTokens
  435. * responses:
  436. * 200:
  437. * description: Succeeded to delete access tokens for slack
  438. */
  439. router.delete('/slack-app-integrations/:id', validator.deleteIntegration, apiV3FormValidator, async(req, res) => {
  440. const { id } = req.params;
  441. try {
  442. const response = await SlackAppIntegration.findOneAndDelete({ _id: id });
  443. // update primary
  444. const countOfPrimary = await SlackAppIntegration.countDocuments({ isPrimary: true });
  445. if (countOfPrimary === 0) {
  446. await SlackAppIntegration.updateOne({}, { isPrimary: true });
  447. }
  448. return res.apiv3({ response });
  449. }
  450. catch (error) {
  451. const msg = 'Error occured in deleting access token for slack app tokens';
  452. logger.error('Error', error);
  453. return res.apiv3Err(new ErrorV3(msg, 'update-slackAppTokens-failed'), 500);
  454. }
  455. });
  456. router.put('/proxy-uri', loginRequiredStrictly, adminRequired, csrf, validator.proxyUri, apiV3FormValidator, async(req, res) => {
  457. const { proxyUri } = req.body;
  458. const requestParams = { 'slackbot:proxyUri': proxyUri };
  459. try {
  460. await updateSlackBotSettings(requestParams);
  461. crowi.slackIntegrationService.publishUpdatedMessage();
  462. return res.apiv3({});
  463. }
  464. catch (error) {
  465. const msg = 'Error occured in updating Custom bot setting';
  466. logger.error('Error', error);
  467. return res.apiv3Err(new ErrorV3(msg, 'delete-SlackAppIntegration-failed'), 500);
  468. }
  469. });
  470. /**
  471. * @swagger
  472. *
  473. * /slack-integration-settings/slack-app-integrations/:id/makeprimary:
  474. * put:
  475. * tags: [SlackIntegration]
  476. * operationId: makePrimary
  477. * summary: /slack-integration
  478. * description: Make SlackAppTokens primary
  479. * responses:
  480. * 200:
  481. * description: Succeeded to make it primary
  482. */
  483. // eslint-disable-next-line max-len
  484. router.put('/slack-app-integrations/:id/make-primary', loginRequiredStrictly, adminRequired, csrf, validator.makePrimary, apiV3FormValidator, async(req, res) => {
  485. const { id } = req.params;
  486. try {
  487. await SlackAppIntegration.bulkWrite([
  488. // unset isPrimary for others
  489. {
  490. updateMany: {
  491. filter: { _id: { $ne: id } },
  492. update: { $unset: { isPrimary: '' } },
  493. },
  494. },
  495. // set primary
  496. {
  497. updateOne: {
  498. filter: { _id: id },
  499. update: { isPrimary: true },
  500. },
  501. },
  502. ]);
  503. return res.apiv3();
  504. }
  505. catch (error) {
  506. const msg = 'Error occurred during making SlackAppIntegration primary';
  507. logger.error('Error', error);
  508. return res.apiv3Err(new ErrorV3(msg, 'making-primary-failed'), 500);
  509. }
  510. });
  511. /**
  512. * @swagger
  513. *
  514. * /slack-integration-settings/slack-app-integrations/:id/regenerate-tokens:
  515. * put:
  516. * tags: [SlackIntegration]
  517. * operationId: putRegenerateTokens
  518. * summary: /slack-integration
  519. * description: Regenerate SlackAppTokens
  520. * responses:
  521. * 200:
  522. * description: Succeeded to regenerate slack app tokens
  523. */
  524. // eslint-disable-next-line max-len
  525. router.put('/slack-app-integrations/:id/regenerate-tokens', loginRequiredStrictly, adminRequired, csrf, validator.regenerateTokens, apiV3FormValidator, async(req, res) => {
  526. const { id } = req.params;
  527. try {
  528. const { tokenGtoP, tokenPtoG } = await SlackAppIntegration.generateUniqueAccessTokens();
  529. const slackAppTokens = await SlackAppIntegration.findByIdAndUpdate(id, { tokenGtoP, tokenPtoG });
  530. return res.apiv3(slackAppTokens, 200);
  531. }
  532. catch (error) {
  533. const msg = 'Error occurred during regenerating slack app tokens';
  534. logger.error('Error', error);
  535. return res.apiv3Err(new ErrorV3(msg, 'regenerating-slackAppTokens-failed'), 500);
  536. }
  537. });
  538. /**
  539. * @swagger
  540. *
  541. * /slack-integration-settings/slack-app-integrations/:id/permissions:
  542. * put:
  543. * tags: [SlackIntegration]
  544. * operationId: putSupportedCommands
  545. * summary: /slack-integration-settings/:id/permissions
  546. * description: update supported commands
  547. * responses:
  548. * 200:
  549. * description: Succeeded to update supported commands
  550. */
  551. // eslint-disable-next-line max-len
  552. router.put('/slack-app-integrations/:id/permissions', loginRequiredStrictly, adminRequired, csrf, validator.updatePermissionsWithProxy, apiV3FormValidator, async(req, res) => {
  553. // TODO: look here 78975
  554. const { permissionsForBroadcastUseCommands, permissionsForSingleUseCommands, permissionsForSlackEventActions } = req.body;
  555. const { id } = req.params;
  556. const updatePermissionsForBroadcastUseCommands = new Map(Object.entries(permissionsForBroadcastUseCommands));
  557. const updatePermissionsForSingleUseCommands = new Map(Object.entries(permissionsForSingleUseCommands));
  558. const newPermissionsForSlackEventActions = new Map(Object.entries(permissionsForSlackEventActions));
  559. try {
  560. const slackAppIntegration = await SlackAppIntegration.findByIdAndUpdate(
  561. id,
  562. {
  563. permissionsForBroadcastUseCommands: updatePermissionsForBroadcastUseCommands,
  564. permissionsForSingleUseCommands: updatePermissionsForSingleUseCommands,
  565. permissionsForSlackEventActions: newPermissionsForSlackEventActions,
  566. },
  567. { new: true },
  568. );
  569. const proxyUri = crowi.slackIntegrationService.proxyUriForCurrentType;
  570. if (proxyUri != null) {
  571. await requestToProxyServer(
  572. slackAppIntegration.tokenGtoP,
  573. 'put',
  574. '/g2s/supported-commands',
  575. {
  576. permissionsForBroadcastUseCommands: slackAppIntegration.permissionsForBroadcastUseCommands,
  577. permissionsForSingleUseCommands: slackAppIntegration.permissionsForSingleUseCommands,
  578. },
  579. );
  580. }
  581. return res.apiv3({});
  582. }
  583. catch (error) {
  584. const msg = `Error occured in updating settings. Cause: ${error.message}`;
  585. logger.error('Error', error);
  586. return res.apiv3Err(new ErrorV3(msg, 'update-permissions-failed'), 500);
  587. }
  588. });
  589. /**
  590. * @swagger
  591. *
  592. * /slack-integration-settings/slack-app-integrations/:id/relation-test:
  593. * post:
  594. * tags: [botType]
  595. * operationId: postRelationTest
  596. * summary: Test relation
  597. * description: Delete botType setting.
  598. * responses:
  599. * 200:
  600. * description: Succeeded to delete botType setting.
  601. */
  602. // eslint-disable-next-line max-len
  603. router.post('/slack-app-integrations/:id/relation-test', loginRequiredStrictly, adminRequired, csrf, validator.relationTest, apiV3FormValidator, async(req, res) => {
  604. const currentBotType = crowi.configManager.getConfig('crowi', 'slackbot:currentBotType');
  605. if (currentBotType === SlackbotType.CUSTOM_WITHOUT_PROXY) {
  606. const msg = 'Not Proxy Type';
  607. return res.apiv3Err(new ErrorV3(msg, 'not-proxy-type'), 400);
  608. }
  609. const proxyUri = crowi.slackIntegrationService.proxyUriForCurrentType;
  610. if (proxyUri == null) {
  611. return res.apiv3Err(new ErrorV3('Proxy URL is null.', 'not-proxy-Uri'), 400);
  612. }
  613. const { id } = req.params;
  614. let slackBotToken;
  615. try {
  616. const slackAppIntegration = await SlackAppIntegration.findOne({ _id: id });
  617. if (slackAppIntegration == null) {
  618. const msg = 'Could not find SlackAppIntegration by id';
  619. return res.apiv3Err(new ErrorV3(msg, 'find-slackAppIntegration-failed'), 400);
  620. }
  621. const result = await requestToProxyServer(
  622. slackAppIntegration.tokenGtoP,
  623. 'post',
  624. '/g2s/relation-test',
  625. {
  626. permissionsForBroadcastUseCommands: slackAppIntegration.permissionsForBroadcastUseCommands,
  627. permissionsForSingleUseCommands: slackAppIntegration.permissionsForSingleUseCommands,
  628. },
  629. );
  630. slackBotToken = result.slackBotToken;
  631. if (slackBotToken == null) {
  632. const msg = 'Could not find slackBotToken by relation';
  633. return res.apiv3Err(new ErrorV3(msg, 'find-slackBotToken-failed'), 400);
  634. }
  635. }
  636. catch (error) {
  637. logger.error('Error', error);
  638. return res.apiv3Err(new ErrorV3(`Error occured while testing. Cause: ${error.message}`, 'test-failed', error.stack));
  639. }
  640. const { channel } = req.body;
  641. const appSiteURL = crowi.configManager.getConfig('crowi', 'app:siteUrl');
  642. try {
  643. await sendSuccessMessage(slackBotToken, channel, appSiteURL);
  644. }
  645. catch (error) {
  646. return res.apiv3Err(new ErrorV3(`Error occured while sending message. Cause: ${error.message}`, 'send-message-failed', error.stack));
  647. }
  648. return res.apiv3();
  649. });
  650. /**
  651. * @swagger
  652. *
  653. * /slack-integration-settings/without-proxy/test:
  654. * post:
  655. * tags: [botType]
  656. * operationId: postTest
  657. * summary: test the connection
  658. * description: Test the connection with slack work space.
  659. * requestBody:
  660. * content:
  661. * application/json:
  662. * schema:
  663. * properties:
  664. * testChannel:
  665. * type: string
  666. * responses:
  667. * 200:
  668. * description: Succeeded to connect to slack work space.
  669. */
  670. router.post('/without-proxy/test', loginRequiredStrictly, adminRequired, csrf, validator.slackChannel, apiV3FormValidator, async(req, res) => {
  671. const currentBotType = crowi.configManager.getConfig('crowi', 'slackbot:currentBotType');
  672. if (currentBotType !== SlackbotType.CUSTOM_WITHOUT_PROXY) {
  673. const msg = 'Select Without Proxy Type';
  674. return res.apiv3Err(new ErrorV3(msg, 'select-not-proxy-type'), 400);
  675. }
  676. const slackBotToken = crowi.configManager.getConfig('crowi', 'slackbot:withoutProxy:botToken');
  677. const status = await getConnectionStatus(slackBotToken);
  678. if (status.error != null) {
  679. return res.apiv3Err(new ErrorV3(`Error occured while getting connection. ${status.error}`, 'send-message-failed'));
  680. }
  681. const { channel } = req.body;
  682. const appSiteURL = crowi.configManager.getConfig('crowi', 'app:siteUrl');
  683. try {
  684. await sendSuccessMessage(slackBotToken, channel, appSiteURL);
  685. }
  686. catch (error) {
  687. return res.apiv3Err(new ErrorV3(`Error occured while sending message. Cause: ${error.message}`, 'send-message-failed', error.stack));
  688. }
  689. return res.apiv3();
  690. });
  691. return router;
  692. };