app-settings.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. const loggerFactory = require('@alias/logger');
  2. const logger = loggerFactory('growi:routes:apiv3:app-settings');
  3. const debug = require('debug')('growi:routes:admin');
  4. const express = require('express');
  5. const { listLocaleIds } = require('@commons/util/locale-utils');
  6. const router = express.Router();
  7. const { body } = require('express-validator/check');
  8. const ErrorV3 = require('../../models/vo/error-apiv3');
  9. /**
  10. * @swagger
  11. * tags:
  12. * name: AppSettings
  13. */
  14. /**
  15. * @swagger
  16. *
  17. * components:
  18. * schemas:
  19. * AppSettingParams:
  20. * description: AppSettingParams
  21. * type: object
  22. * properties:
  23. * title:
  24. * type: string
  25. * description: site name show on page header and tilte of HTML
  26. * confidential:
  27. * type: string
  28. * description: confidential show on page header
  29. * globalLang:
  30. * type: string
  31. * description: language set when create user
  32. * fileUpload:
  33. * type: boolean
  34. * description: enable upload file except image file
  35. * SiteUrlSettingParams:
  36. * description: SiteUrlSettingParams
  37. * type: object
  38. * properties:
  39. * siteUrl:
  40. * type: string
  41. * description: Site URL. e.g. https://example.com, https://example.com:8080
  42. * envSiteUrl:
  43. * type: string
  44. * description: environment variable 'APP_SITE_URL'
  45. * FromAddress:
  46. * description: MailSettingParams
  47. * type: object
  48. * properties:
  49. * fromAddress:
  50. * type: string
  51. * description: e-mail address used as from address of mail which sent from GROWI app
  52. * MailSettingParams:
  53. * description: MailSettingParams
  54. * type: object
  55. * properties:
  56. * smtpHost:
  57. * type: string
  58. * description: host name of client's smtp server
  59. * smtpPort:
  60. * type: string
  61. * description: port of client's smtp server
  62. * smtpUser:
  63. * type: string
  64. * description: user name of client's smtp server
  65. * smtpPassword:
  66. * type: string
  67. * description: password of client's smtp server
  68. * AwsSettingParams:
  69. * description: AwsSettingParams
  70. * type: object
  71. * properties:
  72. * region:
  73. * type: string
  74. * description: region of AWS S3
  75. * customEndpoint:
  76. * type: string
  77. * description: custom endpoint of AWS S3
  78. * bucket:
  79. * type: string
  80. * description: AWS S3 bucket name
  81. * accessKeyId:
  82. * type: string
  83. * description: accesskey id for authentification of AWS
  84. * secretAccessKey:
  85. * type: string
  86. * description: secret key for authentification of AWS
  87. * PluginSettingParams:
  88. * description: PluginSettingParams
  89. * type: object
  90. * properties:
  91. * isEnabledPlugins:
  92. * type: string
  93. * description: enable use plugins
  94. */
  95. module.exports = (crowi) => {
  96. const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
  97. const loginRequired = require('../../middlewares/login-required')(crowi);
  98. const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  99. const adminRequired = require('../../middlewares/admin-required')(crowi);
  100. const csrf = require('../../middlewares/csrf')(crowi);
  101. const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
  102. const validator = {
  103. appSetting: [
  104. body('title').trim(),
  105. body('confidential'),
  106. body('globalLang').isIn(listLocaleIds()),
  107. body('fileUpload').isBoolean(),
  108. ],
  109. siteUrlSetting: [
  110. body('siteUrl').trim().matches(/^(https?:\/\/[^/]+|)$/).isURL({ require_tld: false }),
  111. ],
  112. fromAddress: [
  113. body('fromAddress').trim().if(value => value !== '').isEmail(),
  114. ],
  115. mailSetting: [
  116. body('smtpHost').trim(),
  117. body('smtpPort').trim().isPort(),
  118. body('smtpUser').trim(),
  119. body('smtpPassword').trim(),
  120. ],
  121. awsSetting: [
  122. body('region').trim().matches(/^[a-z]+-[a-z]+-\d+$/).withMessage((value, { req }) => req.t('validation.aws_region')),
  123. body('customEndpoint').trim().matches(/^(https?:\/\/[^/]+|)$/).withMessage((value, { req }) => req.t('validation.aws_custom_endpoint')),
  124. body('bucket').trim(),
  125. body('accessKeyId').trim().matches(/^[\da-zA-Z]+$/),
  126. body('secretAccessKey').trim(),
  127. ],
  128. pluginSetting: [
  129. body('isEnabledPlugins').isBoolean(),
  130. ],
  131. };
  132. /**
  133. * @swagger
  134. *
  135. * /app-settings:
  136. * get:
  137. * tags: [AppSettings]
  138. * operationId: getAppSettings
  139. * summary: /app-settings
  140. * description: get app setting params
  141. * responses:
  142. * 200:
  143. * description: Resources are available
  144. * content:
  145. * application/json:
  146. * schema:
  147. * properties:
  148. * appSettingsParams:
  149. * type: object
  150. * description: app settings params
  151. */
  152. router.get('/', accessTokenParser, loginRequired, adminRequired, async(req, res) => {
  153. const appSettingsParams = {
  154. title: crowi.configManager.getConfig('crowi', 'app:title'),
  155. confidential: crowi.configManager.getConfig('crowi', 'app:confidential'),
  156. globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
  157. fileUpload: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
  158. siteUrl: crowi.configManager.getConfig('crowi', 'app:siteUrl'),
  159. envSiteUrl: crowi.configManager.getConfigFromEnvVars('crowi', 'app:siteUrl'),
  160. fromAddress: crowi.configManager.getConfig('crowi', 'mail:from'),
  161. smtpHost: crowi.configManager.getConfig('crowi', 'mail:smtpHost'),
  162. smtpPort: crowi.configManager.getConfig('crowi', 'mail:smtpPort'),
  163. smtpUser: crowi.configManager.getConfig('crowi', 'mail:smtpUser'),
  164. smtpPassword: crowi.configManager.getConfig('crowi', 'mail:smtpPassword'),
  165. region: crowi.configManager.getConfig('crowi', 'aws:region'),
  166. customEndpoint: crowi.configManager.getConfig('crowi', 'aws:customEndpoint'),
  167. bucket: crowi.configManager.getConfig('crowi', 'aws:bucket'),
  168. accessKeyId: crowi.configManager.getConfig('crowi', 'aws:accessKeyId'),
  169. secretAccessKey: crowi.configManager.getConfig('crowi', 'aws:secretAccessKey'),
  170. isEnabledPlugins: crowi.configManager.getConfig('crowi', 'plugin:isEnabledPlugins'),
  171. };
  172. return res.apiv3({ appSettingsParams });
  173. });
  174. /**
  175. * @swagger
  176. *
  177. * /app-settings/app-setting:
  178. * put:
  179. * tags: [AppSettings]
  180. * summary: /app-settings/app-setting
  181. * operationId: updateAppSettings
  182. * description: Update app setting
  183. * requestBody:
  184. * required: true
  185. * content:
  186. * application/json:
  187. * schema:
  188. * $ref: '#/components/schemas/AppSettingParams'
  189. * responses:
  190. * 200:
  191. * description: Succeeded to update app setting
  192. * content:
  193. * application/json:
  194. * schema:
  195. * $ref: '#/components/schemas/AppSettingParams'
  196. */
  197. router.put('/app-setting', loginRequiredStrictly, adminRequired, csrf, validator.appSetting, apiV3FormValidator, async(req, res) => {
  198. const requestAppSettingParams = {
  199. 'app:title': req.body.title,
  200. 'app:confidential': req.body.confidential,
  201. 'app:globalLang': req.body.globalLang,
  202. 'app:fileUpload': req.body.fileUpload,
  203. };
  204. try {
  205. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestAppSettingParams);
  206. const appSettingParams = {
  207. title: crowi.configManager.getConfig('crowi', 'app:title'),
  208. confidential: crowi.configManager.getConfig('crowi', 'app:confidential'),
  209. globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
  210. fileUpload: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
  211. };
  212. return res.apiv3({ appSettingParams });
  213. }
  214. catch (err) {
  215. const msg = 'Error occurred in updating app setting';
  216. logger.error('Error', err);
  217. return res.apiv3Err(new ErrorV3(msg, 'update-appSetting-failed'));
  218. }
  219. });
  220. /**
  221. * @swagger
  222. *
  223. * /app-settings/site-url-setting:
  224. * put:
  225. * tags: [AppSettings]
  226. * operationId: updateAppSettingSiteUrlSetting
  227. * summary: /app-settings/site-url-setting
  228. * description: Update site url setting
  229. * requestBody:
  230. * required: true
  231. * content:
  232. * application/json:
  233. * schema:
  234. * $ref: '#/components/schemas/SiteUrlSettingParams'
  235. * responses:
  236. * 200:
  237. * description: Succeeded to update site url setting
  238. * content:
  239. * application/json:
  240. * schema:
  241. * $ref: '#/components/schemas/SiteUrlSettingParams'
  242. */
  243. router.put('/site-url-setting', loginRequiredStrictly, adminRequired, csrf, validator.siteUrlSetting, apiV3FormValidator, async(req, res) => {
  244. const requestSiteUrlSettingParams = {
  245. 'app:siteUrl': req.body.siteUrl,
  246. };
  247. try {
  248. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestSiteUrlSettingParams);
  249. const siteUrlSettingParams = {
  250. siteUrl: crowi.configManager.getConfig('crowi', 'app:siteUrl'),
  251. };
  252. return res.apiv3({ siteUrlSettingParams });
  253. }
  254. catch (err) {
  255. const msg = 'Error occurred in updating site url setting';
  256. logger.error('Error', err);
  257. return res.apiv3Err(new ErrorV3(msg, 'update-siteUrlSetting-failed'));
  258. }
  259. });
  260. /**
  261. * send mail (Promise wrapper)
  262. */
  263. async function sendMailPromiseWrapper(smtpClient, options) {
  264. return new Promise((resolve, reject) => {
  265. smtpClient.sendMail(options, (err, res) => {
  266. if (err) {
  267. reject(err);
  268. }
  269. else {
  270. resolve(res);
  271. }
  272. });
  273. });
  274. }
  275. /**
  276. * validate mail setting send test mail
  277. */
  278. async function validateMailSetting(req) {
  279. const { configManager, mailService } = crowi;
  280. const fromAddress = configManager.getConfig('crowi', 'mail:from');
  281. if (fromAddress == null) {
  282. throw Error('fromAddress is not setup');
  283. }
  284. const option = {
  285. host: req.body.smtpHost,
  286. port: req.body.smtpPort,
  287. };
  288. if (req.body.smtpUser && req.body.smtpPassword) {
  289. option.auth = {
  290. user: req.body.smtpUser,
  291. pass: req.body.smtpPassword,
  292. };
  293. }
  294. if (option.port === 465) {
  295. option.secure = true;
  296. }
  297. const smtpClient = mailService.createSMTPClient(option);
  298. debug('mailer setup for validate SMTP setting', smtpClient);
  299. const mailOptions = {
  300. from: fromAddress,
  301. to: req.user.email,
  302. subject: 'Wiki管理設定のアップデートによるメール通知',
  303. text: 'このメールは、WikiのSMTP設定のアップデートにより送信されています。',
  304. };
  305. await sendMailPromiseWrapper(smtpClient, mailOptions);
  306. }
  307. const updateMailSettinConfig = async function(requestMailSettingParams) {
  308. const {
  309. configManager,
  310. mailService,
  311. } = crowi;
  312. // update config without publishing S2sMessage
  313. await configManager.updateConfigsInTheSameNamespace('crowi', requestMailSettingParams, true);
  314. await mailService.initialize();
  315. mailService.publishUpdatedMessage();
  316. return {
  317. smtpHost: configManager.getConfig('crowi', 'mail:smtpHost'),
  318. smtpPort: configManager.getConfig('crowi', 'mail:smtpPort'),
  319. smtpUser: configManager.getConfig('crowi', 'mail:smtpUser'),
  320. smtpPassword: configManager.getConfig('crowi', 'mail:smtpPassword'),
  321. };
  322. };
  323. /**
  324. * @swagger
  325. *
  326. * /app-settings/from-address:
  327. * put:
  328. * tags: [AppSettings]
  329. * operationId: updateAppSettingFromAddress
  330. * summary: /app-settings/from-address
  331. * description: Update from address
  332. * requestBody:
  333. * required: true
  334. * content:
  335. * application/json:
  336. * schema:
  337. * $ref: '#/components/schemas/FromAddress'
  338. * responses:
  339. * 200:
  340. * description: Succeeded to update from adress
  341. * content:
  342. * application/json:
  343. * schema:
  344. * $ref: '#/components/schemas/FromAddress'
  345. */
  346. router.put('/from-address', loginRequiredStrictly, adminRequired, csrf, validator.fromAddress, apiV3FormValidator, async(req, res) => {
  347. try {
  348. const mailSettingParams = await updateMailSettinConfig({ 'mail:from': req.body.fromAddress });
  349. return res.apiv3({ mailSettingParams });
  350. }
  351. catch (err) {
  352. const msg = 'Error occurred in updating from adress';
  353. logger.error('Error', err);
  354. return res.apiv3Err(new ErrorV3(msg, 'update-from-adress-failed'));
  355. }
  356. });
  357. /**
  358. * @swagger
  359. *
  360. * /app-settings/mail-setting:
  361. * put:
  362. * tags: [AppSettings]
  363. * operationId: updateAppSettingMailSetting
  364. * summary: /app-settings/mail-setting
  365. * description: Update mail setting
  366. * requestBody:
  367. * required: true
  368. * content:
  369. * application/json:
  370. * schema:
  371. * $ref: '#/components/schemas/MailSettingParams'
  372. * responses:
  373. * 200:
  374. * description: Succeeded to update mail setting
  375. * content:
  376. * application/json:
  377. * schema:
  378. * $ref: '#/components/schemas/MailSettingParams'
  379. */
  380. router.put('/mail-setting', loginRequiredStrictly, adminRequired, csrf, validator.mailSetting, apiV3FormValidator, async(req, res) => {
  381. try {
  382. await validateMailSetting(req);
  383. }
  384. catch (err) {
  385. const msg = req.t('validation.failed_to_send_a_test_email');
  386. logger.error('Error', err);
  387. debug('Error validate mail setting: ', err);
  388. return res.apiv3Err(new ErrorV3(msg, 'update-mailSetting-failed'));
  389. }
  390. const requestMailSettingParams = {
  391. 'mail:smtpHost': req.body.smtpHost,
  392. 'mail:smtpPort': req.body.smtpPort,
  393. 'mail:smtpUser': req.body.smtpUser,
  394. 'mail:smtpPassword': req.body.smtpPassword,
  395. };
  396. try {
  397. const mailSettingParams = await updateMailSettinConfig(requestMailSettingParams);
  398. return res.apiv3({ mailSettingParams });
  399. }
  400. catch (err) {
  401. const msg = 'Error occurred in updating mail setting';
  402. logger.error('Error', err);
  403. return res.apiv3Err(new ErrorV3(msg, 'update-mailSetting-failed'));
  404. }
  405. });
  406. /**
  407. * @swagger
  408. *
  409. * /app-settings/mail-setting:
  410. * delete:
  411. * tags: [AppSettings]
  412. * operationId: deleteAppSettingMailSetting
  413. * summary: /app-settings/mail-setting
  414. * description: delete mail setting
  415. * requestBody:
  416. * required: true
  417. * content:
  418. * application/json:
  419. * schema:
  420. * $ref: '#/components/schemas/MailSettingParams'
  421. * responses:
  422. * 200:
  423. * description: Succeeded to delete mail setting
  424. * content:
  425. * application/json:
  426. * schema:
  427. * $ref: '#/components/schemas/MailSettingParams'
  428. */
  429. router.delete('/mail-setting', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  430. const requestMailSettingParams = {
  431. 'mail:smtpHost': null,
  432. 'mail:smtpPort': null,
  433. 'mail:smtpUser': null,
  434. 'mail:smtpPassword': null,
  435. };
  436. try {
  437. const mailSettingParams = await updateMailSettinConfig(requestMailSettingParams);
  438. return res.apiv3({ mailSettingParams });
  439. }
  440. catch (err) {
  441. const msg = 'Error occurred in initializing mail setting';
  442. logger.error('Error', err);
  443. return res.apiv3Err(new ErrorV3(msg, 'initialize-mailSetting-failed'));
  444. }
  445. });
  446. /**
  447. * @swagger
  448. *
  449. * /app-settings/aws-setting:
  450. * put:
  451. * tags: [AppSettings]
  452. * operationId: updateAppSettingAwsSetting
  453. * summary: /app-settings/aws-setting
  454. * description: Update aws setting
  455. * requestBody:
  456. * required: true
  457. * content:
  458. * application/json:
  459. * schema:
  460. * $ref: '#/components/schemas/AwsSettingParams'
  461. * responses:
  462. * 200:
  463. * description: Succeeded to update aws setting
  464. * content:
  465. * application/json:
  466. * schema:
  467. * $ref: '#/components/schemas/AwsSettingParams'
  468. */
  469. router.put('/aws-setting', loginRequiredStrictly, adminRequired, csrf, validator.awsSetting, apiV3FormValidator, async(req, res) => {
  470. const requestAwsSettingParams = {
  471. 'aws:region': req.body.region,
  472. 'aws:customEndpoint': req.body.customEndpoint,
  473. 'aws:bucket': req.body.bucket,
  474. 'aws:accessKeyId': req.body.accessKeyId,
  475. 'aws:secretAccessKey': req.body.secretAccessKey,
  476. };
  477. try {
  478. const { configManager, mailService } = crowi;
  479. // update config without publishing S2sMessage
  480. await configManager.updateConfigsInTheSameNamespace('crowi', requestAwsSettingParams, true);
  481. await mailService.initialize();
  482. mailService.publishUpdatedMessage();
  483. const awsSettingParams = {
  484. region: crowi.configManager.getConfig('crowi', 'aws:region'),
  485. customEndpoint: crowi.configManager.getConfig('crowi', 'aws:customEndpoint'),
  486. bucket: crowi.configManager.getConfig('crowi', 'aws:bucket'),
  487. accessKeyId: crowi.configManager.getConfig('crowi', 'aws:accessKeyId'),
  488. secretAccessKey: crowi.configManager.getConfig('crowi', 'aws:secretAccessKey'),
  489. };
  490. return res.apiv3({ awsSettingParams });
  491. }
  492. catch (err) {
  493. const msg = 'Error occurred in updating aws setting';
  494. logger.error('Error', err);
  495. return res.apiv3Err(new ErrorV3(msg, 'update-awsSetting-failed'));
  496. }
  497. });
  498. /**
  499. * @swagger
  500. *
  501. * /app-settings/plugin-setting:
  502. * put:
  503. * tags: [AppSettings]
  504. * operationId: updateAppSettingPluginSetting
  505. * summary: /app-settings/plugin-setting
  506. * description: Update plugin setting
  507. * requestBody:
  508. * required: true
  509. * content:
  510. * application/json:
  511. * schema:
  512. * $ref: '#/components/schemas/PluginSettingParams'
  513. * responses:
  514. * 200:
  515. * description: Succeeded to update plugin setting
  516. * content:
  517. * application/json:
  518. * schema:
  519. * $ref: '#/components/schemas/PluginSettingParams'
  520. */
  521. router.put('/plugin-setting', loginRequiredStrictly, adminRequired, csrf, validator.pluginSetting, apiV3FormValidator, async(req, res) => {
  522. const requestPluginSettingParams = {
  523. 'plugin:isEnabledPlugins': req.body.isEnabledPlugins,
  524. };
  525. try {
  526. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestPluginSettingParams);
  527. const pluginSettingParams = {
  528. isEnabledPlugins: crowi.configManager.getConfig('crowi', 'plugin:isEnabledPlugins'),
  529. };
  530. return res.apiv3({ pluginSettingParams });
  531. }
  532. catch (err) {
  533. const msg = 'Error occurred in updating plugin setting';
  534. logger.error('Error', err);
  535. return res.apiv3Err(new ErrorV3(msg, 'update-pluginSetting-failed'));
  536. }
  537. });
  538. return router;
  539. };