app-settings.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  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. * MailSetting:
  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. * SmtpSettingParams:
  53. * description: SmtpSettingParams
  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. * SesSettingParams:
  69. * description: SesSettingParams
  70. * type: object
  71. * properties:
  72. * accessKeyId:
  73. * type: string
  74. * description: accesskey id for authentification of AWS
  75. * secretAccessKey:
  76. * type: string
  77. * description: secret key for authentification of AWS
  78. * AwsSettingParams:
  79. * description: AwsSettingParams
  80. * type: object
  81. * properties:
  82. * region:
  83. * type: string
  84. * description: region of AWS S3
  85. * customEndpoint:
  86. * type: string
  87. * description: custom endpoint of AWS S3
  88. * bucket:
  89. * type: string
  90. * description: AWS S3 bucket name
  91. * accessKeyId:
  92. * type: string
  93. * description: accesskey id for authentification of AWS
  94. * secretAccessKey:
  95. * type: string
  96. * description: secret key for authentification of AWS
  97. * PluginSettingParams:
  98. * description: PluginSettingParams
  99. * type: object
  100. * properties:
  101. * isEnabledPlugins:
  102. * type: string
  103. * description: enable use plugins
  104. */
  105. module.exports = (crowi) => {
  106. const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
  107. const loginRequired = require('../../middlewares/login-required')(crowi);
  108. const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  109. const adminRequired = require('../../middlewares/admin-required')(crowi);
  110. const csrf = require('../../middlewares/csrf')(crowi);
  111. const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
  112. const validator = {
  113. appSetting: [
  114. body('title').trim(),
  115. body('confidential'),
  116. body('globalLang').isIn(listLocaleIds()),
  117. body('fileUpload').isBoolean(),
  118. ],
  119. siteUrlSetting: [
  120. body('siteUrl').trim().matches(/^(https?:\/\/[^/]+|)$/).isURL({ require_tld: false }),
  121. ],
  122. mailSetting: [
  123. body('fromAddress').trim().if(value => value !== '').isEmail(),
  124. ],
  125. smtpSetting: [
  126. body('smtpHost').trim(),
  127. body('smtpPort').trim().isPort(),
  128. body('smtpUser').trim(),
  129. body('smtpPassword').trim(),
  130. ],
  131. sesSetting: [
  132. body('sesAccessKeyId').trim().if(value => value !== '').matches(/^[\da-zA-Z]+$/),
  133. body('sesSecretAccessKey').trim(),
  134. ],
  135. awsSetting: [
  136. body('region').trim().matches(/^[a-z]+-[a-z]+-\d+$/).withMessage((value, { req }) => req.t('validation.aws_region')),
  137. body('customEndpoint').trim().matches(/^(https?:\/\/[^/]+|)$/).withMessage((value, { req }) => req.t('validation.aws_custom_endpoint')),
  138. body('bucket').trim(),
  139. body('accessKeyId').trim().if(value => value !== '').matches(/^[\da-zA-Z]+$/),
  140. body('secretAccessKey').trim(),
  141. ],
  142. pluginSetting: [
  143. body('isEnabledPlugins').isBoolean(),
  144. ],
  145. };
  146. /**
  147. * @swagger
  148. *
  149. * /app-settings:
  150. * get:
  151. * tags: [AppSettings]
  152. * operationId: getAppSettings
  153. * summary: /app-settings
  154. * description: get app setting params
  155. * responses:
  156. * 200:
  157. * description: Resources are available
  158. * content:
  159. * application/json:
  160. * schema:
  161. * properties:
  162. * appSettingsParams:
  163. * type: object
  164. * description: app settings params
  165. */
  166. router.get('/', accessTokenParser, loginRequired, adminRequired, async(req, res) => {
  167. const appSettingsParams = {
  168. title: crowi.configManager.getConfig('crowi', 'app:title'),
  169. confidential: crowi.configManager.getConfig('crowi', 'app:confidential'),
  170. globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
  171. fileUpload: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
  172. siteUrl: crowi.configManager.getConfig('crowi', 'app:siteUrl'),
  173. envSiteUrl: crowi.configManager.getConfigFromEnvVars('crowi', 'app:siteUrl'),
  174. fromAddress: crowi.configManager.getConfig('crowi', 'mail:from'),
  175. smtpHost: crowi.configManager.getConfig('crowi', 'mail:smtpHost'),
  176. smtpPort: crowi.configManager.getConfig('crowi', 'mail:smtpPort'),
  177. smtpUser: crowi.configManager.getConfig('crowi', 'mail:smtpUser'),
  178. smtpPassword: crowi.configManager.getConfig('crowi', 'mail:smtpPassword'),
  179. sesAccessKeyId: crowi.configManager.getConfig('crowi', 'mail:sesAccessKeyId'),
  180. sesSecretAccessKey: crowi.configManager.getConfig('crowi', 'mail:sesSecretAccessKey'),
  181. region: crowi.configManager.getConfig('crowi', 'aws:region'),
  182. customEndpoint: crowi.configManager.getConfig('crowi', 'aws:customEndpoint'),
  183. bucket: crowi.configManager.getConfig('crowi', 'aws:bucket'),
  184. accessKeyId: crowi.configManager.getConfig('crowi', 'aws:accessKeyId'),
  185. secretAccessKey: crowi.configManager.getConfig('crowi', 'aws:secretAccessKey'),
  186. isEnabledPlugins: crowi.configManager.getConfig('crowi', 'plugin:isEnabledPlugins'),
  187. };
  188. return res.apiv3({ appSettingsParams });
  189. });
  190. /**
  191. * @swagger
  192. *
  193. * /app-settings/app-setting:
  194. * put:
  195. * tags: [AppSettings]
  196. * summary: /app-settings/app-setting
  197. * operationId: updateAppSettings
  198. * description: Update app setting
  199. * requestBody:
  200. * required: true
  201. * content:
  202. * application/json:
  203. * schema:
  204. * $ref: '#/components/schemas/AppSettingParams'
  205. * responses:
  206. * 200:
  207. * description: Succeeded to update app setting
  208. * content:
  209. * application/json:
  210. * schema:
  211. * $ref: '#/components/schemas/AppSettingParams'
  212. */
  213. router.put('/app-setting', loginRequiredStrictly, adminRequired, csrf, validator.appSetting, apiV3FormValidator, async(req, res) => {
  214. const requestAppSettingParams = {
  215. 'app:title': req.body.title,
  216. 'app:confidential': req.body.confidential,
  217. 'app:globalLang': req.body.globalLang,
  218. 'app:fileUpload': req.body.fileUpload,
  219. };
  220. try {
  221. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestAppSettingParams);
  222. const appSettingParams = {
  223. title: crowi.configManager.getConfig('crowi', 'app:title'),
  224. confidential: crowi.configManager.getConfig('crowi', 'app:confidential'),
  225. globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
  226. fileUpload: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
  227. };
  228. return res.apiv3({ appSettingParams });
  229. }
  230. catch (err) {
  231. const msg = 'Error occurred in updating app setting';
  232. logger.error('Error', err);
  233. return res.apiv3Err(new ErrorV3(msg, 'update-appSetting-failed'));
  234. }
  235. });
  236. /**
  237. * @swagger
  238. *
  239. * /app-settings/site-url-setting:
  240. * put:
  241. * tags: [AppSettings]
  242. * operationId: updateAppSettingSiteUrlSetting
  243. * summary: /app-settings/site-url-setting
  244. * description: Update site url setting
  245. * requestBody:
  246. * required: true
  247. * content:
  248. * application/json:
  249. * schema:
  250. * $ref: '#/components/schemas/SiteUrlSettingParams'
  251. * responses:
  252. * 200:
  253. * description: Succeeded to update site url setting
  254. * content:
  255. * application/json:
  256. * schema:
  257. * $ref: '#/components/schemas/SiteUrlSettingParams'
  258. */
  259. router.put('/site-url-setting', loginRequiredStrictly, adminRequired, csrf, validator.siteUrlSetting, apiV3FormValidator, async(req, res) => {
  260. const requestSiteUrlSettingParams = {
  261. 'app:siteUrl': req.body.siteUrl,
  262. };
  263. try {
  264. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestSiteUrlSettingParams);
  265. const siteUrlSettingParams = {
  266. siteUrl: crowi.configManager.getConfig('crowi', 'app:siteUrl'),
  267. };
  268. return res.apiv3({ siteUrlSettingParams });
  269. }
  270. catch (err) {
  271. const msg = 'Error occurred in updating site url setting';
  272. logger.error('Error', err);
  273. return res.apiv3Err(new ErrorV3(msg, 'update-siteUrlSetting-failed'));
  274. }
  275. });
  276. /**
  277. * send mail (Promise wrapper)
  278. */
  279. async function sendMailPromiseWrapper(smtpClient, options) {
  280. return new Promise((resolve, reject) => {
  281. smtpClient.sendMail(options, (err, res) => {
  282. if (err) {
  283. reject(err);
  284. }
  285. else {
  286. resolve(res);
  287. }
  288. });
  289. });
  290. }
  291. /**
  292. * validate mail setting send test mail
  293. */
  294. async function validateMailSetting(req) {
  295. const { configManager, mailService } = crowi;
  296. const fromAddress = configManager.getConfig('crowi', 'mail:from');
  297. if (fromAddress == null) {
  298. throw Error('fromAddress is not setup');
  299. }
  300. const option = {
  301. host: req.body.smtpHost,
  302. port: req.body.smtpPort,
  303. };
  304. if (req.body.smtpUser && req.body.smtpPassword) {
  305. option.auth = {
  306. user: req.body.smtpUser,
  307. pass: req.body.smtpPassword,
  308. };
  309. }
  310. if (option.port === 465) {
  311. option.secure = true;
  312. }
  313. const smtpClient = mailService.createSMTPClient(option);
  314. debug('mailer setup for validate SMTP setting', smtpClient);
  315. const mailOptions = {
  316. from: fromAddress,
  317. to: req.user.email,
  318. subject: 'Wiki管理設定のアップデートによるメール通知',
  319. text: 'このメールは、WikiのSMTP設定のアップデートにより送信されています。',
  320. };
  321. await sendMailPromiseWrapper(smtpClient, mailOptions);
  322. }
  323. const updateMailSettinConfig = async function(requestMailSettingParams) {
  324. const {
  325. configManager,
  326. mailService,
  327. } = crowi;
  328. // update config without publishing S2sMessage
  329. await configManager.updateConfigsInTheSameNamespace('crowi', requestMailSettingParams, true);
  330. await mailService.initialize();
  331. mailService.publishUpdatedMessage();
  332. return {
  333. smtpHost: configManager.getConfig('crowi', 'mail:smtpHost'),
  334. smtpPort: configManager.getConfig('crowi', 'mail:smtpPort'),
  335. smtpUser: configManager.getConfig('crowi', 'mail:smtpUser'),
  336. smtpPassword: configManager.getConfig('crowi', 'mail:smtpPassword'),
  337. };
  338. };
  339. /**
  340. * @swagger
  341. *
  342. * /app-settings/mail-setting:
  343. * put:
  344. * tags: [AppSettings]
  345. * operationId: updateAppSettingMailSetting
  346. * summary: /app-settings/mail-setting
  347. * description: Update mail setting
  348. * requestBody:
  349. * required: true
  350. * content:
  351. * application/json:
  352. * schema:
  353. * $ref: '#/components/schemas/MailSetting'
  354. * responses:
  355. * 200:
  356. * description: Succeeded to update mail setting
  357. * content:
  358. * application/json:
  359. * schema:
  360. * $ref: '#/components/schemas/MailSetting'
  361. */
  362. router.put('/mail-setting', loginRequiredStrictly, adminRequired, csrf, validator.mailSetting, apiV3FormValidator, async(req, res) => {
  363. try {
  364. const mailSettingParams = await updateMailSettinConfig({ 'mail:from': req.body.fromAddress });
  365. return res.apiv3({ mailSettingParams });
  366. }
  367. catch (err) {
  368. const msg = 'Error occurred in updating from adress';
  369. logger.error('Error', err);
  370. return res.apiv3Err(new ErrorV3(msg, 'update-from-adress-failed'));
  371. }
  372. });
  373. /**
  374. * @swagger
  375. *
  376. * /app-settings/smtp-setting:
  377. * put:
  378. * tags: [AppSettings]
  379. * operationId: updateAppSettingSmtpSetting
  380. * summary: /app-settings/smtp-setting
  381. * description: Update smtp setting
  382. * requestBody:
  383. * required: true
  384. * content:
  385. * application/json:
  386. * schema:
  387. * $ref: '#/components/schemas/SmtpSettingParams'
  388. * responses:
  389. * 200:
  390. * description: Succeeded to update smtp setting
  391. * content:
  392. * application/json:
  393. * schema:
  394. * $ref: '#/components/schemas/SmtpSettingParams'
  395. */
  396. router.put('/smtp-setting', loginRequiredStrictly, adminRequired, csrf, validator.smtpSetting, apiV3FormValidator, async(req, res) => {
  397. try {
  398. await validateMailSetting(req);
  399. }
  400. catch (err) {
  401. const msg = req.t('validation.failed_to_send_a_test_email');
  402. logger.error('Error', err);
  403. debug('Error validate mail setting: ', err);
  404. return res.apiv3Err(new ErrorV3(msg, 'update-mailSetting-failed'));
  405. }
  406. const requestMailSettingParams = {
  407. 'mail:smtpHost': req.body.smtpHost,
  408. 'mail:smtpPort': req.body.smtpPort,
  409. 'mail:smtpUser': req.body.smtpUser,
  410. 'mail:smtpPassword': req.body.smtpPassword,
  411. };
  412. try {
  413. const mailSettingParams = await updateMailSettinConfig(requestMailSettingParams);
  414. return res.apiv3({ mailSettingParams });
  415. }
  416. catch (err) {
  417. const msg = 'Error occurred in updating smtp setting';
  418. logger.error('Error', err);
  419. return res.apiv3Err(new ErrorV3(msg, 'update-smtpSetting-failed'));
  420. }
  421. });
  422. /**
  423. * @swagger
  424. *
  425. * /app-settings/ses-setting:
  426. * put:
  427. * tags: [AppSettings]
  428. * operationId: updateAppSettingSesSetting
  429. * summary: /app-settings/ses-setting
  430. * description: Update ses setting
  431. * requestBody:
  432. * required: true
  433. * content:
  434. * application/json:
  435. * schema:
  436. * $ref: '#/components/schemas/SesSettingParams'
  437. * responses:
  438. * 200:
  439. * description: Succeeded to update ses setting
  440. * content:
  441. * application/json:
  442. * schema:
  443. * $ref: '#/components/schemas/SesSettingParams'
  444. */
  445. router.put('/ses-setting', loginRequiredStrictly, adminRequired, csrf, validator.sesSetting, apiV3FormValidator, async(req, res) => {
  446. const { mailService } = crowi;
  447. await mailService.initialize();
  448. mailService.publishUpdatedMessage();
  449. const requestSesSettingParams = {
  450. 'mail:sesAccessKeyId': req.body.sesAccessKeyId,
  451. 'mail:sesSecretAccessKey': req.body.sesSecretAccessKey,
  452. };
  453. try {
  454. const mailSettingParams = await updateMailSettinConfig(requestSesSettingParams);
  455. return res.apiv3({ mailSettingParams });
  456. }
  457. catch (err) {
  458. const msg = 'Error occurred in updating ses setting';
  459. logger.error('Error', err);
  460. return res.apiv3Err(new ErrorV3(msg, 'update-ses-setting-failed'));
  461. }
  462. });
  463. /**
  464. * @swagger
  465. *
  466. * /app-settings/smtp-setting:
  467. * delete:
  468. * tags: [AppSettings]
  469. * operationId: deleteAppSettingSmtpSetting
  470. * summary: /app-settings/smtp-setting
  471. * description: delete smtp setting
  472. * responses:
  473. * 200:
  474. * description: Succeeded to delete smtp setting
  475. * content:
  476. * application/json:
  477. * schema:
  478. * $ref: '#/components/schemas/SmtpSettingParams'
  479. */
  480. router.delete('/smtp-setting', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  481. const requestMailSettingParams = {
  482. 'mail:smtpHost': null,
  483. 'mail:smtpPort': null,
  484. 'mail:smtpUser': null,
  485. 'mail:smtpPassword': null,
  486. };
  487. try {
  488. const mailSettingParams = await updateMailSettinConfig(requestMailSettingParams);
  489. return res.apiv3({ mailSettingParams });
  490. }
  491. catch (err) {
  492. const msg = 'Error occurred in initializing stmp setting';
  493. logger.error('Error', err);
  494. return res.apiv3Err(new ErrorV3(msg, 'initialize-smtpSetting-failed'));
  495. }
  496. });
  497. /**
  498. * @swagger
  499. *
  500. * /app-settings/aws-setting:
  501. * put:
  502. * tags: [AppSettings]
  503. * operationId: updateAppSettingAwsSetting
  504. * summary: /app-settings/aws-setting
  505. * description: Update aws setting
  506. * requestBody:
  507. * required: true
  508. * content:
  509. * application/json:
  510. * schema:
  511. * $ref: '#/components/schemas/AwsSettingParams'
  512. * responses:
  513. * 200:
  514. * description: Succeeded to update aws setting
  515. * content:
  516. * application/json:
  517. * schema:
  518. * $ref: '#/components/schemas/AwsSettingParams'
  519. */
  520. router.put('/aws-setting', loginRequiredStrictly, adminRequired, csrf, validator.awsSetting, apiV3FormValidator, async(req, res) => {
  521. const requestAwsSettingParams = {
  522. 'aws:region': req.body.region,
  523. 'aws:customEndpoint': req.body.customEndpoint,
  524. 'aws:bucket': req.body.bucket,
  525. 'aws:accessKeyId': req.body.accessKeyId,
  526. 'aws:secretAccessKey': req.body.secretAccessKey,
  527. };
  528. try {
  529. const { configManager } = crowi;
  530. // update config without publishing S2sMessage
  531. await configManager.updateConfigsInTheSameNamespace('crowi', requestAwsSettingParams, true);
  532. const awsSettingParams = {
  533. region: crowi.configManager.getConfig('crowi', 'aws:region'),
  534. customEndpoint: crowi.configManager.getConfig('crowi', 'aws:customEndpoint'),
  535. bucket: crowi.configManager.getConfig('crowi', 'aws:bucket'),
  536. accessKeyId: crowi.configManager.getConfig('crowi', 'aws:accessKeyId'),
  537. secretAccessKey: crowi.configManager.getConfig('crowi', 'aws:secretAccessKey'),
  538. };
  539. return res.apiv3({ awsSettingParams });
  540. }
  541. catch (err) {
  542. const msg = 'Error occurred in updating aws setting';
  543. logger.error('Error', err);
  544. return res.apiv3Err(new ErrorV3(msg, 'update-awsSetting-failed'));
  545. }
  546. });
  547. /**
  548. * @swagger
  549. *
  550. * /app-settings/plugin-setting:
  551. * put:
  552. * tags: [AppSettings]
  553. * operationId: updateAppSettingPluginSetting
  554. * summary: /app-settings/plugin-setting
  555. * description: Update plugin setting
  556. * requestBody:
  557. * required: true
  558. * content:
  559. * application/json:
  560. * schema:
  561. * $ref: '#/components/schemas/PluginSettingParams'
  562. * responses:
  563. * 200:
  564. * description: Succeeded to update plugin setting
  565. * content:
  566. * application/json:
  567. * schema:
  568. * $ref: '#/components/schemas/PluginSettingParams'
  569. */
  570. router.put('/plugin-setting', loginRequiredStrictly, adminRequired, csrf, validator.pluginSetting, apiV3FormValidator, async(req, res) => {
  571. const requestPluginSettingParams = {
  572. 'plugin:isEnabledPlugins': req.body.isEnabledPlugins,
  573. };
  574. try {
  575. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestPluginSettingParams);
  576. const pluginSettingParams = {
  577. isEnabledPlugins: crowi.configManager.getConfig('crowi', 'plugin:isEnabledPlugins'),
  578. };
  579. return res.apiv3({ pluginSettingParams });
  580. }
  581. catch (err) {
  582. const msg = 'Error occurred in updating plugin setting';
  583. logger.error('Error', err);
  584. return res.apiv3Err(new ErrorV3(msg, 'update-pluginSetting-failed'));
  585. }
  586. });
  587. return router;
  588. };