markdown-setting.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. import { SCOPE } from '@growi/core/dist/interfaces';
  2. import { ErrorV3 } from '@growi/core/dist/models';
  3. import { SupportedAction } from '~/interfaces/activity';
  4. import { accessTokenParser } from '~/server/middlewares/access-token-parser';
  5. import { configManager } from '~/server/service/config-manager';
  6. import loggerFactory from '~/utils/logger';
  7. import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
  8. import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
  9. const logger = loggerFactory('growi:routes:apiv3:markdown-setting');
  10. const express = require('express');
  11. const router = express.Router();
  12. const { body } = require('express-validator');
  13. const validator = {
  14. lineBreak: [
  15. body('isEnabledLinebreaks').isBoolean(),
  16. body('isEnabledLinebreaksInComments').isBoolean(),
  17. ],
  18. indent: [
  19. body('adminPreferredIndentSize').isIn([2, 4]),
  20. body('isIndentSizeForced').isBoolean(),
  21. ],
  22. xssSetting: [
  23. body('isEnabledXss').isBoolean(),
  24. body('tagWhitelist').isArray(),
  25. body('attrWhitelist').isString(),
  26. ],
  27. };
  28. /**
  29. * @swagger
  30. *
  31. * components:
  32. * schemas:
  33. * MarkdownParams:
  34. * description: MarkdownParams
  35. * type: object
  36. * properties:
  37. * isEnabledLinebreaks:
  38. * type: boolean
  39. * description: enable lineBreak
  40. * isEnabledLinebreaksInComments:
  41. * type: boolean
  42. * description: enable lineBreak in comment
  43. * adminPreferredIndentSize:
  44. * type: number
  45. * description: preferred indent size
  46. * isIndentSizeForced:
  47. * type: boolean
  48. * description: force indent size
  49. * isEnabledXss:
  50. * type: boolean
  51. * description: enable xss
  52. * xssOption:
  53. * type: number
  54. * description: number of xss option
  55. * tagWhitelist:
  56. * type: array
  57. * description: array of tag whitelist
  58. * items:
  59. * type: string
  60. * description: tag whitelist
  61. * attrWhitelist:
  62. * type: string
  63. * description: attr whitelist
  64. * LineBreakParams:
  65. * description: LineBreakParams
  66. * type: object
  67. * properties:
  68. * isEnabledLinebreaks:
  69. * type: boolean
  70. * description: enable lineBreak
  71. * isEnabledLinebreaksInComments:
  72. * type: boolean
  73. * description: enable lineBreak in comment
  74. * PresentationParams:
  75. * description: PresentationParams
  76. * type: object
  77. * properties:
  78. * pageBreakSeparator:
  79. * type: number
  80. * description: number of pageBreakSeparator
  81. * pageBreakCustomSeparator:
  82. * type: string
  83. * description: string of pageBreakCustomSeparator
  84. * XssParams:
  85. * description: XssParams
  86. * type: object
  87. * properties:
  88. * isEnabledXss:
  89. * type: boolean
  90. * description: enable xss
  91. * xssOption:
  92. * type: number
  93. * description: number of xss option
  94. * tagWhitelist:
  95. * type: array
  96. * description: array of tag whitelist
  97. * items:
  98. * type: string
  99. * description: tag whitelist
  100. * attrWhitelist:
  101. * type: string
  102. * description: attr whitelist
  103. * IndentParams:
  104. * description: IndentParams
  105. * type: object
  106. * properties:
  107. * adminPreferredIndentSize:
  108. * type: number
  109. * description: preferred indent size
  110. * isIndentSizeForced:
  111. * type: boolean
  112. * description: force indent size
  113. */
  114. /** @param {import('~/server/crowi').default} crowi Crowi instance */
  115. module.exports = (crowi) => {
  116. const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  117. const adminRequired = require('../../middlewares/admin-required')(crowi);
  118. const addActivity = generateAddActivityMiddleware(crowi);
  119. const activityEvent = crowi.event('activity');
  120. /**
  121. * @swagger
  122. *
  123. * /markdown-setting:
  124. * get:
  125. * tags: [MarkDownSetting]
  126. * security:
  127. * - cookieAuth: []
  128. * summary: Get markdown parameters
  129. * responses:
  130. * 200:
  131. * description: params of markdown
  132. * content:
  133. * application/json:
  134. * schema:
  135. * properties:
  136. * markdownParams:
  137. * type: object
  138. * description: markdown params
  139. * $ref: '#/components/schemas/MarkdownParams'
  140. */
  141. router.get('/', accessTokenParser([SCOPE.READ.ADMIN.MARKDOWN]), loginRequiredStrictly, adminRequired, async(req, res) => {
  142. const markdownParams = {
  143. isEnabledLinebreaks: await crowi.configManager.getConfig('markdown:isEnabledLinebreaks'),
  144. isEnabledLinebreaksInComments: await crowi.configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
  145. adminPreferredIndentSize: await crowi.configManager.getConfig('markdown:adminPreferredIndentSize'),
  146. isIndentSizeForced: await crowi.configManager.getConfig('markdown:isIndentSizeForced'),
  147. isEnabledXss: await crowi.configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
  148. xssOption: await crowi.configManager.getConfig('markdown:rehypeSanitize:option'),
  149. tagWhitelist: await crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
  150. attrWhitelist: await crowi.configManager.getConfig('markdown:rehypeSanitize:attributes'),
  151. };
  152. return res.apiv3({ markdownParams });
  153. });
  154. /**
  155. * @swagger
  156. *
  157. * /markdown-setting/lineBreak:
  158. * put:
  159. * tags: [MarkDownSetting]
  160. * security:
  161. * - cookieAuth: []
  162. * summary: Update lineBreak setting
  163. * requestBody:
  164. * required: true
  165. * content:
  166. * application/json:
  167. * schema:
  168. * $ref: '#/components/schemas/LineBreakParams'
  169. * responses:
  170. * 200:
  171. * description: Succeeded to update lineBreak setting
  172. * content:
  173. * application/json:
  174. * schema:
  175. * type: object
  176. * properties:
  177. * lineBreaksParams:
  178. * type: object
  179. * $ref: '#/components/schemas/LineBreakParams'
  180. */
  181. router.put('/lineBreak', accessTokenParser([SCOPE.WRITE.ADMIN.MARKDOWN]),
  182. loginRequiredStrictly, adminRequired, addActivity, validator.lineBreak, apiV3FormValidator, async(req, res) => {
  183. const requestLineBreakParams = {
  184. 'markdown:isEnabledLinebreaks': req.body.isEnabledLinebreaks,
  185. 'markdown:isEnabledLinebreaksInComments': req.body.isEnabledLinebreaksInComments,
  186. };
  187. try {
  188. await configManager.updateConfigs(requestLineBreakParams);
  189. const lineBreaksParams = {
  190. isEnabledLinebreaks: await crowi.configManager.getConfig('markdown:isEnabledLinebreaks'),
  191. isEnabledLinebreaksInComments: await crowi.configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
  192. };
  193. const parameters = { action: SupportedAction.ACTION_ADMIN_MARKDOWN_LINE_BREAK_UPDATE };
  194. activityEvent.emit('update', res.locals.activity._id, parameters);
  195. return res.apiv3({ lineBreaksParams });
  196. }
  197. catch (err) {
  198. const msg = 'Error occurred in updating lineBreak';
  199. logger.error('Error', err);
  200. return res.apiv3Err(new ErrorV3(msg, 'update-lineBreak-failed'));
  201. }
  202. });
  203. /**
  204. * @swagger
  205. *
  206. * /markdown-setting/indent:
  207. * put:
  208. * tags: [MarkDownSetting]
  209. * security:
  210. * - cookieAuth: []
  211. * summary: Update indent setting
  212. * requestBody:
  213. * required: true
  214. * content:
  215. * application/json:
  216. * schema:
  217. * $ref: '#/components/schemas/IndentParams'
  218. * responses:
  219. * 200:
  220. * description: Succeeded to update indent setting
  221. * content:
  222. * application/json:
  223. * schema:
  224. * type: object
  225. * properties:
  226. * indentParams:
  227. * type: object
  228. * description: indent params
  229. * $ref: '#/components/schemas/IndentParams'
  230. */
  231. router.put('/indent', accessTokenParser([SCOPE.WRITE.ADMIN.MARKDOWN]),
  232. loginRequiredStrictly, adminRequired, addActivity, validator.indent, apiV3FormValidator, async(req, res) => {
  233. const requestIndentParams = {
  234. 'markdown:adminPreferredIndentSize': req.body.adminPreferredIndentSize,
  235. 'markdown:isIndentSizeForced': req.body.isIndentSizeForced,
  236. };
  237. try {
  238. await configManager.updateConfigs(requestIndentParams);
  239. const indentParams = {
  240. adminPreferredIndentSize: await crowi.configManager.getConfig('markdown:adminPreferredIndentSize'),
  241. isIndentSizeForced: await crowi.configManager.getConfig('markdown:isIndentSizeForced'),
  242. };
  243. const parameters = { action: SupportedAction.ACTION_ADMIN_MARKDOWN_INDENT_UPDATE };
  244. activityEvent.emit('update', res.locals.activity._id, parameters);
  245. return res.apiv3({ indentParams });
  246. }
  247. catch (err) {
  248. const msg = 'Error occurred in updating indent';
  249. logger.error('Error', err);
  250. return res.apiv3Err(new ErrorV3(msg, 'update-indent-failed'));
  251. }
  252. });
  253. /**
  254. * @swagger
  255. *
  256. * /markdown-setting/xss:
  257. * put:
  258. * tags: [MarkDownSetting]
  259. * security:
  260. * - cookieAuth: []
  261. * summary: Update XSS setting
  262. * description: Update xss
  263. * requestBody:
  264. * required: true
  265. * content:
  266. * application/json:
  267. * schema:
  268. * $ref: '#/components/schemas/XssParams'
  269. * responses:
  270. * 200:
  271. * description: Succeeded to update xss setting
  272. * content:
  273. * application/json:
  274. * schema:
  275. * $ref: '#/components/schemas/XssParams'
  276. */
  277. router.put('/xss', accessTokenParser([SCOPE.WRITE.ADMIN.MARKDOWN]),
  278. loginRequiredStrictly, adminRequired, addActivity, validator.xssSetting, apiV3FormValidator, async(req, res) => {
  279. if (req.body.isEnabledXss && req.body.xssOption == null) {
  280. return res.apiv3Err(new ErrorV3('xss option is required'));
  281. }
  282. try {
  283. JSON.parse(req.body.attrWhitelist);
  284. }
  285. catch (err) {
  286. const msg = 'Error occurred in updating xss';
  287. logger.error('Error', err);
  288. return res.apiv3Err(new ErrorV3(msg, 'update-xss-failed'));
  289. }
  290. const reqestXssParams = {
  291. 'markdown:rehypeSanitize:isEnabledPrevention': req.body.isEnabledXss,
  292. 'markdown:rehypeSanitize:option': req.body.xssOption,
  293. 'markdown:rehypeSanitize:tagNames': req.body.tagWhitelist,
  294. 'markdown:rehypeSanitize:attributes': req.body.attrWhitelist,
  295. };
  296. try {
  297. await configManager.updateConfigs(reqestXssParams);
  298. const xssParams = {
  299. isEnabledXss: await crowi.configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
  300. xssOption: await crowi.configManager.getConfig('markdown:rehypeSanitize:option'),
  301. tagWhitelist: await crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
  302. attrWhitelist: await crowi.configManager.getConfig('markdown:rehypeSanitize:attributes'),
  303. };
  304. const parameters = { action: SupportedAction.ACTION_ADMIN_MARKDOWN_XSS_UPDATE };
  305. activityEvent.emit('update', res.locals.activity._id, parameters);
  306. return res.apiv3({ xssParams });
  307. }
  308. catch (err) {
  309. const msg = 'Error occurred in updating xss';
  310. logger.error('Error', err);
  311. return res.apiv3Err(new ErrorV3(msg, 'update-xss-failed'));
  312. }
  313. });
  314. return router;
  315. };