slack-integration.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. const loggerFactory = require('@alias/logger');
  2. const logger = loggerFactory('growi:routes:apiv3:notification-setting');
  3. const express = require('express');
  4. const { body } = require('express-validator');
  5. const { WebClient } = require('@slack/web-api');
  6. const crypto = require('crypto');
  7. const ErrorV3 = require('../../models/vo/error-apiv3');
  8. const router = express.Router();
  9. /**
  10. * @swagger
  11. * tags:
  12. * name: SlackIntegration
  13. */
  14. /**
  15. * @swagger
  16. *
  17. * components:
  18. * schemas:
  19. * CustomBotWithoutProxy:
  20. * description: CustomBotWithoutProxy
  21. * type: object
  22. * properties:
  23. * slackSigningSecret:
  24. * type: string
  25. * slackBotToken:
  26. * type: string
  27. * currentBotType:
  28. * type: string
  29. * SlackIntegration:
  30. * description: SlackIntegration
  31. * type: object
  32. * properties:
  33. * currentBotType:
  34. * type: string
  35. */
  36. module.exports = (crowi) => {
  37. const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
  38. const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  39. const adminRequired = require('../../middlewares/admin-required')(crowi);
  40. const csrf = require('../../middlewares/csrf')(crowi);
  41. const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
  42. const validator = {
  43. CustomBotWithoutProxy: [
  44. body('slackSigningSecret').isString(),
  45. body('slackBotToken').isString(),
  46. body('currentBotType').isString(),
  47. ],
  48. SlackIntegration: [
  49. body('currentBotType')
  50. .isIn(['official-bot', 'custom-bot-without-proxy', 'custom-bot-with-proxy']),
  51. ],
  52. };
  53. async function updateSlackBotSettings(params) {
  54. const { configManager } = crowi;
  55. // update config without publishing S2sMessage
  56. return configManager.updateConfigsInTheSameNamespace('crowi', params, true);
  57. }
  58. function generateAccessToken(user) {
  59. const hasher = crypto.createHash('sha512');
  60. hasher.update(new Date().getTime() + user._id);
  61. return hasher.digest('base64');
  62. }
  63. /**
  64. * @swagger
  65. *
  66. * /slack-integration/:
  67. * get:
  68. * tags: [SlackBotSettingParams]
  69. * operationId: getSlackBotSettingParams
  70. * summary: get /slack-integration
  71. * description: Get slackBot setting params.
  72. * responses:
  73. * 200:
  74. * description: Succeeded to get slackBot setting params.
  75. */
  76. router.get('/', accessTokenParser, loginRequiredStrictly, adminRequired, async(req, res) => {
  77. const slackBotSettingParams = {
  78. accessToken: crowi.configManager.getConfig('crowi', 'slackbot:accessToken'),
  79. currentBotType: crowi.configManager.getConfig('crowi', 'slackbot:currentBotType'),
  80. // TODO impl when creating official bot
  81. officialBotSettings: {
  82. // TODO impl this after GW-4939
  83. // AccessToken: "tempaccessdatahogehoge",
  84. },
  85. customBotWithoutProxySettings: {
  86. // TODO impl this after GW-4939
  87. // AccessToken: "tempaccessdatahogehoge",
  88. slackSigningSecretEnvVars: crowi.configManager.getConfigFromEnvVars('crowi', 'slackbot:signingSecret'),
  89. slackBotTokenEnvVars: crowi.configManager.getConfigFromEnvVars('crowi', 'slackbot:token'),
  90. slackSigningSecret: crowi.configManager.getConfig('crowi', 'slackbot:signingSecret'),
  91. slackBotToken: crowi.configManager.getConfig('crowi', 'slackbot:token'),
  92. },
  93. // TODO imple when creating with proxy
  94. customBotWithProxySettings: {
  95. // TODO impl this after GW-4939
  96. // AccessToken: "tempaccessdatahogehoge",
  97. },
  98. };
  99. return res.apiv3({ slackBotSettingParams });
  100. });
  101. /**
  102. * @swagger
  103. *
  104. * /slack-integration/:
  105. * put:
  106. * tags: [SlackIntegration]
  107. * operationId: putSlackIntegration
  108. * summary: put /slack-integration
  109. * description: Put SlackIntegration setting.
  110. * requestBody:
  111. * required: true
  112. * content:
  113. * application/json:
  114. * schema:
  115. * $ref: '#/components/schemas/SlackIntegration'
  116. * responses:
  117. * 200:
  118. * description: Succeeded to put Slack Integration setting.
  119. */
  120. router.put('/',
  121. accessTokenParser, loginRequiredStrictly, adminRequired, csrf, validator.SlackIntegration, apiV3FormValidator, async(req, res) => {
  122. const { currentBotType } = req.body;
  123. const requestParams = {
  124. 'slackbot:currentBotType': currentBotType,
  125. };
  126. try {
  127. await updateSlackBotSettings(requestParams);
  128. // initialize bolt service
  129. crowi.boltService.initialize();
  130. crowi.boltService.publishUpdatedMessage();
  131. const slackIntegrationSettingsParams = {
  132. currentBotType: crowi.configManager.getConfig('crowi', 'slackbot:currentBotType'),
  133. };
  134. return res.apiv3({ slackIntegrationSettingsParams });
  135. }
  136. catch (error) {
  137. const msg = 'Error occured in updating Slack bot setting';
  138. logger.error('Error', error);
  139. return res.apiv3Err(new ErrorV3(msg, 'update-SlackIntegrationSetting-failed'));
  140. }
  141. });
  142. /**
  143. * @swagger
  144. *
  145. * /slack-integration/custom-bot-without-proxy/:
  146. * put:
  147. * tags: [CustomBotWithoutProxy]
  148. * operationId: putCustomBotWithoutProxy
  149. * summary: /slack-integration/custom-bot-without-proxy
  150. * description: Put customBotWithoutProxy setting.
  151. * requestBody:
  152. * required: true
  153. * content:
  154. * application/json:
  155. * schema:
  156. * $ref: '#/components/schemas/CustomBotWithoutProxy'
  157. * responses:
  158. * 200:
  159. * description: Succeeded to put CustomBotWithoutProxy setting.
  160. */
  161. router.put('/custom-bot-without-proxy',
  162. accessTokenParser, loginRequiredStrictly, adminRequired, csrf, validator.CustomBotWithoutProxy, apiV3FormValidator, async(req, res) => {
  163. const { slackSigningSecret, slackBotToken, currentBotType } = req.body;
  164. const requestParams = {
  165. 'slackbot:signingSecret': slackSigningSecret,
  166. 'slackbot:token': slackBotToken,
  167. 'slackbot:currentBotType': currentBotType,
  168. };
  169. try {
  170. await updateSlackBotSettings(requestParams);
  171. // initialize bolt service
  172. crowi.boltService.initialize();
  173. crowi.boltService.publishUpdatedMessage();
  174. // TODO Impl to delete AccessToken both of Proxy and GROWI when botType changes.
  175. const customBotWithoutProxySettingParams = {
  176. slackSigningSecret: crowi.configManager.getConfig('crowi', 'slackbot:signingSecret'),
  177. slackBotToken: crowi.configManager.getConfig('crowi', 'slackbot:token'),
  178. slackBotType: crowi.configManager.getConfig('crowi', 'slackbot:currentBotType'),
  179. };
  180. return res.apiv3({ customBotWithoutProxySettingParams });
  181. }
  182. catch (error) {
  183. const msg = 'Error occured in updating Custom bot setting';
  184. logger.error('Error', error);
  185. return res.apiv3Err(new ErrorV3(msg, 'update-CustomBotSetting-failed'), 500);
  186. }
  187. });
  188. /**
  189. * @swagger
  190. *
  191. * /slack-integration/custom-bot-without-proxy/slack-workspace-name:
  192. * get:
  193. * tags: [slackWorkSpaceName]
  194. * operationId: getSlackWorkSpaceName
  195. * summary: Get slack work space name for custom bot without proxy
  196. * description: get slack WS name in custom bot without proxy
  197. * responses:
  198. * 200:
  199. * description: Succeeded to get slack ws name for custom bot without proxy
  200. */
  201. router.get('/custom-bot-without-proxy/slack-workspace-name', async(req, res) => {
  202. // get ws name in custom bot from slackbot token
  203. const slackBotToken = crowi.configManager.getConfig('crowi', 'slackbot:token');
  204. let slackWorkSpaceName = null;
  205. if (slackBotToken != null) {
  206. const web = new WebClient(slackBotToken);
  207. try {
  208. const slackTeamInfo = await web.team.info();
  209. slackWorkSpaceName = slackTeamInfo.team.name;
  210. }
  211. catch (error) {
  212. const msg = 'Error occured in slack_bot_token';
  213. logger.error('Error', msg);
  214. return res.apiv3Err(new ErrorV3(msg, 'get-SlackWorkSpaceName-failed'));
  215. }
  216. }
  217. return res.apiv3({ slackWorkSpaceName });
  218. });
  219. /**
  220. * @swagger
  221. *
  222. * /slack-integration/access-token:
  223. * put:
  224. * tags: [SlackIntegration]
  225. * operationId: getCustomBotSetting
  226. * summary: /slack-integration
  227. * description: Generate accessToken
  228. * responses:
  229. * 200:
  230. * description: Succeeded to update access token for slack
  231. */
  232. router.put('/access-token', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  233. try {
  234. let accessToken = '';
  235. if (req.body.deleteAccessToken !== true) {
  236. accessToken = generateAccessToken(req.user);
  237. }
  238. await updateSlackBotSettings({ 'slackbot:access-token': accessToken });
  239. // initialize bolt service
  240. crowi.boltService.initialize();
  241. crowi.boltService.publishUpdatedMessage();
  242. return res.apiv3({ accessToken });
  243. }
  244. catch (error) {
  245. const msg = 'Error occured in updating access token for access token';
  246. logger.error('Error', error);
  247. return res.apiv3Err(new ErrorV3(msg, 'update-accessToken-failed'), 500);
  248. }
  249. });
  250. /**
  251. * @swagger
  252. *
  253. * /slack-integration/access-token:
  254. * delete:
  255. * tags: [SlackIntegration]
  256. * operationId: deleteAccessTokenForSlackBot
  257. * summary: /slack-integration
  258. * description: Delete accessToken
  259. * responses:
  260. * 200:
  261. * description: Succeeded to delete accessToken
  262. */
  263. router.delete('/access-token', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  264. try {
  265. await updateSlackBotSettings({ 'slackbot:access-token': null });
  266. // initialize bolt service
  267. crowi.boltService.initialize();
  268. crowi.boltService.publishUpdatedMessage();
  269. return res.apiv3({});
  270. }
  271. catch (error) {
  272. const msg = 'Error occured in discard of slackbotAccessToken';
  273. logger.error('Error', error);
  274. return res.apiv3Err(new ErrorV3(msg, 'discard-slackbotAccessToken-failed'), 500);
  275. }
  276. });
  277. return router;
  278. };