slack-integration-settings.js 29 KB

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