app-settings.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. import { ErrorV3 } from '@growi/core/dist/models';
  2. import { body } from 'express-validator';
  3. import { i18n } from '^/config/next-i18next.config';
  4. import { SupportedAction } from '~/interfaces/activity';
  5. import { accessTokenParser } from '~/server/middlewares/access-token-parser';
  6. import { getTranslation } from '~/server/service/i18next';
  7. import loggerFactory from '~/utils/logger';
  8. import { generateAddActivityMiddleware } from '../../middlewares/add-activity';
  9. import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
  10. const logger = loggerFactory('growi:routes:apiv3:app-settings');
  11. const { pathUtils } = require('@growi/core/dist/utils');
  12. const express = require('express');
  13. const router = express.Router();
  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. * isEmailPublishedForNewUser:
  33. * type: boolean
  34. * description: default email show/hide setting when create user
  35. * fileUpload:
  36. * type: boolean
  37. * description: enable upload file except image file
  38. * SiteUrlSettingParams:
  39. * description: SiteUrlSettingParams
  40. * type: object
  41. * properties:
  42. * siteUrl:
  43. * type: string
  44. * description: Site URL. e.g. https://example.com, https://example.com:8080
  45. * envSiteUrl:
  46. * type: string
  47. * description: environment variable 'APP_SITE_URL'
  48. * MailSetting:
  49. * description: MailSettingParams
  50. * type: object
  51. * properties:
  52. * fromAddress:
  53. * type: string
  54. * description: e-mail address used as from address of mail which sent from GROWI app
  55. * transmissionMethod:
  56. * type: string
  57. * description: transmission method
  58. * SmtpSettingParams:
  59. * description: SmtpSettingParams
  60. * type: object
  61. * properties:
  62. * smtpHost:
  63. * type: string
  64. * description: host name of client's smtp server
  65. * smtpPort:
  66. * type: string
  67. * description: port of client's smtp server
  68. * smtpUser:
  69. * type: string
  70. * description: user name of client's smtp server
  71. * smtpPassword:
  72. * type: string
  73. * description: password of client's smtp server
  74. * SesSettingParams:
  75. * description: SesSettingParams
  76. * type: object
  77. * properties:
  78. * accessKeyId:
  79. * type: string
  80. * description: accesskey id for authentification of AWS
  81. * secretAccessKey:
  82. * type: string
  83. * description: secret key for authentification of AWS
  84. * FileUploadSettingParams:
  85. * description: FileUploadTypeParams
  86. * type: object
  87. * properties:
  88. * fileUploadType:
  89. * type: string
  90. * description: fileUploadType
  91. * s3Region:
  92. * type: string
  93. * description: region of AWS S3
  94. * s3CustomEndpoint:
  95. * type: string
  96. * description: custom endpoint of AWS S3
  97. * s3Bucket:
  98. * type: string
  99. * description: AWS S3 bucket name
  100. * s3AccessKeyId:
  101. * type: string
  102. * description: accesskey id for authentification of AWS
  103. * s3SecretAccessKey:
  104. * type: string
  105. * description: secret key for authentification of AWS
  106. * s3ReferenceFileWithRelayMode:
  107. * type: boolean
  108. * description: is enable internal stream system for s3 file request
  109. * gcsApiKeyJsonPath:
  110. * type: string
  111. * description: apiKeyJsonPath of gcp
  112. * gcsBucket:
  113. * type: string
  114. * description: bucket name of gcs
  115. * gcsUploadNamespace:
  116. * type: string
  117. * description: name space of gcs
  118. * gcsReferenceFileWithRelayMode:
  119. * type: boolean
  120. * description: is enable internal stream system for gcs file request
  121. * envGcsApiKeyJsonPath:
  122. * type: string
  123. * description: Path of the JSON file that contains service account key to authenticate to GCP API
  124. * envGcsBucket:
  125. * type: string
  126. * description: Name of the GCS bucket
  127. * envGcsUploadNamespace:
  128. * type: string
  129. * description: Directory name to create in the bucket
  130. * PluginSettingParams:
  131. * description: PluginSettingParams
  132. * type: object
  133. * properties:
  134. * isEnabledPlugins:
  135. * type: string
  136. * description: enable use plugins
  137. */
  138. module.exports = (crowi) => {
  139. const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  140. const adminRequired = require('../../middlewares/admin-required')(crowi);
  141. const addActivity = generateAddActivityMiddleware(crowi);
  142. const activityEvent = crowi.event('activity');
  143. const validator = {
  144. appSetting: [
  145. body('title').trim(),
  146. body('confidential'),
  147. body('globalLang').isIn(i18n.locales),
  148. body('isEmailPublishedForNewUser').isBoolean(),
  149. body('fileUpload').isBoolean(),
  150. ],
  151. siteUrlSetting: [
  152. // https://regex101.com/r/5Xef8V/1
  153. body('siteUrl').trim().matches(/^(https?:\/\/)/).isURL({ require_tld: false }),
  154. ],
  155. mailSetting: [
  156. body('fromAddress').trim().if(value => value !== '').isEmail(),
  157. body('transmissionMethod').isIn(['smtp', 'ses']),
  158. ],
  159. smtpSetting: [
  160. body('smtpHost').trim(),
  161. body('smtpPort').trim().if(value => value !== '').isPort(),
  162. body('smtpUser').trim(),
  163. body('smtpPassword').trim(),
  164. ],
  165. sesSetting: [
  166. body('sesAccessKeyId').trim().if(value => value !== '').matches(/^[\da-zA-Z]+$/),
  167. body('sesSecretAccessKey').trim(),
  168. ],
  169. fileUploadSetting: [
  170. body('fileUploadType').isIn(['aws', 'gcs', 'local', 'gridfs', 'azure']),
  171. body('gcsApiKeyJsonPath').trim(),
  172. body('gcsBucket').trim(),
  173. body('gcsUploadNamespace').trim(),
  174. body('gcsReferenceFileWithRelayMode').if(value => value != null).isBoolean(),
  175. body('s3Region')
  176. .trim()
  177. .if(value => value !== '')
  178. .custom(async(value) => {
  179. const { t } = await getTranslation();
  180. if (!/^[a-z]+-[a-z]+-\d+$/.test(value)) {
  181. throw new Error(t('validation.aws_region'));
  182. }
  183. return true;
  184. }),
  185. body('s3CustomEndpoint')
  186. .trim()
  187. .if(value => value !== '')
  188. .custom(async(value) => {
  189. const { t } = await getTranslation();
  190. if (!/^(https?:\/\/[^/]+|)$/.test(value)) {
  191. throw new Error(t('validation.aws_custom_endpoint'));
  192. }
  193. return true;
  194. }),
  195. body('s3Bucket').trim(),
  196. body('s3AccessKeyId').trim().if(value => value !== '').matches(/^[\da-zA-Z]+$/),
  197. body('s3SecretAccessKey').trim(),
  198. body('s3ReferenceFileWithRelayMode').if(value => value != null).isBoolean(),
  199. body('azureTenantId').trim(),
  200. body('azureClientId').trim(),
  201. body('azureClientSecret').trim(),
  202. body('azureStorageAccountName').trim(),
  203. body('azureStorageStorageName').trim(),
  204. body('azureReferenceFileWithRelayMode').if(value => value != null).isBoolean(),
  205. ],
  206. questionnaireSettings: [
  207. body('isQuestionnaireEnabled').isBoolean(),
  208. body('isAppSiteUrlHashed').isBoolean(),
  209. ],
  210. maintenanceMode: [
  211. body('flag').isBoolean(),
  212. ],
  213. };
  214. /**
  215. * @swagger
  216. *
  217. * /app-settings:
  218. * get:
  219. * tags: [AppSettings]
  220. * operationId: getAppSettings
  221. * summary: /app-settings
  222. * description: get app setting params
  223. * responses:
  224. * 200:
  225. * description: Resources are available
  226. * content:
  227. * application/json:
  228. * schema:
  229. * properties:
  230. * appSettingsParams:
  231. * type: object
  232. * description: app settings params
  233. */
  234. router.get('/', accessTokenParser, loginRequiredStrictly, adminRequired, async(req, res) => {
  235. const appSettingsParams = {
  236. title: crowi.configManager.getConfig('crowi', 'app:title'),
  237. confidential: crowi.configManager.getConfig('crowi', 'app:confidential'),
  238. globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
  239. isEmailPublishedForNewUser: crowi.configManager.getConfig('crowi', 'customize:isEmailPublishedForNewUser'),
  240. fileUpload: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
  241. isV5Compatible: crowi.configManager.getConfig('crowi', 'app:isV5Compatible'),
  242. siteUrl: crowi.configManager.getConfig('crowi', 'app:siteUrl'),
  243. siteUrlUseOnlyEnvVars: crowi.configManager.getConfig('crowi', 'app:siteUrl:useOnlyEnvVars'),
  244. envSiteUrl: crowi.configManager.getConfigFromEnvVars('crowi', 'app:siteUrl'),
  245. isMailerSetup: crowi.mailService.isMailerSetup,
  246. fromAddress: crowi.configManager.getConfig('crowi', 'mail:from'),
  247. transmissionMethod: crowi.configManager.getConfig('crowi', 'mail:transmissionMethod'),
  248. smtpHost: crowi.configManager.getConfig('crowi', 'mail:smtpHost'),
  249. smtpPort: crowi.configManager.getConfig('crowi', 'mail:smtpPort'),
  250. smtpUser: crowi.configManager.getConfig('crowi', 'mail:smtpUser'),
  251. smtpPassword: crowi.configManager.getConfig('crowi', 'mail:smtpPassword'),
  252. sesAccessKeyId: crowi.configManager.getConfig('crowi', 'mail:sesAccessKeyId'),
  253. sesSecretAccessKey: crowi.configManager.getConfig('crowi', 'mail:sesSecretAccessKey'),
  254. fileUploadType: crowi.configManager.getConfig('crowi', 'app:fileUploadType'),
  255. envFileUploadType: crowi.configManager.getConfigFromEnvVars('crowi', 'app:fileUploadType'),
  256. useOnlyEnvVarForFileUploadType: crowi.configManager.getConfig('crowi', 'app:useOnlyEnvVarForFileUploadType'),
  257. s3Region: crowi.configManager.getConfig('crowi', 'aws:s3Region'),
  258. s3CustomEndpoint: crowi.configManager.getConfig('crowi', 'aws:s3CustomEndpoint'),
  259. s3Bucket: crowi.configManager.getConfig('crowi', 'aws:s3Bucket'),
  260. s3AccessKeyId: crowi.configManager.getConfig('crowi', 'aws:s3AccessKeyId'),
  261. s3ReferenceFileWithRelayMode: crowi.configManager.getConfig('crowi', 'aws:referenceFileWithRelayMode'),
  262. gcsUseOnlyEnvVars: crowi.configManager.getConfig('crowi', 'gcs:useOnlyEnvVarsForSomeOptions'),
  263. gcsApiKeyJsonPath: crowi.configManager.getConfig('crowi', 'gcs:apiKeyJsonPath'),
  264. gcsBucket: crowi.configManager.getConfig('crowi', 'gcs:bucket'),
  265. gcsUploadNamespace: crowi.configManager.getConfig('crowi', 'gcs:uploadNamespace'),
  266. gcsReferenceFileWithRelayMode: crowi.configManager.getConfig('crowi', 'gcs:referenceFileWithRelayMode'),
  267. envGcsApiKeyJsonPath: crowi.configManager.getConfigFromEnvVars('crowi', 'gcs:apiKeyJsonPath'),
  268. envGcsBucket: crowi.configManager.getConfigFromEnvVars('crowi', 'gcs:bucket'),
  269. envGcsUploadNamespace: crowi.configManager.getConfigFromEnvVars('crowi', 'gcs:uploadNamespace'),
  270. azureUseOnlyEnvVars: crowi.configManager.getConfig('crowi', 'azure:useOnlyEnvVarsForSomeOptions'),
  271. azureTenantId: crowi.configManager.getConfigFromDB('crowi', 'azure:tenantId'),
  272. azureClientId: crowi.configManager.getConfigFromDB('crowi', 'azure:clientId'),
  273. azureClientSecret: crowi.configManager.getConfigFromDB('crowi', 'azure:clientSecret'),
  274. azureStorageAccountName: crowi.configManager.getConfigFromDB('crowi', 'azure:storageAccountName'),
  275. azureStorageContainerName: crowi.configManager.getConfigFromDB('crowi', 'azure:storageContainerName'),
  276. azureReferenceFileWithRelayMode: crowi.configManager.getConfig('crowi', 'azure:referenceFileWithRelayMode'),
  277. envAzureTenantId: crowi.configManager.getConfigFromEnvVars('crowi', 'azure:tenantId'),
  278. envAzureClientId: crowi.configManager.getConfigFromEnvVars('crowi', 'azure:clientId'),
  279. envAzureClientSecret: crowi.configManager.getConfigFromEnvVars('crowi', 'azure:clientSecret'),
  280. envAzureStorageAccountName: crowi.configManager.getConfigFromEnvVars('crowi', 'azure:storageAccountName'),
  281. envAzureStorageContainerName: crowi.configManager.getConfigFromEnvVars('crowi', 'azure:storageContainerName'),
  282. isEnabledPlugins: crowi.configManager.getConfig('crowi', 'plugin:isEnabledPlugins'),
  283. isQuestionnaireEnabled: crowi.configManager.getConfig('crowi', 'questionnaire:isQuestionnaireEnabled'),
  284. isAppSiteUrlHashed: crowi.configManager.getConfig('crowi', 'questionnaire:isAppSiteUrlHashed'),
  285. isMaintenanceMode: crowi.configManager.getConfig('crowi', 'app:isMaintenanceMode'),
  286. };
  287. return res.apiv3({ appSettingsParams });
  288. });
  289. /**
  290. * @swagger
  291. *
  292. * /app-settings/app-setting:
  293. * put:
  294. * tags: [AppSettings]
  295. * summary: /app-settings/app-setting
  296. * operationId: updateAppSettings
  297. * description: Update app setting
  298. * requestBody:
  299. * required: true
  300. * content:
  301. * application/json:
  302. * schema:
  303. * $ref: '#/components/schemas/AppSettingParams'
  304. * responses:
  305. * 200:
  306. * description: Succeeded to update app setting
  307. * content:
  308. * application/json:
  309. * schema:
  310. * $ref: '#/components/schemas/AppSettingParams'
  311. */
  312. router.put('/app-setting', loginRequiredStrictly, adminRequired, addActivity, validator.appSetting, apiV3FormValidator, async(req, res) => {
  313. const requestAppSettingParams = {
  314. 'app:title': req.body.title,
  315. 'app:confidential': req.body.confidential,
  316. 'app:globalLang': req.body.globalLang,
  317. 'customize:isEmailPublishedForNewUser': req.body.isEmailPublishedForNewUser,
  318. 'app:fileUpload': req.body.fileUpload,
  319. };
  320. try {
  321. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestAppSettingParams);
  322. const appSettingParams = {
  323. title: crowi.configManager.getConfig('crowi', 'app:title'),
  324. confidential: crowi.configManager.getConfig('crowi', 'app:confidential'),
  325. globalLang: crowi.configManager.getConfig('crowi', 'app:globalLang'),
  326. isEmailPublishedForNewUser: crowi.configManager.getConfig('crowi', 'customize:isEmailPublishedForNewUser'),
  327. fileUpload: crowi.configManager.getConfig('crowi', 'app:fileUpload'),
  328. };
  329. const parameters = { action: SupportedAction.ACTION_ADMIN_APP_SETTINGS_UPDATE };
  330. activityEvent.emit('update', res.locals.activity._id, parameters);
  331. return res.apiv3({ appSettingParams });
  332. }
  333. catch (err) {
  334. const msg = 'Error occurred in updating app setting';
  335. logger.error('Error', err);
  336. return res.apiv3Err(new ErrorV3(msg, 'update-appSetting-failed'));
  337. }
  338. });
  339. /**
  340. * @swagger
  341. *
  342. * /app-settings/site-url-setting:
  343. * put:
  344. * tags: [AppSettings]
  345. * operationId: updateAppSettingSiteUrlSetting
  346. * summary: /app-settings/site-url-setting
  347. * description: Update site url setting
  348. * requestBody:
  349. * required: true
  350. * content:
  351. * application/json:
  352. * schema:
  353. * $ref: '#/components/schemas/SiteUrlSettingParams'
  354. * responses:
  355. * 200:
  356. * description: Succeeded to update site url setting
  357. * content:
  358. * application/json:
  359. * schema:
  360. * $ref: '#/components/schemas/SiteUrlSettingParams'
  361. */
  362. router.put('/site-url-setting', loginRequiredStrictly, adminRequired, addActivity, validator.siteUrlSetting, apiV3FormValidator, async(req, res) => {
  363. const useOnlyEnvVars = crowi.configManager.getConfig('crowi', 'app:siteUrl:useOnlyEnvVars');
  364. if (useOnlyEnvVars) {
  365. const msg = 'Updating the Site URL is prohibited on this system.';
  366. return res.apiv3Err(new ErrorV3(msg, 'update-siteUrlSetting-prohibited'));
  367. }
  368. const requestSiteUrlSettingParams = {
  369. 'app:siteUrl': pathUtils.removeTrailingSlash(req.body.siteUrl),
  370. };
  371. try {
  372. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestSiteUrlSettingParams);
  373. const siteUrlSettingParams = {
  374. siteUrl: crowi.configManager.getConfig('crowi', 'app:siteUrl'),
  375. };
  376. const parameters = { action: SupportedAction.ACTION_ADMIN_SITE_URL_UPDATE };
  377. activityEvent.emit('update', res.locals.activity._id, parameters);
  378. return res.apiv3({ siteUrlSettingParams });
  379. }
  380. catch (err) {
  381. const msg = 'Error occurred in updating site url setting';
  382. logger.error('Error', err);
  383. return res.apiv3Err(new ErrorV3(msg, 'update-siteUrlSetting-failed'));
  384. }
  385. });
  386. /**
  387. * send mail (Promise wrapper)
  388. */
  389. async function sendMailPromiseWrapper(smtpClient, options) {
  390. return new Promise((resolve, reject) => {
  391. smtpClient.sendMail(options, (err, res) => {
  392. if (err) {
  393. reject(err);
  394. }
  395. else {
  396. resolve(res);
  397. }
  398. });
  399. });
  400. }
  401. /**
  402. * validate mail setting send test mail
  403. */
  404. async function sendTestEmail(destinationAddress) {
  405. const { configManager, mailService } = crowi;
  406. if (!mailService.isMailerSetup) {
  407. throw Error('mailService is not setup');
  408. }
  409. const fromAddress = configManager.getConfig('crowi', 'mail:from');
  410. if (fromAddress == null) {
  411. throw Error('fromAddress is not setup');
  412. }
  413. const smtpHost = configManager.getConfig('crowi', 'mail:smtpHost');
  414. const smtpPort = configManager.getConfig('crowi', 'mail:smtpPort');
  415. const smtpUser = configManager.getConfig('crowi', 'mail:smtpUser');
  416. const smtpPassword = configManager.getConfig('crowi', 'mail:smtpPassword');
  417. const option = {
  418. host: smtpHost,
  419. port: smtpPort,
  420. };
  421. if (smtpUser && smtpPassword) {
  422. option.auth = {
  423. user: smtpUser,
  424. pass: smtpPassword,
  425. };
  426. }
  427. if (option.port === 465) {
  428. option.secure = true;
  429. }
  430. const smtpClient = mailService.createSMTPClient(option);
  431. logger.debug('mailer setup for validate SMTP setting', smtpClient);
  432. const mailOptions = {
  433. from: fromAddress,
  434. to: destinationAddress,
  435. subject: 'Wiki管理設定のアップデートによるメール通知',
  436. text: 'このメールは、WikiのSMTP設定のアップデートにより送信されています。',
  437. };
  438. await sendMailPromiseWrapper(smtpClient, mailOptions);
  439. }
  440. const updateMailSettinConfig = async function(requestMailSettingParams) {
  441. const {
  442. configManager,
  443. mailService,
  444. } = crowi;
  445. // update config without publishing S2sMessage
  446. await configManager.updateConfigsInTheSameNamespace('crowi', requestMailSettingParams, true);
  447. await mailService.initialize();
  448. mailService.publishUpdatedMessage();
  449. return {
  450. isMailerSetup: mailService.isMailerSetup,
  451. fromAddress: configManager.getConfig('crowi', 'mail:from'),
  452. smtpHost: configManager.getConfig('crowi', 'mail:smtpHost'),
  453. smtpPort: configManager.getConfig('crowi', 'mail:smtpPort'),
  454. smtpUser: configManager.getConfig('crowi', 'mail:smtpUser'),
  455. smtpPassword: configManager.getConfig('crowi', 'mail:smtpPassword'),
  456. sesAccessKeyId: configManager.getConfig('crowi', 'mail:sesAccessKeyId'),
  457. sesSecretAccessKey: configManager.getConfig('crowi', 'mail:sesSecretAccessKey'),
  458. };
  459. };
  460. /**
  461. * @swagger
  462. *
  463. * /app-settings/smtp-setting:
  464. * put:
  465. * tags: [AppSettings]
  466. * operationId: updateAppSettingSmtpSetting
  467. * summary: /app-settings/smtp-setting
  468. * description: Update smtp setting
  469. * requestBody:
  470. * required: true
  471. * content:
  472. * application/json:
  473. * schema:
  474. * $ref: '#/components/schemas/SmtpSettingParams'
  475. * responses:
  476. * 200:
  477. * description: Succeeded to update smtp setting
  478. * content:
  479. * application/json:
  480. * schema:
  481. * $ref: '#/components/schemas/SmtpSettingParams'
  482. */
  483. router.put('/smtp-setting', loginRequiredStrictly, adminRequired, addActivity, validator.smtpSetting, apiV3FormValidator, async(req, res) => {
  484. const requestMailSettingParams = {
  485. 'mail:from': req.body.fromAddress,
  486. 'mail:transmissionMethod': req.body.transmissionMethod,
  487. 'mail:smtpHost': req.body.smtpHost,
  488. 'mail:smtpPort': req.body.smtpPort,
  489. 'mail:smtpUser': req.body.smtpUser,
  490. 'mail:smtpPassword': req.body.smtpPassword,
  491. };
  492. try {
  493. const mailSettingParams = await updateMailSettinConfig(requestMailSettingParams);
  494. const parameters = { action: SupportedAction.ACTION_ADMIN_MAIL_SMTP_UPDATE };
  495. activityEvent.emit('update', res.locals.activity._id, parameters);
  496. return res.apiv3({ mailSettingParams });
  497. }
  498. catch (err) {
  499. const msg = 'Error occurred in updating smtp setting';
  500. logger.error('Error', err);
  501. return res.apiv3Err(new ErrorV3(msg, 'update-smtpSetting-failed'));
  502. }
  503. });
  504. /**
  505. * @swagger
  506. *
  507. * /app-settings/smtp-test:
  508. * post:
  509. * tags: [AppSettings]
  510. * operationId: postSmtpTest
  511. * summary: /app-settings/smtp-setting
  512. * description: Send test mail for smtp
  513. * responses:
  514. * 200:
  515. * description: Succeeded to send test mail for smtp
  516. */
  517. router.post('/smtp-test', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
  518. const { t } = await getTranslation(req.user.lang);
  519. try {
  520. await sendTestEmail(req.user.email);
  521. const parameters = { action: SupportedAction.ACTION_ADMIN_MAIL_TEST_SUBMIT };
  522. activityEvent.emit('update', res.locals.activity._id, parameters);
  523. return res.apiv3({});
  524. }
  525. catch (err) {
  526. const msg = t('validation.failed_to_send_a_test_email');
  527. logger.error('Error', err);
  528. logger.debug('Error validate mail setting: ', err);
  529. return res.apiv3Err(new ErrorV3(msg, 'send-email-with-smtp-failed'));
  530. }
  531. });
  532. /**
  533. * @swagger
  534. *
  535. * /app-settings/ses-setting:
  536. * put:
  537. * tags: [AppSettings]
  538. * operationId: updateAppSettingSesSetting
  539. * summary: /app-settings/ses-setting
  540. * description: Update ses setting
  541. * requestBody:
  542. * required: true
  543. * content:
  544. * application/json:
  545. * schema:
  546. * $ref: '#/components/schemas/SesSettingParams'
  547. * responses:
  548. * 200:
  549. * description: Succeeded to update ses setting
  550. * content:
  551. * application/json:
  552. * schema:
  553. * $ref: '#/components/schemas/SesSettingParams'
  554. */
  555. router.put('/ses-setting', loginRequiredStrictly, adminRequired, addActivity, validator.sesSetting, apiV3FormValidator, async(req, res) => {
  556. const { mailService } = crowi;
  557. const requestSesSettingParams = {
  558. 'mail:from': req.body.fromAddress,
  559. 'mail:transmissionMethod': req.body.transmissionMethod,
  560. 'mail:sesAccessKeyId': req.body.sesAccessKeyId,
  561. 'mail:sesSecretAccessKey': req.body.sesSecretAccessKey,
  562. };
  563. let mailSettingParams;
  564. try {
  565. mailSettingParams = await updateMailSettinConfig(requestSesSettingParams);
  566. }
  567. catch (err) {
  568. const msg = 'Error occurred in updating ses setting';
  569. logger.error('Error', err);
  570. return res.apiv3Err(new ErrorV3(msg, 'update-ses-setting-failed'));
  571. }
  572. await mailService.initialize();
  573. mailService.publishUpdatedMessage();
  574. const parameters = { action: SupportedAction.ACTION_ADMIN_MAIL_SES_UPDATE };
  575. activityEvent.emit('update', res.locals.activity._id, parameters);
  576. return res.apiv3({ mailSettingParams });
  577. });
  578. /**
  579. * @swagger
  580. *
  581. * /app-settings/file-upload-settings:
  582. * put:
  583. * tags: [AppSettings]
  584. * operationId: updateAppSettingFileUploadSetting
  585. * summary: /app-settings/file-upload-setting
  586. * description: Update fileUploadSetting
  587. * requestBody:
  588. * required: true
  589. * content:
  590. * application/json:
  591. * schema:
  592. * $ref: '#/components/schemas/FileUploadSettingParams'
  593. * responses:
  594. * 200:
  595. * description: Succeeded to update fileUploadSetting
  596. * content:
  597. * application/json:
  598. * schema:
  599. * $ref: '#/components/schemas/FileUploadSettingParams'
  600. */
  601. // eslint-disable-next-line max-len
  602. router.put('/file-upload-setting', loginRequiredStrictly, adminRequired, addActivity, validator.fileUploadSetting, apiV3FormValidator, async(req, res) => {
  603. const { fileUploadType } = req.body;
  604. const requestParams = {
  605. 'app:fileUploadType': fileUploadType,
  606. };
  607. if (fileUploadType === 'gcs') {
  608. requestParams['gcs:apiKeyJsonPath'] = req.body.gcsApiKeyJsonPath;
  609. requestParams['gcs:bucket'] = req.body.gcsBucket;
  610. requestParams['gcs:uploadNamespace'] = req.body.gcsUploadNamespace;
  611. requestParams['gcs:referenceFileWithRelayMode'] = req.body.gcsReferenceFileWithRelayMode;
  612. }
  613. if (fileUploadType === 'aws') {
  614. requestParams['aws:s3Region'] = req.body.s3Region;
  615. requestParams['aws:s3CustomEndpoint'] = req.body.s3CustomEndpoint;
  616. requestParams['aws:s3Bucket'] = req.body.s3Bucket;
  617. requestParams['aws:s3AccessKeyId'] = req.body.s3AccessKeyId;
  618. requestParams['aws:referenceFileWithRelayMode'] = req.body.s3ReferenceFileWithRelayMode;
  619. }
  620. if (fileUploadType === 'azure') {
  621. requestParams['azure:tenantId'] = req.body.azureTenantId;
  622. requestParams['azure:clientId'] = req.body.azureClientId;
  623. requestParams['azure:clientSecret'] = req.body.azureClientSecret;
  624. requestParams['azure:storageAccountName'] = req.body.azureStorageAccountName;
  625. requestParams['azure:storageContainerName'] = req.body.azureStorageContainerName;
  626. requestParams['azure:referenceFileWithRelayMode'] = req.body.azureReferenceFileWithRelayMode;
  627. }
  628. try {
  629. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams, true);
  630. const s3SecretAccessKey = req.body.s3SecretAccessKey;
  631. if (fileUploadType === 'aws' && s3SecretAccessKey != null && s3SecretAccessKey.trim() !== '') {
  632. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', { 'aws:s3SecretAccessKey': s3SecretAccessKey }, true);
  633. }
  634. await crowi.setUpFileUpload(true);
  635. crowi.fileUploaderSwitchService.publishUpdatedMessage();
  636. const responseParams = {
  637. fileUploadType: crowi.configManager.getConfig('crowi', 'app:fileUploadType'),
  638. };
  639. if (fileUploadType === 'gcs') {
  640. responseParams.gcsApiKeyJsonPath = crowi.configManager.getConfig('crowi', 'gcs:apiKeyJsonPath');
  641. responseParams.gcsBucket = crowi.configManager.getConfig('crowi', 'gcs:bucket');
  642. responseParams.gcsUploadNamespace = crowi.configManager.getConfig('crowi', 'gcs:uploadNamespace');
  643. responseParams.gcsReferenceFileWithRelayMode = crowi.configManager.getConfig('crowi', 'gcs:referenceFileWithRelayMode ');
  644. }
  645. if (fileUploadType === 'aws') {
  646. responseParams.s3Region = crowi.configManager.getConfig('crowi', 'aws:s3Region');
  647. responseParams.s3CustomEndpoint = crowi.configManager.getConfig('crowi', 'aws:s3CustomEndpoint');
  648. responseParams.s3Bucket = crowi.configManager.getConfig('crowi', 'aws:s3Bucket');
  649. responseParams.s3AccessKeyId = crowi.configManager.getConfig('crowi', 'aws:s3AccessKeyId');
  650. responseParams.s3ReferenceFileWithRelayMode = crowi.configManager.getConfig('crowi', 'aws:referenceFileWithRelayMode');
  651. }
  652. if (fileUploadType === 'azure') {
  653. responseParams.azureTenantId = crowi.configManager.getConfig('crowi', 'azure:tenantId');
  654. responseParams.azureClientId = crowi.configManager.getConfig('crowi', 'azure:clientId');
  655. responseParams.azureClientSecret = crowi.configManager.getConfig('crowi', 'azure:clientSecret');
  656. responseParams.azureStorageAccountName = crowi.configManager.getConfig('crowi', 'azure:storageAccountName');
  657. responseParams.azureStorageContainerName = crowi.configManager.getConfig('crowi', 'azure:storageContainerName');
  658. responseParams.azureReferenceFileWithRelayMode = crowi.configManager.getConfig('crowi', 'azure:referenceFileWithRelayMode');
  659. }
  660. const parameters = { action: SupportedAction.ACTION_ADMIN_FILE_UPLOAD_CONFIG_UPDATE };
  661. activityEvent.emit('update', res.locals.activity._id, parameters);
  662. return res.apiv3({ responseParams });
  663. }
  664. catch (err) {
  665. const msg = 'Error occurred in updating fileUploadType';
  666. logger.error('Error', err);
  667. return res.apiv3Err(new ErrorV3(msg, 'update-fileUploadType-failed'));
  668. }
  669. });
  670. // eslint-disable-next-line max-len
  671. router.put('/questionnaire-settings', loginRequiredStrictly, adminRequired, addActivity, validator.questionnaireSettings, apiV3FormValidator, async(req, res) => {
  672. const { isQuestionnaireEnabled, isAppSiteUrlHashed } = req.body;
  673. const requestParams = {
  674. 'questionnaire:isQuestionnaireEnabled': isQuestionnaireEnabled,
  675. 'questionnaire:isAppSiteUrlHashed': isAppSiteUrlHashed,
  676. };
  677. try {
  678. await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams, true);
  679. const responseParams = {
  680. isQuestionnaireEnabled: crowi.configManager.getConfig('crowi', 'questionnaire:isQuestionnaireEnabled'),
  681. isAppSiteUrlHashed: crowi.configManager.getConfig('crowi', 'questionnaire:isAppSiteUrlHashed'),
  682. };
  683. const parameters = { action: SupportedAction.ACTION_ADMIN_QUESTIONNAIRE_SETTINGS_UPDATE };
  684. activityEvent.emit('update', res.locals.activity._id, parameters);
  685. return res.apiv3({ responseParams });
  686. }
  687. catch (err) {
  688. const msg = 'Error occurred in updating questionnaire settings';
  689. logger.error('Error', err);
  690. return res.apiv3Err(new ErrorV3(msg, 'update-questionnaire-settings-failed'));
  691. }
  692. });
  693. router.post('/v5-schema-migration', accessTokenParser, loginRequiredStrictly, adminRequired, async(req, res) => {
  694. const isMaintenanceMode = crowi.appService.isMaintenanceMode();
  695. if (!isMaintenanceMode) {
  696. return res.apiv3Err(new ErrorV3('GROWI is not maintenance mode. To import data, please activate the maintenance mode first.', 'not_maintenance_mode'));
  697. }
  698. const isV5Compatible = crowi.configManager.getConfig('crowi', 'app:isV5Compatible');
  699. try {
  700. if (!isV5Compatible) {
  701. // This method throws and emit socketIo event when error occurs
  702. crowi.pageService.normalizeAllPublicPages();
  703. }
  704. }
  705. catch (err) {
  706. return res.apiv3Err(new ErrorV3(`Failed to migrate pages: ${err.message}`), 500);
  707. }
  708. return res.apiv3({ isV5Compatible });
  709. });
  710. // eslint-disable-next-line max-len
  711. router.post('/maintenance-mode', accessTokenParser, loginRequiredStrictly, adminRequired, addActivity, validator.maintenanceMode, apiV3FormValidator, async(req, res) => {
  712. const { flag } = req.body;
  713. const parameters = {};
  714. try {
  715. if (flag) {
  716. await crowi.appService.startMaintenanceMode();
  717. Object.assign(parameters, { action: SupportedAction.ACTION_ADMIN_MAINTENANCEMODE_ENABLED });
  718. }
  719. else {
  720. await crowi.appService.endMaintenanceMode();
  721. Object.assign(parameters, { action: SupportedAction.ACTION_ADMIN_MAINTENANCEMODE_DISABLED });
  722. }
  723. }
  724. catch (err) {
  725. logger.error(err);
  726. if (flag) {
  727. res.apiv3Err(new ErrorV3('Failed to start maintenance mode', 'failed_to_start_maintenance_mode'), 500);
  728. }
  729. else {
  730. res.apiv3Err(new ErrorV3('Failed to end maintenance mode', 'failed_to_end_maintenance_mode'), 500);
  731. }
  732. }
  733. if ('action' in parameters) {
  734. activityEvent.emit('update', res.locals.activity._id, parameters);
  735. }
  736. res.apiv3({ flag });
  737. });
  738. return router;
  739. };