app-settings.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  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. * MailSettingParams:
  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. * smtpHost:
  53. * type: string
  54. * description: host name of client's smtp server
  55. * smtpPort:
  56. * type: string
  57. * description: port of client's smtp server
  58. * smtpUser:
  59. * type: string
  60. * description: user name of client's smtp server
  61. * smtpPassword:
  62. * type: string
  63. * description: password of client's smtp server
  64. * AwsSettingParams:
  65. * description: AwsSettingParams
  66. * type: object
  67. * properties:
  68. * region:
  69. * type: string
  70. * description: region of AWS S3
  71. * customEndpoint:
  72. * type: string
  73. * description: custom endpoint of AWS S3
  74. * bucket:
  75. * type: string
  76. * description: AWS S3 bucket name
  77. * accessKeyId:
  78. * type: string
  79. * description: accesskey id for authentification of AWS
  80. * secretAccessKey:
  81. * type: string
  82. * description: secret key for authentification of AWS
  83. * PluginSettingParams:
  84. * description: PluginSettingParams
  85. * type: object
  86. * properties:
  87. * isEnabledPlugins:
  88. * type: string
  89. * description: enable use plugins
  90. */
  91. module.exports = (crowi) => {
  92. const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
  93. const loginRequired = require('../../middlewares/login-required')(crowi);
  94. const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  95. const adminRequired = require('../../middlewares/admin-required')(crowi);
  96. const csrf = require('../../middlewares/csrf')(crowi);
  97. const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
  98. const validator = {
  99. appSetting: [
  100. body('title').trim(),
  101. body('confidential'),
  102. body('globalLang').isIn(listLocaleIds()),
  103. body('fileUpload').isBoolean(),
  104. ],
  105. siteUrlSetting: [
  106. body('siteUrl').trim().matches(/^(https?:\/\/[^/]+|)$/).isURL({ require_tld: false }),
  107. ],
  108. mailSetting: [
  109. body('fromAddress').trim().isEmail(),
  110. body('smtpHost').trim(),
  111. body('smtpPort').trim().isPort(),
  112. body('smtpUser').trim(),
  113. body('smtpPassword').trim(),
  114. ],
  115. awsSetting: [
  116. body('region').trim().matches(/^[a-z]+-[a-z]+-\d+$/).withMessage('リージョンには、AWSリージョン名を入力してください。 例: ap-northeast-1'),
  117. body('customEndpoint').trim().matches(/^(https?:\/\/[^/]+|)$/).withMessage('カスタムエンドポイントは、http(s)://で始まるURLを指定してください。また、末尾の/は不要です。'),
  118. body('bucket').trim(),
  119. body('accessKeyId').trim().matches(/^[\da-zA-Z]+$/),
  120. body('secretAccessKey').trim(),
  121. ],
  122. pluginSetting: [
  123. body('isEnabledPlugins').isBoolean(),
  124. ],
  125. };
  126. /**
  127. * @swagger
  128. *
  129. * /app-settings:
  130. * get:
  131. * tags: [AppSettings]
  132. * operationId: getAppSettings
  133. * summary: /app-settings
  134. * description: get app setting params
  135. * responses:
  136. * 200:
  137. * description: Resources are available
  138. * content:
  139. * application/json:
  140. * schema:
  141. * properties:
  142. * appSettingsParams:
  143. * type: object
  144. * description: app settings params
  145. */
  146. router.get('/', accessTokenParser, loginRequired, adminRequired, async(req, res) => {
  147. const appSettingsParams = {
  148. title: crowi.configManager.getConfig('crowi', 'app:title'),
  149. confidential: crowi.configManager.getConfig('crowi', 'app:confidential'),
  150. globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
  151. fileUpload: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
  152. siteUrl: crowi.configManager.getConfig('crowi', 'app:siteUrl'),
  153. envSiteUrl: crowi.configManager.getConfigFromEnvVars('crowi', 'app:siteUrl'),
  154. fromAddress: crowi.configManager.getConfig('crowi', 'mail:from'),
  155. smtpHost: crowi.configManager.getConfig('crowi', 'mail:smtpHost'),
  156. smtpPort: crowi.configManager.getConfig('crowi', 'mail:smtpPort'),
  157. smtpUser: crowi.configManager.getConfig('crowi', 'mail:smtpUser'),
  158. smtpPassword: crowi.configManager.getConfig('crowi', 'mail:smtpPassword'),
  159. region: crowi.configManager.getConfig('crowi', 'aws:region'),
  160. customEndpoint: crowi.configManager.getConfig('crowi', 'aws:customEndpoint'),
  161. bucket: crowi.configManager.getConfig('crowi', 'aws:bucket'),
  162. accessKeyId: crowi.configManager.getConfig('crowi', 'aws:accessKeyId'),
  163. secretAccessKey: crowi.configManager.getConfig('crowi', 'aws:secretAccessKey'),
  164. isEnabledPlugins: crowi.configManager.getConfig('crowi', 'plugin:isEnabledPlugins'),
  165. };
  166. return res.apiv3({ appSettingsParams });
  167. });
  168. /**
  169. * @swagger
  170. *
  171. * /app-settings/app-setting:
  172. * put:
  173. * tags: [AppSettings]
  174. * summary: /app-settings/app-setting
  175. * operationId: updateAppSettings
  176. * description: Update app setting
  177. * requestBody:
  178. * required: true
  179. * content:
  180. * application/json:
  181. * schema:
  182. * $ref: '#/components/schemas/AppSettingParams'
  183. * responses:
  184. * 200:
  185. * description: Succeeded to update app setting
  186. * content:
  187. * application/json:
  188. * schema:
  189. * $ref: '#/components/schemas/AppSettingParams'
  190. */
  191. router.put('/app-setting', loginRequiredStrictly, adminRequired, csrf, validator.appSetting, apiV3FormValidator, async(req, res) => {
  192. const requestAppSettingParams = {
  193. 'app:title': req.body.title,
  194. 'app:confidential': req.body.confidential,
  195. 'app:globalLang': req.body.globalLang,
  196. 'app:fileUpload': req.body.fileUpload,
  197. };
  198. try {
  199. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestAppSettingParams);
  200. const appSettingParams = {
  201. title: crowi.configManager.getConfig('crowi', 'app:title'),
  202. confidential: crowi.configManager.getConfig('crowi', 'app:confidential'),
  203. globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
  204. fileUpload: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
  205. };
  206. return res.apiv3({ appSettingParams });
  207. }
  208. catch (err) {
  209. const msg = 'Error occurred in updating app setting';
  210. logger.error('Error', err);
  211. return res.apiv3Err(new ErrorV3(msg, 'update-appSetting-failed'));
  212. }
  213. });
  214. /**
  215. * @swagger
  216. *
  217. * /app-settings/site-url-setting:
  218. * put:
  219. * tags: [AppSettings]
  220. * operationId: updateAppSettingSiteUrlSetting
  221. * summary: /app-settings/site-url-setting
  222. * description: Update site url setting
  223. * requestBody:
  224. * required: true
  225. * content:
  226. * application/json:
  227. * schema:
  228. * $ref: '#/components/schemas/SiteUrlSettingParams'
  229. * responses:
  230. * 200:
  231. * description: Succeeded to update site url setting
  232. * content:
  233. * application/json:
  234. * schema:
  235. * $ref: '#/components/schemas/SiteUrlSettingParams'
  236. */
  237. router.put('/site-url-setting', loginRequiredStrictly, adminRequired, csrf, validator.siteUrlSetting, apiV3FormValidator, async(req, res) => {
  238. const requestSiteUrlSettingParams = {
  239. 'app:siteUrl': req.body.siteUrl,
  240. };
  241. try {
  242. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestSiteUrlSettingParams);
  243. const siteUrlSettingParams = {
  244. siteUrl: crowi.configManager.getConfig('crowi', 'app:siteUrl'),
  245. };
  246. return res.apiv3({ siteUrlSettingParams });
  247. }
  248. catch (err) {
  249. const msg = 'Error occurred in updating site url setting';
  250. logger.error('Error', err);
  251. return res.apiv3Err(new ErrorV3(msg, 'update-siteUrlSetting-failed'));
  252. }
  253. });
  254. /**
  255. * send mail (Promise wrapper)
  256. */
  257. async function sendMailPromiseWrapper(smtpClient, options) {
  258. return new Promise((resolve, reject) => {
  259. smtpClient.sendMail(options, (err, res) => {
  260. if (err) {
  261. reject(err);
  262. }
  263. else {
  264. resolve(res);
  265. }
  266. });
  267. });
  268. }
  269. /**
  270. * validate mail setting send test mail
  271. */
  272. async function validateMailSetting(req) {
  273. const { mailService } = crowi;
  274. const option = {
  275. host: req.body.smtpHost,
  276. port: req.body.smtpPort,
  277. };
  278. if (req.body.smtpUser && req.body.smtpPassword) {
  279. option.auth = {
  280. user: req.body.smtpUser,
  281. pass: req.body.smtpPassword,
  282. };
  283. }
  284. if (option.port === 465) {
  285. option.secure = true;
  286. }
  287. const smtpClient = mailService.createSMTPClient(option);
  288. debug('mailer setup for validate SMTP setting', smtpClient);
  289. const mailOptions = {
  290. from: req.body.fromAddress,
  291. to: req.user.email,
  292. subject: 'Wiki管理設定のアップデートによるメール通知',
  293. text: 'このメールは、WikiのSMTP設定のアップデートにより送信されています。',
  294. };
  295. await sendMailPromiseWrapper(smtpClient, mailOptions);
  296. }
  297. const updateMailSettinConfig = async function(requestMailSettingParams) {
  298. const {
  299. configManager,
  300. mailService,
  301. } = crowi;
  302. // update config without publishing S2sMessage
  303. await configManager.updateConfigsInTheSameNamespace('crowi', requestMailSettingParams, true);
  304. await mailService.initialize();
  305. mailService.publishUpdatedMessage();
  306. return {
  307. fromAddress: configManager.getConfig('crowi', 'mail:from'),
  308. smtpHost: configManager.getConfig('crowi', 'mail:smtpHost'),
  309. smtpPort: configManager.getConfig('crowi', 'mail:smtpPort'),
  310. smtpUser: configManager.getConfig('crowi', 'mail:smtpUser'),
  311. smtpPassword: configManager.getConfig('crowi', 'mail:smtpPassword'),
  312. };
  313. };
  314. /**
  315. * @swagger
  316. *
  317. * /app-settings/mail-setting:
  318. * put:
  319. * tags: [AppSettings]
  320. * operationId: updateAppSettingMailSetting
  321. * summary: /app-settings/mail-setting
  322. * description: Update mail setting
  323. * requestBody:
  324. * required: true
  325. * content:
  326. * application/json:
  327. * schema:
  328. * $ref: '#/components/schemas/MailSettingParams'
  329. * responses:
  330. * 200:
  331. * description: Succeeded to update mail setting
  332. * content:
  333. * application/json:
  334. * schema:
  335. * $ref: '#/components/schemas/MailSettingParams'
  336. */
  337. router.put('/mail-setting', loginRequiredStrictly, adminRequired, csrf, validator.mailSetting, apiV3FormValidator, async(req, res) => {
  338. try {
  339. await validateMailSetting(req);
  340. }
  341. catch (err) {
  342. const msg = 'SMTPを利用したテストメール送信に失敗しました。設定をみなおしてください。';
  343. logger.error('Error', err);
  344. debug('Error validate mail setting: ', err);
  345. return res.apiv3Err(new ErrorV3(msg, 'update-mailSetting-failed'));
  346. }
  347. const requestMailSettingParams = {
  348. 'mail:from': req.body.fromAddress,
  349. 'mail:smtpHost': req.body.smtpHost,
  350. 'mail:smtpPort': req.body.smtpPort,
  351. 'mail:smtpUser': req.body.smtpUser,
  352. 'mail:smtpPassword': req.body.smtpPassword,
  353. };
  354. try {
  355. const mailSettingParams = await updateMailSettinConfig(requestMailSettingParams);
  356. return res.apiv3({ mailSettingParams });
  357. }
  358. catch (err) {
  359. const msg = 'Error occurred in updating mail setting';
  360. logger.error('Error', err);
  361. return res.apiv3Err(new ErrorV3(msg, 'update-mailSetting-failed'));
  362. }
  363. });
  364. /**
  365. * @swagger
  366. *
  367. * /app-settings/mail-setting:
  368. * delete:
  369. * tags: [AppSettings]
  370. * operationId: deleteAppSettingMailSetting
  371. * summary: /app-settings/mail-setting
  372. * description: delete mail setting
  373. * requestBody:
  374. * required: true
  375. * content:
  376. * application/json:
  377. * schema:
  378. * $ref: '#/components/schemas/MailSettingParams'
  379. * responses:
  380. * 200:
  381. * description: Succeeded to delete mail setting
  382. * content:
  383. * application/json:
  384. * schema:
  385. * $ref: '#/components/schemas/MailSettingParams'
  386. */
  387. router.delete('/mail-setting', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
  388. const requestMailSettingParams = {
  389. 'mail:from': null,
  390. 'mail:smtpHost': null,
  391. 'mail:smtpPort': null,
  392. 'mail:smtpUser': null,
  393. 'mail:smtpPassword': null,
  394. };
  395. try {
  396. const mailSettingParams = await updateMailSettinConfig(requestMailSettingParams);
  397. return res.apiv3({ mailSettingParams });
  398. }
  399. catch (err) {
  400. const msg = 'Error occurred in initializing mail setting';
  401. logger.error('Error', err);
  402. return res.apiv3Err(new ErrorV3(msg, 'initialize-mailSetting-failed'));
  403. }
  404. });
  405. /**
  406. * @swagger
  407. *
  408. * /app-settings/aws-setting:
  409. * put:
  410. * tags: [AppSettings]
  411. * operationId: updateAppSettingAwsSetting
  412. * summary: /app-settings/aws-setting
  413. * description: Update aws setting
  414. * requestBody:
  415. * required: true
  416. * content:
  417. * application/json:
  418. * schema:
  419. * $ref: '#/components/schemas/AwsSettingParams'
  420. * responses:
  421. * 200:
  422. * description: Succeeded to update aws setting
  423. * content:
  424. * application/json:
  425. * schema:
  426. * $ref: '#/components/schemas/AwsSettingParams'
  427. */
  428. router.put('/aws-setting', loginRequiredStrictly, adminRequired, csrf, validator.awsSetting, apiV3FormValidator, async(req, res) => {
  429. const requestAwsSettingParams = {
  430. 'aws:region': req.body.region,
  431. 'aws:customEndpoint': req.body.customEndpoint,
  432. 'aws:bucket': req.body.bucket,
  433. 'aws:accessKeyId': req.body.accessKeyId,
  434. 'aws:secretAccessKey': req.body.secretAccessKey,
  435. };
  436. try {
  437. const { configManager, mailService } = crowi;
  438. // update config without publishing S2sMessage
  439. await configManager.updateConfigsInTheSameNamespace('crowi', requestAwsSettingParams, true);
  440. await mailService.initialize();
  441. mailService.publishUpdatedMessage();
  442. const awsSettingParams = {
  443. region: crowi.configManager.getConfig('crowi', 'aws:region'),
  444. customEndpoint: crowi.configManager.getConfig('crowi', 'aws:customEndpoint'),
  445. bucket: crowi.configManager.getConfig('crowi', 'aws:bucket'),
  446. accessKeyId: crowi.configManager.getConfig('crowi', 'aws:accessKeyId'),
  447. secretAccessKey: crowi.configManager.getConfig('crowi', 'aws:secretAccessKey'),
  448. };
  449. return res.apiv3({ awsSettingParams });
  450. }
  451. catch (err) {
  452. const msg = 'Error occurred in updating aws setting';
  453. logger.error('Error', err);
  454. return res.apiv3Err(new ErrorV3(msg, 'update-awsSetting-failed'));
  455. }
  456. });
  457. /**
  458. * @swagger
  459. *
  460. * /app-settings/plugin-setting:
  461. * put:
  462. * tags: [AppSettings]
  463. * operationId: updateAppSettingPluginSetting
  464. * summary: /app-settings/plugin-setting
  465. * description: Update plugin setting
  466. * requestBody:
  467. * required: true
  468. * content:
  469. * application/json:
  470. * schema:
  471. * $ref: '#/components/schemas/PluginSettingParams'
  472. * responses:
  473. * 200:
  474. * description: Succeeded to update plugin setting
  475. * content:
  476. * application/json:
  477. * schema:
  478. * $ref: '#/components/schemas/PluginSettingParams'
  479. */
  480. router.put('/plugin-setting', loginRequiredStrictly, adminRequired, csrf, validator.pluginSetting, apiV3FormValidator, async(req, res) => {
  481. const requestPluginSettingParams = {
  482. 'plugin:isEnabledPlugins': req.body.isEnabledPlugins,
  483. };
  484. try {
  485. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestPluginSettingParams);
  486. const pluginSettingParams = {
  487. isEnabledPlugins: crowi.configManager.getConfig('crowi', 'plugin:isEnabledPlugins'),
  488. };
  489. return res.apiv3({ pluginSettingParams });
  490. }
  491. catch (err) {
  492. const msg = 'Error occurred in updating plugin setting';
  493. logger.error('Error', err);
  494. return res.apiv3Err(new ErrorV3(msg, 'update-pluginSetting-failed'));
  495. }
  496. });
  497. return router;
  498. };