slack-integration-settings.js 30 KB

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