slack-integration-settings.js 12 KB

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