slack-integration.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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 crypto = require('crypto');
  6. const { WebClient, LogLevel } = require('@slack/web-api');
  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:access-token'),
  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. isSetupSlackBot: crowi.slackBotService.isSetupSlackBot,
  93. isConnectedToSlack: crowi.slackBotService.isConnectedToSlack,
  94. },
  95. // TODO imple when creating with proxy
  96. customBotWithProxySettings: {
  97. // TODO impl this after GW-4939
  98. // AccessToken: "tempaccessdatahogehoge",
  99. },
  100. };
  101. return res.apiv3({ slackBotSettingParams });
  102. });
  103. /**
  104. * @swagger
  105. *
  106. * /slack-integration/:
  107. * put:
  108. * tags: [SlackIntegration]
  109. * operationId: putSlackIntegration
  110. * summary: put /slack-integration
  111. * description: Put SlackIntegration setting.
  112. * requestBody:
  113. * required: true
  114. * content:
  115. * application/json:
  116. * schema:
  117. * $ref: '#/components/schemas/SlackIntegration'
  118. * responses:
  119. * 200:
  120. * description: Succeeded to put Slack Integration setting.
  121. */
  122. router.put('/',
  123. accessTokenParser, loginRequiredStrictly, adminRequired, csrf, validator.SlackIntegration, apiV3FormValidator, async(req, res) => {
  124. const { currentBotType } = req.body;
  125. const requestParams = {
  126. 'slackbot:currentBotType': currentBotType,
  127. };
  128. try {
  129. await updateSlackBotSettings(requestParams);
  130. // initialize slack service
  131. await crowi.slackBotService.initialize();
  132. crowi.slackBotService.publishUpdatedMessage();
  133. const slackIntegrationSettingsParams = {
  134. currentBotType: crowi.configManager.getConfig('crowi', 'slackbot:currentBotType'),
  135. };
  136. return res.apiv3({ slackIntegrationSettingsParams });
  137. }
  138. catch (error) {
  139. const msg = 'Error occured in updating Slack bot setting';
  140. logger.error('Error', error);
  141. return res.apiv3Err(new ErrorV3(msg, 'update-SlackIntegrationSetting-failed'), 500);
  142. }
  143. });
  144. /**
  145. * @swagger
  146. *
  147. * /slack-integration/custom-bot-without-proxy/:
  148. * put:
  149. * tags: [CustomBotWithoutProxy]
  150. * operationId: putCustomBotWithoutProxy
  151. * summary: /slack-integration/custom-bot-without-proxy
  152. * description: Put customBotWithoutProxy setting.
  153. * requestBody:
  154. * required: true
  155. * content:
  156. * application/json:
  157. * schema:
  158. * $ref: '#/components/schemas/CustomBotWithoutProxy'
  159. * responses:
  160. * 200:
  161. * description: Succeeded to put CustomBotWithoutProxy setting.
  162. */
  163. router.put('/custom-bot-without-proxy',
  164. accessTokenParser, loginRequiredStrictly, adminRequired, csrf, validator.CustomBotWithoutProxy, apiV3FormValidator, async(req, res) => {
  165. const { slackSigningSecret, slackBotToken, currentBotType } = req.body;
  166. const requestParams = {
  167. 'slackbot:signingSecret': slackSigningSecret,
  168. 'slackbot:token': slackBotToken,
  169. 'slackbot:currentBotType': currentBotType,
  170. };
  171. try {
  172. await updateSlackBotSettings(requestParams);
  173. // initialize slack service
  174. await crowi.slackBotService.initialize();
  175. crowi.slackBotService.publishUpdatedMessage();
  176. // TODO Impl to delete AccessToken both of Proxy and GROWI when botType changes.
  177. const customBotWithoutProxySettingParams = {
  178. slackSigningSecret: crowi.configManager.getConfig('crowi', 'slackbot:signingSecret'),
  179. slackBotToken: crowi.configManager.getConfig('crowi', 'slackbot:token'),
  180. slackBotType: crowi.configManager.getConfig('crowi', 'slackbot:currentBotType'),
  181. };
  182. return res.apiv3({ customBotWithoutProxySettingParams });
  183. }
  184. catch (error) {
  185. const msg = 'Error occured in updating Custom bot setting';
  186. logger.error('Error', error);
  187. return res.apiv3Err(new ErrorV3(msg, 'update-CustomBotSetting-failed'), 500);
  188. }
  189. });
  190. /**
  191. * @swagger
  192. *
  193. * /slack-integration/custom-bot-without-proxy/slack-workspace-name:
  194. * get:
  195. * tags: [slackWorkSpaceName]
  196. * operationId: getSlackWorkSpaceName
  197. * summary: Get slack work space name for custom bot without proxy
  198. * description: get slack WS name in custom bot without proxy
  199. * responses:
  200. * 200:
  201. * description: Succeeded to get slack ws name for custom bot without proxy
  202. */
  203. router.get('/custom-bot-without-proxy/slack-workspace-name', loginRequiredStrictly, adminRequired, async(req, res) => {
  204. try {
  205. const slackWorkSpaceName = await crowi.slackBotService.getSlackChannelName();
  206. return res.apiv3({ slackWorkSpaceName });
  207. }
  208. catch (error) {
  209. const msg = 'Error occured in slack_bot_token';
  210. logger.error('Error', error);
  211. return res.apiv3Err(new ErrorV3(msg, 'get-SlackWorkSpaceName-failed'), 500);
  212. }
  213. });
  214. /**
  215. * @swagger
  216. *
  217. * /slack-integration/access-token:
  218. * put:
  219. * tags: [SlackIntegration]
  220. * operationId: getCustomBotSetting
  221. * summary: /slack-integration
  222. * description: Generate accessToken
  223. * responses:
  224. * 200:
  225. * description: Succeeded to update access token for slack
  226. */
  227. router.put('/access-token', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  228. try {
  229. const accessToken = generateAccessToken(req.user);
  230. await updateSlackBotSettings({ 'slackbot:access-token': accessToken });
  231. // initialize slack service
  232. await crowi.slackBotService.initialize();
  233. crowi.slackBotService.publishUpdatedMessage();
  234. return res.apiv3({ accessToken });
  235. }
  236. catch (error) {
  237. const msg = 'Error occured in updating access token for access token';
  238. logger.error('Error', error);
  239. return res.apiv3Err(new ErrorV3(msg, 'update-accessToken-failed'), 500);
  240. }
  241. });
  242. function checkConnectedToSlack(res) {
  243. const isConnectedToSlack = crowi.slackBotService.isConnectedToSlack;
  244. if (!isConnectedToSlack) {
  245. const msg = 'Bot User OAuth Token is not setup.';
  246. logger.error('Error', msg);
  247. return res.apiv3Err(new ErrorV3(msg, 'not-setup-slack-bot-token', 400));
  248. }
  249. }
  250. router.post('/test-notification-to-slack-work-space', async(req, res) => {
  251. checkConnectedToSlack(res);
  252. const slackBotToken = crowi.configManager.getConfig('crowi', 'slackbot:token');
  253. this.client = new WebClient(slackBotToken, { logLevel: LogLevel.DEBUG });
  254. logger.debug('SlackBot: setup is done');
  255. try {
  256. this.client.chat.postMessage({
  257. channel: '#general',
  258. text: 'Your test was successful!',
  259. });
  260. logger.info('SlackTest: send success massage to slack work space at #general.');
  261. logger.info('If you do not receive a message, you may not have invited the bot to the #general channel.');
  262. // eslint-disable-next-line max-len
  263. const message = 'Successfully send message to Slack work space. If you do not receive a message, you may not have invited the bot to the #general channel.';
  264. return res.apiv3({ message });
  265. }
  266. catch (error) {
  267. const msg = 'Error occured in testing to notify slack work space';
  268. logger.error('Error', error);
  269. return res.apiv3Err(new ErrorV3(msg, 'test-notify-slack-work-space-failed'), 500);
  270. }
  271. });
  272. /**
  273. * @swagger
  274. *
  275. * /slack-integration/access-token:
  276. * delete:
  277. * tags: [SlackIntegration]
  278. * operationId: deleteAccessTokenForSlackBot
  279. * summary: /slack-integration
  280. * description: Delete accessToken
  281. * responses:
  282. * 200:
  283. * description: Succeeded to delete accessToken
  284. */
  285. router.delete('/access-token', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  286. try {
  287. await updateSlackBotSettings({ 'slackbot:access-token': null });
  288. // initialize slack service
  289. await crowi.slackBotService.initialize();
  290. crowi.slackBotService.publishUpdatedMessage();
  291. return res.apiv3({});
  292. }
  293. catch (error) {
  294. const msg = 'Error occured in discard of slackbotAccessToken';
  295. logger.error('Error', error);
  296. return res.apiv3Err(new ErrorV3(msg, 'discard-slackbotAccessToken-failed'), 500);
  297. }
  298. });
  299. return router;
  300. };