slack-integration-settings.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. const mongoose = require('mongoose');
  2. const express = require('express');
  3. const { body } = require('express-validator');
  4. const axios = require('axios');
  5. const urljoin = require('url-join');
  6. const loggerFactory = require('@alias/logger');
  7. const { getConnectionStatuses } = require('@growi/slack');
  8. const ErrorV3 = require('../../models/vo/error-apiv3');
  9. const logger = loggerFactory('growi:routes:apiv3:notification-setting');
  10. const router = express.Router();
  11. /**
  12. * @swagger
  13. * tags:
  14. * name: SlackIntegrationSettings
  15. */
  16. /**
  17. * @swagger
  18. *
  19. * components:
  20. * schemas:
  21. * CustomBotWithoutProxy:
  22. * description: CustomBotWithoutProxy
  23. * type: object
  24. * properties:
  25. * slackSigningSecret:
  26. * type: string
  27. * slackBotToken:
  28. * type: string
  29. * currentBotType:
  30. * type: string
  31. * BotType:
  32. * description: CustomBotWithoutProxy
  33. * properties:
  34. * currentBotType:
  35. * type: string
  36. * SlackIntegration:
  37. * description: SlackIntegration
  38. * type: object
  39. * properties:
  40. * currentBotType:
  41. * type: string
  42. */
  43. module.exports = (crowi) => {
  44. const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
  45. const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  46. const adminRequired = require('../../middlewares/admin-required')(crowi);
  47. const csrf = require('../../middlewares/csrf')(crowi);
  48. const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
  49. const SlackAppIntegration = mongoose.model('SlackAppIntegration');
  50. const validator = {
  51. BotType: [
  52. body('currentBotType').isString(),
  53. ],
  54. SlackIntegration: [
  55. body('currentBotType')
  56. .isIn(['officialBot', 'customBotWithoutProxy', 'customBotWithProxy']),
  57. ],
  58. NotificationTestToSlackWorkSpace: [
  59. body('channel').trim().not().isEmpty()
  60. .isString(),
  61. ],
  62. };
  63. async function resetAllBotSettings() {
  64. const params = {
  65. 'slackbot:currentBotType': null,
  66. 'slackbot:signingSecret': null,
  67. 'slackbot:token': null,
  68. };
  69. const { configManager } = crowi;
  70. // update config without publishing S2sMessage
  71. return configManager.updateConfigsInTheSameNamespace('crowi', params, true);
  72. }
  73. async function updateSlackBotSettings(params) {
  74. const { configManager } = crowi;
  75. // update config without publishing S2sMessage
  76. return configManager.updateConfigsInTheSameNamespace('crowi', params, true);
  77. }
  78. async function getConnectionStatusesFromProxy(tokens) {
  79. const csv = tokens.join(',');
  80. const proxyUri = crowi.configManager.getConfig('crowi', 'slackbot:serverUri');
  81. const result = await axios.get(urljoin(proxyUri, '/g2s/connection-status'), {
  82. headers: {
  83. 'x-growi-gtop-tokens': csv,
  84. },
  85. });
  86. return result.data;
  87. }
  88. /**
  89. * @swagger
  90. *
  91. * /slack-integration-settings/:
  92. * get:
  93. * tags: [SlackBotSettingParams]
  94. * operationId: getSlackBotSettingParams
  95. * summary: get /slack-integration
  96. * description: Get current settings and connection statuses.
  97. * responses:
  98. * 200:
  99. * description: Succeeded to get info.
  100. */
  101. router.get('/', accessTokenParser, loginRequiredStrictly, adminRequired, async(req, res) => {
  102. const { configManager } = crowi;
  103. const currentBotType = configManager.getConfig('crowi', 'slackbot:currentBotType');
  104. // retrieve settings
  105. const settings = {};
  106. if (currentBotType === 'customBotWithoutProxy') {
  107. settings.slackSigningSecretEnvVars = configManager.getConfigFromEnvVars('crowi', 'slackbot:signingSecret');
  108. settings.slackBotTokenEnvVars = configManager.getConfigFromEnvVars('crowi', 'slackbot:token');
  109. settings.slackSigningSecret = configManager.getConfig('crowi', 'slackbot:signingSecret');
  110. settings.slackBotToken = configManager.getConfig('crowi', 'slackbot:token');
  111. }
  112. else {
  113. settings.proxyUri = crowi.configManager.getConfig('crowi', 'slackbot:serverUri');
  114. settings.proxyUriEnvVars = configManager.getConfigFromEnvVars('crowi', 'slackbot:serverUri');
  115. }
  116. // retrieve connection statuses
  117. let connectionStatuses;
  118. if (currentBotType == null) {
  119. // TODO imple null action
  120. }
  121. else if (currentBotType === 'customBotWithoutProxy') {
  122. const token = settings.slackBotToken;
  123. // check the token is not null
  124. if (token != null) {
  125. try {
  126. connectionStatuses = await getConnectionStatuses([token]);
  127. }
  128. catch (error) {
  129. const msg = 'Error occured in getting connection statuses';
  130. logger.error('Error', error);
  131. return res.apiv3Err(new ErrorV3(msg, 'get-connection-failed'), 500);
  132. }
  133. }
  134. }
  135. else {
  136. const proxyUri = settings.proxyUri;
  137. if (proxyUri != null) {
  138. try {
  139. const slackAppIntegrations = await SlackAppIntegration.find();
  140. settings.slackAppIntegrations = slackAppIntegrations;
  141. if (slackAppIntegrations.length > 0) {
  142. const tokenGtoPs = slackAppIntegrations.map(slackAppIntegration => slackAppIntegration.tokenGtoP);
  143. connectionStatuses = (await getConnectionStatusesFromProxy(tokenGtoPs)).connectionStatuses;
  144. }
  145. }
  146. catch (error) {
  147. const msg = 'Error occured in getting connection statuses';
  148. logger.error('Error', error);
  149. return res.apiv3Err(new ErrorV3(msg, 'get-connection-failed'), 500);
  150. }
  151. }
  152. }
  153. return res.apiv3({ currentBotType, settings, connectionStatuses });
  154. });
  155. /**
  156. * @swagger
  157. *
  158. * /slack-integration-settings/:
  159. * put:
  160. * tags: [SlackIntegration]
  161. * operationId: putSlackIntegration
  162. * summary: put /slack-integration
  163. * description: Put SlackIntegration setting.
  164. * requestBody:
  165. * required: true
  166. * content:
  167. * application/json:
  168. * schema:
  169. * $ref: '#/components/schemas/SlackIntegration'
  170. * responses:
  171. * 200:
  172. * description: Succeeded to put Slack Integration setting.
  173. */
  174. router.put('/', accessTokenParser, loginRequiredStrictly, adminRequired, csrf, validator.SlackIntegration, apiV3FormValidator, async(req, res) => {
  175. const { currentBotType } = req.body;
  176. const requestParams = {
  177. 'slackbot:currentBotType': currentBotType,
  178. };
  179. try {
  180. await updateSlackBotSettings(requestParams);
  181. crowi.slackBotService.publishUpdatedMessage();
  182. const slackIntegrationSettingsParams = {
  183. currentBotType: crowi.configManager.getConfig('crowi', 'slackbot:currentBotType'),
  184. };
  185. return res.apiv3({ slackIntegrationSettingsParams });
  186. }
  187. catch (error) {
  188. const msg = 'Error occured in updating Slack bot setting';
  189. logger.error('Error', error);
  190. return res.apiv3Err(new ErrorV3(msg, 'update-SlackIntegrationSetting-failed'), 500);
  191. }
  192. });
  193. /**
  194. * @swagger
  195. *
  196. * /slack-integration-settings/bot-type/:
  197. * put:
  198. * tags: [botType]
  199. * operationId: putBotType
  200. * summary: /slack-integration/bot-type
  201. * description: Put botType setting.
  202. * requestBody:
  203. * required: true
  204. * content:
  205. * application/json:
  206. * schema:
  207. * $ref: '#/components/schemas/BotType'
  208. * responses:
  209. * 200:
  210. * description: Succeeded to put botType setting.
  211. */
  212. router.put('/bot-type', accessTokenParser, loginRequiredStrictly, adminRequired, csrf, validator.BotType, apiV3FormValidator, async(req, res) => {
  213. const { currentBotType } = req.body;
  214. await resetAllBotSettings();
  215. const requestParams = { 'slackbot:currentBotType': currentBotType };
  216. try {
  217. await updateSlackBotSettings(requestParams);
  218. crowi.slackBotService.publishUpdatedMessage();
  219. // TODO Impl to delete AccessToken both of Proxy and GROWI when botType changes.
  220. const slackBotTypeParam = { slackBotType: crowi.configManager.getConfig('crowi', 'slackbot:currentBotType') };
  221. return res.apiv3({ slackBotTypeParam });
  222. }
  223. catch (error) {
  224. const msg = 'Error occured in updating Custom bot setting';
  225. logger.error('Error', error);
  226. return res.apiv3Err(new ErrorV3(msg, 'update-CustomBotSetting-failed'), 500);
  227. }
  228. });
  229. /**
  230. * @swagger
  231. *
  232. * /slack-integration/bot-type/:
  233. * delete:
  234. * tags: [botType]
  235. * operationId: deleteBotType
  236. * summary: /slack-integration/bot-type
  237. * description: Delete botType setting.
  238. * requestBody:
  239. * content:
  240. * application/json:
  241. * schema:
  242. * $ref: '#/components/schemas/BotType'
  243. * responses:
  244. * 200:
  245. * description: Succeeded to delete botType setting.
  246. */
  247. router.delete('/bot-type', accessTokenParser, loginRequiredStrictly, adminRequired, csrf, apiV3FormValidator, async(req, res) => {
  248. await resetAllBotSettings();
  249. const params = { 'slackbot:currentBotType': null };
  250. try {
  251. await updateSlackBotSettings(params);
  252. crowi.slackBotService.publishUpdatedMessage();
  253. // TODO Impl to delete AccessToken both of Proxy and GROWI when botType changes.
  254. const slackBotTypeParam = { slackBotType: crowi.configManager.getConfig('crowi', 'slackbot:currentBotType') };
  255. return res.apiv3({ slackBotTypeParam });
  256. }
  257. catch (error) {
  258. const msg = 'Error occured in updating Custom bot setting';
  259. logger.error('Error', error);
  260. return res.apiv3Err(new ErrorV3(msg, 'update-CustomBotSetting-failed'), 500);
  261. }
  262. });
  263. /**
  264. * @swagger
  265. *
  266. * /slack-integration-settings/without-proxy/update-settings/:
  267. * put:
  268. * tags: [UpdateWithoutProxySettings]
  269. * operationId: putWithoutProxySettings
  270. * summary: update customBotWithoutProxy settings
  271. * description: Update customBotWithoutProxy setting.
  272. * responses:
  273. * 200:
  274. * description: Succeeded to put CustomBotWithoutProxy setting.
  275. */
  276. router.put('/without-proxy/update-settings', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  277. const currentBotType = crowi.configManager.getConfig('crowi', 'slackbot:currentBotType');
  278. if (currentBotType !== 'customBotWithoutProxy') {
  279. const msg = 'Not CustomBotWithoutProxy';
  280. return res.apiv3Err(new ErrorV3(msg, 'not-customBotWithoutProxy'), 400);
  281. }
  282. const { slackSigningSecret, slackBotToken } = req.body;
  283. const requestParams = {
  284. 'slackbot:signingSecret': slackSigningSecret,
  285. 'slackbot:token': slackBotToken,
  286. };
  287. try {
  288. await updateSlackBotSettings(requestParams);
  289. crowi.slackBotService.publishUpdatedMessage();
  290. const customBotWithoutProxySettingParams = {
  291. slackSigningSecret: crowi.configManager.getConfig('crowi', 'slackbot:signingSecret'),
  292. slackBotToken: crowi.configManager.getConfig('crowi', 'slackbot:token'),
  293. };
  294. return res.apiv3({ customBotWithoutProxySettingParams });
  295. }
  296. catch (error) {
  297. const msg = 'Error occured in updating Custom bot setting';
  298. logger.error('Error', error);
  299. return res.apiv3Err(new ErrorV3(msg, 'update-CustomBotSetting-failed'), 500);
  300. }
  301. });
  302. /**
  303. * @swagger
  304. *
  305. * /slack-integration-settings/access-tokens:
  306. * put:
  307. * tags: [SlackIntegration]
  308. * operationId: putAccessTokens
  309. * summary: /slack-integration
  310. * description: Generate accessTokens
  311. * responses:
  312. * 200:
  313. * description: Succeeded to update access tokens for slack
  314. */
  315. router.put('/access-tokens', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  316. let checkTokens;
  317. let tokenGtoP;
  318. let tokenPtoG;
  319. let generateTokens;
  320. do {
  321. generateTokens = SlackAppIntegration.generateAccessToken();
  322. tokenGtoP = generateTokens[0];
  323. tokenPtoG = generateTokens[1];
  324. // eslint-disable-next-line no-await-in-loop
  325. checkTokens = await SlackAppIntegration.findOne({ $or: [{ tokenGtoP }, { tokenPtoG }] });
  326. } while (checkTokens != null);
  327. try {
  328. const slackAppTokens = await SlackAppIntegration.create({ tokenGtoP, tokenPtoG });
  329. return res.apiv3(slackAppTokens, 200);
  330. }
  331. catch (error) {
  332. const msg = 'Error occured in updating access token for slack app tokens';
  333. logger.error('Error', error);
  334. return res.apiv3Err(new ErrorV3(msg, 'update-slackAppTokens-failed'), 500);
  335. }
  336. });
  337. router.put('/proxy-uri', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  338. const { proxyUri } = req.body;
  339. const requestParams = { 'slackbot:serverUri': proxyUri };
  340. try {
  341. await updateSlackBotSettings(requestParams);
  342. crowi.slackBotService.publishUpdatedMessage();
  343. return res.apiv3({});
  344. }
  345. catch (error) {
  346. const msg = 'Error occured in updating Custom bot setting';
  347. logger.error('Error', error);
  348. return res.apiv3Err(new ErrorV3(msg, 'update-CustomBotSetting-failed'), 500);
  349. }
  350. });
  351. return router;
  352. };