index.ts 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123
  1. import { ConfigSource, SCOPE } from '@growi/core/dist/interfaces';
  2. import { ErrorV3 } from '@growi/core/dist/models';
  3. import { body } from 'express-validator';
  4. import { i18n } from '^/config/next-i18next.config';
  5. import { SupportedAction } from '~/interfaces/activity';
  6. import { accessTokenParser } from '~/server/middlewares/access-token-parser';
  7. import { configManager } from '~/server/service/config-manager';
  8. import { getTranslation } from '~/server/service/i18next';
  9. import loggerFactory from '~/utils/logger';
  10. import { generateAddActivityMiddleware } from '../../../middlewares/add-activity';
  11. import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';
  12. const logger = loggerFactory('growi:routes:apiv3:app-settings');
  13. const { pathUtils } = require('@growi/core/dist/utils');
  14. const express = require('express');
  15. const router = express.Router();
  16. /**
  17. * @swagger
  18. *
  19. * components:
  20. * schemas:
  21. * AppSettingParams:
  22. * description: AppSettingParams
  23. * type: object
  24. * properties:
  25. * azureReferenceFileWithRelayMode:
  26. * type: boolean
  27. * example: false
  28. * azureUseOnlyEnvVars:
  29. * type: boolean
  30. * example: false
  31. * confidential:
  32. * type: string
  33. * description: confidential show on page header
  34. * example: 'GROWI'
  35. * envAzureClientId:
  36. * type: string
  37. * example: 'AZURE_CLIENT_ID'
  38. * envAzureClientSecret:
  39. * type: string
  40. * example: 'AZURE_CLIENT_SECRET'
  41. * envAzureStorageAccountName:
  42. * type: string
  43. * example: 'AZURE_STORAGE_ACCOUNT_NAME'
  44. * envAzureStorageContainerName:
  45. * type: string
  46. * example: 'AZURE_STORAGE_CONTAINER_NAME'
  47. * envFileUploadType:
  48. * type: string
  49. * example: 'mongodb'
  50. * envGcsApiKeyJsonPath:
  51. * type: string
  52. * example: 'GCS_API_KEY_JSON_PATH'
  53. * envGcsBucket:
  54. * type: string
  55. * example: 'GCS_BUCKET'
  56. * envGcsUploadNamespace:
  57. * type: string
  58. * example: 'GCS_UPLOAD_NAMESPACE'
  59. * envSiteUrl:
  60. * type: string
  61. * example: 'http://localhost:3000'
  62. * fileUpload:
  63. * type: boolean
  64. * example: true
  65. * fileUploadType:
  66. * type: string
  67. * example: 'local'
  68. * fromAddress:
  69. * type: string
  70. * example: info@growi.org
  71. * gcsApiKeyJsonPath:
  72. * type: string
  73. * example: 'GCS_API_KEY_JSON_PATH'
  74. * gcsBucket:
  75. * type: string
  76. * example: 'GCS_BUCKET'
  77. * gcsReferenceFileWithRelayMode:
  78. * type: boolean
  79. * example: false
  80. * gcsUploadNamespace:
  81. * type: string
  82. * example: 'GCS_UPLOAD_NAMESPACE'
  83. * gcsUseOnlyEnvVars:
  84. * type: boolean
  85. * example: false
  86. * globalLang:
  87. * type: string
  88. * example: 'ja_JP'
  89. * isAppSiteUrlHashed:
  90. * type: boolean
  91. * example: false
  92. * isEmailPublishedForNewUser:
  93. * type: boolean
  94. * example: true
  95. * isMaintenanceMode:
  96. * type: boolean
  97. * example: false
  98. * isV5Compatible:
  99. * type: boolean
  100. * example: true
  101. * s3AccessKeyId:
  102. * type: string
  103. * s3Bucket:
  104. * type: string
  105. * s3CustomEndpoint:
  106. * type: string
  107. * s3ReferenceFileWithRelayMode:
  108. * type: boolean
  109. * s3Region:
  110. * type: string
  111. * siteUrl:
  112. * type: string
  113. * siteUrlUseOnlyEnvVars:
  114. * type: boolean
  115. * smtpHost:
  116. * type: string
  117. * smtpPassword:
  118. * type: string
  119. * smtpPort:
  120. * type: string
  121. * smtpUser:
  122. * type: string
  123. * useOnlyEnvVarForFileUploadType:
  124. * type: boolean
  125. * AppSettingPutParams:
  126. * description: AppSettingPutParams
  127. * type: object
  128. * properties:
  129. * title:
  130. * type: string
  131. * description: title of the site
  132. * example: 'GROWI'
  133. * confidential:
  134. * type: string
  135. * description: confidential show on page header
  136. * example: 'GROWI'
  137. * globalLang:
  138. * type: string
  139. * description: global language
  140. * example: 'ja_JP'
  141. * isEmailPublishedForNewUser:
  142. * type: boolean
  143. * description: is email published for new user, or not
  144. * example: true
  145. * fileUpload:
  146. * type: boolean
  147. * description: is file upload enabled, or not
  148. * example: true
  149. * SiteUrlSettingParams:
  150. * description: SiteUrlSettingParams
  151. * type: object
  152. * properties:
  153. * siteUrl:
  154. * type: string
  155. * description: Site URL. e.g. https://example.com, https://example.com:8080
  156. * envSiteUrl:
  157. * type: string
  158. * description: environment variable 'APP_SITE_URL'
  159. * SmtpSettingParams:
  160. * description: SmtpSettingParams
  161. * type: object
  162. * properties:
  163. * smtpHost:
  164. * type: string
  165. * description: host name of client's smtp server
  166. * example: 'smtp.example.com'
  167. * smtpPort:
  168. * type: string
  169. * description: port of client's smtp server
  170. * example: '587'
  171. * smtpUser:
  172. * type: string
  173. * description: user name of client's smtp server
  174. * example: 'USER'
  175. * smtpPassword:
  176. * type: string
  177. * description: password of client's smtp server
  178. * example: 'PASSWORD'
  179. * fromAddress:
  180. * type: string
  181. * description: e-mail address
  182. * example: 'info@example.com'
  183. * SmtpSettingResponseParams:
  184. * description: SmtpSettingResponseParams
  185. * type: object
  186. * properties:
  187. * isMailerSetup:
  188. * type: boolean
  189. * description: is mailer setup, or not
  190. * example: true
  191. * smtpHost:
  192. * type: string
  193. * description: host name of client's smtp server
  194. * example: 'smtp.example.com'
  195. * smtpPort:
  196. * type: string
  197. * description: port of client's smtp server
  198. * example: '587'
  199. * smtpUser:
  200. * type: string
  201. * description: user name of client's smtp server
  202. * example: 'USER'
  203. * smtpPassword:
  204. * type: string
  205. * description: password of client's smtp server
  206. * example: 'PASSWORD'
  207. * fromAddress:
  208. * type: string
  209. * description: e-mail address
  210. * example: 'info@example.com'
  211. * SesSettingParams:
  212. * description: SesSettingParams
  213. * type: object
  214. * properties:
  215. * from:
  216. * type: string
  217. * description: e-mail address used as from address of mail which sent from GROWI app
  218. * example: 'info@growi.org'
  219. * transmissionMethod:
  220. * type: string
  221. * description: transmission method
  222. * example: 'ses'
  223. * sesAccessKeyId:
  224. * type: string
  225. * description: accesskey id for authentification of AWS
  226. * sesSecretAccessKey:
  227. * type: string
  228. * description: secret key for authentification of AWS
  229. * SesSettingResponseParams:
  230. * description: SesSettingParams
  231. * type: object
  232. * properties:
  233. * isMailerSetup:
  234. * type: boolean
  235. * description: is mailer setup, or not
  236. * example: true
  237. * from:
  238. * type: string
  239. * description: e-mail address used as from address of mail which sent from GROWI app
  240. * example: 'info@growi.org'
  241. * transmissionMethod:
  242. * type: string
  243. * description: transmission method
  244. * example: 'ses'
  245. * sesAccessKeyId:
  246. * type: string
  247. * description: accesskey id for authentification of AWS
  248. * sesSecretAccessKey:
  249. * type: string
  250. * description: secret key for authentification of AWS
  251. * FileUploadSettingParams:
  252. * description: FileUploadTypeParams
  253. * type: object
  254. * properties:
  255. * fileUploadType:
  256. * type: string
  257. * description: fileUploadType
  258. * s3Region:
  259. * type: string
  260. * description: region of AWS S3
  261. * s3CustomEndpoint:
  262. * type: string
  263. * description: custom endpoint of AWS S3
  264. * s3Bucket:
  265. * type: string
  266. * description: AWS S3 bucket name
  267. * s3AccessKeyId:
  268. * type: string
  269. * description: accesskey id for authentification of AWS
  270. * s3SecretAccessKey:
  271. * type: string
  272. * description: secret key for authentification of AWS
  273. * s3ReferenceFileWithRelayMode:
  274. * type: boolean
  275. * description: is enable internal stream system for s3 file request
  276. * gcsApiKeyJsonPath:
  277. * type: string
  278. * description: apiKeyJsonPath of gcp
  279. * gcsBucket:
  280. * type: string
  281. * description: bucket name of gcs
  282. * gcsUploadNamespace:
  283. * type: string
  284. * description: name space of gcs
  285. * gcsReferenceFileWithRelayMode:
  286. * type: boolean
  287. * description: is enable internal stream system for gcs file request
  288. * azureTenantId:
  289. * type: string
  290. * description: tenant id of azure
  291. * azureClientId:
  292. * type: string
  293. * description: client id of azure
  294. * azureClientSecret:
  295. * type: string
  296. * description: client secret of azure
  297. * azureStorageAccountName:
  298. * type: string
  299. * description: storage account name of azure
  300. * azureStorageContainerName:
  301. * type: string
  302. * description: storage container name of azure
  303. * azureReferenceFileWithRelayMode:
  304. * type: boolean
  305. * description: is enable internal stream system for azure file request
  306. */
  307. /** @param {import('~/server/crowi').default} crowi Crowi instance */
  308. module.exports = (crowi) => {
  309. const loginRequiredStrictly = require('../../../middlewares/login-required')(
  310. crowi,
  311. );
  312. const adminRequired = require('../../../middlewares/admin-required')(crowi);
  313. const addActivity = generateAddActivityMiddleware();
  314. const activityEvent = crowi.event('activity');
  315. const validator = {
  316. appSetting: [
  317. body('title').trim(),
  318. body('confidential'),
  319. body('globalLang').isIn(i18n.locales),
  320. body('isEmailPublishedForNewUser').isBoolean(),
  321. body('fileUpload').isBoolean(),
  322. ],
  323. siteUrlSetting: [
  324. // https://regex101.com/r/5Xef8V/1
  325. body('siteUrl')
  326. .trim()
  327. .matches(/^(https?:\/\/)/)
  328. .isURL({ require_tld: false }),
  329. ],
  330. mailSetting: [
  331. body('fromAddress')
  332. .trim()
  333. .if((value) => value !== '')
  334. .isEmail(),
  335. body('transmissionMethod').isIn(['smtp', 'ses']),
  336. ],
  337. smtpSetting: [
  338. body('smtpHost').trim(),
  339. body('smtpPort')
  340. .trim()
  341. .if((value) => value !== '')
  342. .isPort(),
  343. body('smtpUser').trim(),
  344. body('smtpPassword').trim(),
  345. ],
  346. sesSetting: [
  347. body('sesAccessKeyId')
  348. .trim()
  349. .if((value) => value !== '')
  350. .matches(/^[\da-zA-Z]+$/),
  351. body('sesSecretAccessKey').trim(),
  352. ],
  353. pageBulkExportSettings: [
  354. body('isBulkExportPagesEnabled').isBoolean(),
  355. body('bulkExportDownloadExpirationSeconds').isInt(),
  356. ],
  357. maintenanceMode: [body('flag').isBoolean()],
  358. };
  359. /**
  360. * @swagger
  361. *
  362. * /app-settings:
  363. * get:
  364. * tags: [AppSettings]
  365. * security:
  366. * - bearer: []
  367. * - accessTokenInQuery: []
  368. * summary: /app-settings
  369. * description: get app setting params
  370. * responses:
  371. * 200:
  372. * description: Resources are available
  373. * content:
  374. * application/json:
  375. * schema:
  376. * properties:
  377. * appSettingsParams:
  378. * type: object
  379. * $ref: '#/components/schemas/AppSettingParams'
  380. */
  381. router.get(
  382. '/',
  383. accessTokenParser([SCOPE.READ.ADMIN.APP], { acceptLegacy: true }),
  384. loginRequiredStrictly,
  385. adminRequired,
  386. async (req, res) => {
  387. const appSettingsParams = {
  388. title: configManager.getConfig('app:title'),
  389. confidential: configManager.getConfig('app:confidential'),
  390. globalLang: configManager.getConfig('app:globalLang'),
  391. isEmailPublishedForNewUser: configManager.getConfig(
  392. 'customize:isEmailPublishedForNewUser',
  393. ),
  394. fileUpload: configManager.getConfig('app:fileUpload'),
  395. useOnlyEnvVarsForIsBulkExportPagesEnabled: configManager.getConfig(
  396. 'env:useOnlyEnvVars:app:isBulkExportPagesEnabled',
  397. ),
  398. isV5Compatible: configManager.getConfig('app:isV5Compatible'),
  399. siteUrl: configManager.getConfig('app:siteUrl'),
  400. siteUrlUseOnlyEnvVars: configManager.getConfig(
  401. 'env:useOnlyEnvVars:app:siteUrl',
  402. ),
  403. envSiteUrl: configManager.getConfig('app:siteUrl', ConfigSource.env),
  404. isMailerSetup: crowi.mailService.isMailerSetup,
  405. fromAddress: configManager.getConfig('mail:from'),
  406. transmissionMethod: configManager.getConfig('mail:transmissionMethod'),
  407. smtpHost: configManager.getConfig('mail:smtpHost'),
  408. smtpPort: configManager.getConfig('mail:smtpPort'),
  409. smtpUser: configManager.getConfig('mail:smtpUser'),
  410. smtpPassword: configManager.getConfig('mail:smtpPassword'),
  411. sesAccessKeyId: configManager.getConfig('mail:sesAccessKeyId'),
  412. sesSecretAccessKey: configManager.getConfig('mail:sesSecretAccessKey'),
  413. fileUploadType: configManager.getConfig('app:fileUploadType'),
  414. envFileUploadType: configManager.getConfig(
  415. 'app:fileUploadType',
  416. ConfigSource.env,
  417. ),
  418. useOnlyEnvVarForFileUploadType: configManager.getConfig(
  419. 'env:useOnlyEnvVars:app:fileUploadType',
  420. ),
  421. s3Region: configManager.getConfig('aws:s3Region'),
  422. s3CustomEndpoint: configManager.getConfig('aws:s3CustomEndpoint'),
  423. s3Bucket: configManager.getConfig('aws:s3Bucket'),
  424. s3AccessKeyId: configManager.getConfig('aws:s3AccessKeyId'),
  425. s3ReferenceFileWithRelayMode: configManager.getConfig(
  426. 'aws:referenceFileWithRelayMode',
  427. ),
  428. gcsUseOnlyEnvVars: configManager.getConfig('env:useOnlyEnvVars:gcs'),
  429. gcsApiKeyJsonPath: configManager.getConfig('gcs:apiKeyJsonPath'),
  430. gcsBucket: configManager.getConfig('gcs:bucket'),
  431. gcsUploadNamespace: configManager.getConfig('gcs:uploadNamespace'),
  432. gcsReferenceFileWithRelayMode: configManager.getConfig(
  433. 'gcs:referenceFileWithRelayMode',
  434. ),
  435. envGcsApiKeyJsonPath: configManager.getConfig(
  436. 'gcs:apiKeyJsonPath',
  437. ConfigSource.env,
  438. ),
  439. envGcsBucket: configManager.getConfig('gcs:bucket', ConfigSource.env),
  440. envGcsUploadNamespace: configManager.getConfig(
  441. 'gcs:uploadNamespace',
  442. ConfigSource.env,
  443. ),
  444. azureUseOnlyEnvVars: configManager.getConfig(
  445. 'env:useOnlyEnvVars:azure',
  446. ),
  447. azureTenantId: configManager.getConfig(
  448. 'azure:tenantId',
  449. ConfigSource.db,
  450. ),
  451. azureClientId: configManager.getConfig(
  452. 'azure:clientId',
  453. ConfigSource.db,
  454. ),
  455. azureClientSecret: configManager.getConfig(
  456. 'azure:clientSecret',
  457. ConfigSource.db,
  458. ),
  459. azureStorageAccountName: configManager.getConfig(
  460. 'azure:storageAccountName',
  461. ConfigSource.db,
  462. ),
  463. azureStorageContainerName: configManager.getConfig(
  464. 'azure:storageContainerName',
  465. ConfigSource.db,
  466. ),
  467. azureReferenceFileWithRelayMode: configManager.getConfig(
  468. 'azure:referenceFileWithRelayMode',
  469. ),
  470. envAzureTenantId: configManager.getConfig(
  471. 'azure:tenantId',
  472. ConfigSource.env,
  473. ),
  474. envAzureClientId: configManager.getConfig(
  475. 'azure:clientId',
  476. ConfigSource.env,
  477. ),
  478. envAzureClientSecret: configManager.getConfig(
  479. 'azure:clientSecret',
  480. ConfigSource.env,
  481. ),
  482. envAzureStorageAccountName: configManager.getConfig(
  483. 'azure:storageAccountName',
  484. ConfigSource.env,
  485. ),
  486. envAzureStorageContainerName: configManager.getConfig(
  487. 'azure:storageContainerName',
  488. ConfigSource.env,
  489. ),
  490. isMaintenanceMode: configManager.getConfig('app:isMaintenanceMode'),
  491. isBulkExportPagesEnabled: configManager.getConfig(
  492. 'app:isBulkExportPagesEnabled',
  493. ),
  494. envIsBulkExportPagesEnabled: configManager.getConfig(
  495. 'app:isBulkExportPagesEnabled',
  496. ),
  497. bulkExportDownloadExpirationSeconds: configManager.getConfig(
  498. 'app:bulkExportDownloadExpirationSeconds',
  499. ),
  500. // TODO: remove this property when bulk export can be relased for cloud (https://redmine.weseek.co.jp/issues/163220)
  501. isBulkExportDisabledForCloud:
  502. configManager.getConfig('app:growiCloudUri') != null,
  503. };
  504. return res.apiv3({ appSettingsParams });
  505. },
  506. );
  507. /**
  508. * @swagger
  509. *
  510. * /app-settings/app-setting:
  511. * put:
  512. * tags: [AppSettings]
  513. * security:
  514. * - cookieAuth: []
  515. * summary: /app-settings/app-setting
  516. * description: Update app setting
  517. * requestBody:
  518. * required: true
  519. * content:
  520. * application/json:
  521. * schema:
  522. * $ref: '#/components/schemas/AppSettingPutParams'
  523. * responses:
  524. * 200:
  525. * description: Succeeded to update app setting
  526. * content:
  527. * application/json:
  528. * schema:
  529. * type: object
  530. * properties:
  531. * appSettingParams:
  532. * type: object
  533. * $ref: '#/components/schemas/AppSettingPutParams'
  534. */
  535. router.put(
  536. '/app-setting',
  537. accessTokenParser([SCOPE.WRITE.ADMIN.APP]),
  538. loginRequiredStrictly,
  539. adminRequired,
  540. addActivity,
  541. validator.appSetting,
  542. apiV3FormValidator,
  543. async (req, res) => {
  544. const requestAppSettingParams = {
  545. 'app:title': req.body.title,
  546. 'app:confidential': req.body.confidential,
  547. 'app:globalLang': req.body.globalLang,
  548. 'customize:isEmailPublishedForNewUser':
  549. req.body.isEmailPublishedForNewUser,
  550. 'app:fileUpload': req.body.fileUpload,
  551. };
  552. try {
  553. await configManager.updateConfigs(requestAppSettingParams);
  554. const appSettingParams = {
  555. title: configManager.getConfig('app:title'),
  556. confidential: configManager.getConfig('app:confidential'),
  557. globalLang: configManager.getConfig('app:globalLang'),
  558. isEmailPublishedForNewUser: configManager.getConfig(
  559. 'customize:isEmailPublishedForNewUser',
  560. ),
  561. fileUpload: configManager.getConfig('app:fileUpload'),
  562. };
  563. const parameters = {
  564. action: SupportedAction.ACTION_ADMIN_APP_SETTINGS_UPDATE,
  565. };
  566. activityEvent.emit('update', res.locals.activity._id, parameters);
  567. return res.apiv3({ appSettingParams });
  568. } catch (err) {
  569. const msg = 'Error occurred in updating app setting';
  570. logger.error('Error', err);
  571. return res.apiv3Err(new ErrorV3(msg, 'update-appSetting-failed'));
  572. }
  573. },
  574. );
  575. /**
  576. * @swagger
  577. *
  578. * /app-settings/site-url-setting:
  579. * put:
  580. * tags: [AppSettings]
  581. * security:
  582. * - cookieAuth: []
  583. * summary: /app-settings/site-url-setting
  584. * description: Update site url setting
  585. * requestBody:
  586. * required: true
  587. * content:
  588. * application/json:
  589. * schema:
  590. * $ref: '#/components/schemas/SiteUrlSettingParams'
  591. * responses:
  592. * 200:
  593. * description: Succeeded to update site url setting
  594. * content:
  595. * application/json:
  596. * schema:
  597. * type: object
  598. * properties:
  599. * siteUrlSettingParams:
  600. * type: object
  601. * properties:
  602. * siteUrl:
  603. * type: string
  604. * description: Site URL. e.g. https://example.com, https://example.com:3000
  605. * example: 'http://localhost:3000'
  606. */
  607. router.put(
  608. '/site-url-setting',
  609. accessTokenParser([SCOPE.WRITE.ADMIN.APP]),
  610. loginRequiredStrictly,
  611. adminRequired,
  612. addActivity,
  613. validator.siteUrlSetting,
  614. apiV3FormValidator,
  615. async (req, res) => {
  616. const useOnlyEnvVars = configManager.getConfig(
  617. 'env:useOnlyEnvVars:app:siteUrl',
  618. );
  619. if (useOnlyEnvVars) {
  620. const msg = 'Updating the Site URL is prohibited on this system.';
  621. return res.apiv3Err(
  622. new ErrorV3(msg, 'update-siteUrlSetting-prohibited'),
  623. );
  624. }
  625. const requestSiteUrlSettingParams = {
  626. 'app:siteUrl': pathUtils.removeTrailingSlash(req.body.siteUrl),
  627. };
  628. try {
  629. await configManager.updateConfigs(requestSiteUrlSettingParams);
  630. const siteUrlSettingParams = {
  631. siteUrl: configManager.getConfig('app:siteUrl'),
  632. };
  633. const parameters = {
  634. action: SupportedAction.ACTION_ADMIN_SITE_URL_UPDATE,
  635. };
  636. activityEvent.emit('update', res.locals.activity._id, parameters);
  637. return res.apiv3({ siteUrlSettingParams });
  638. } catch (err) {
  639. const msg = 'Error occurred in updating site url setting';
  640. logger.error('Error', err);
  641. return res.apiv3Err(new ErrorV3(msg, 'update-siteUrlSetting-failed'));
  642. }
  643. },
  644. );
  645. /**
  646. * send mail (Promise wrapper)
  647. */
  648. async function sendMailPromiseWrapper(smtpClient, options) {
  649. return new Promise((resolve, reject) => {
  650. smtpClient.sendMail(options, (err, res) => {
  651. if (err) {
  652. reject(err);
  653. } else {
  654. resolve(res);
  655. }
  656. });
  657. });
  658. }
  659. /**
  660. * validate mail setting send test mail
  661. */
  662. async function sendTestEmail(destinationAddress) {
  663. const { mailService } = crowi;
  664. if (!mailService.isMailerSetup) {
  665. throw Error('mailService is not setup');
  666. }
  667. const fromAddress = configManager.getConfig('mail:from');
  668. if (fromAddress == null) {
  669. throw Error('fromAddress is not setup');
  670. }
  671. const smtpHost = configManager.getConfig('mail:smtpHost');
  672. const smtpPort = configManager.getConfig('mail:smtpPort');
  673. const smtpUser = configManager.getConfig('mail:smtpUser');
  674. const smtpPassword = configManager.getConfig('mail:smtpPassword');
  675. // Define the option object with possible 'auth' and 'secure' properties
  676. const option: {
  677. host: string | undefined;
  678. port: string | undefined;
  679. auth?: { user: string; pass: string };
  680. secure?: boolean;
  681. } = {
  682. host: smtpHost,
  683. port: smtpPort,
  684. };
  685. if (smtpUser && smtpPassword) {
  686. option.auth = {
  687. user: smtpUser,
  688. pass: smtpPassword,
  689. };
  690. }
  691. if (option.port === '465') {
  692. option.secure = true;
  693. }
  694. const smtpClient = mailService.createSMTPClient(option);
  695. logger.debug('mailer setup for validate SMTP setting', smtpClient);
  696. const mailOptions = {
  697. from: fromAddress,
  698. to: destinationAddress,
  699. subject: 'Wiki管理設定のアップデートによるメール通知',
  700. text: 'このメールは、WikiのSMTP設定のアップデートにより送信されています。',
  701. };
  702. await sendMailPromiseWrapper(smtpClient, mailOptions);
  703. }
  704. const updateMailSettinConfig = async (requestMailSettingParams) => {
  705. const { mailService } = crowi;
  706. // update config without publishing S2sMessage
  707. await configManager.updateConfigs(requestMailSettingParams, {
  708. skipPubsub: true,
  709. });
  710. await mailService.initialize();
  711. mailService.publishUpdatedMessage();
  712. return {
  713. isMailerSetup: mailService.isMailerSetup,
  714. fromAddress: configManager.getConfig('mail:from'),
  715. smtpHost: configManager.getConfig('mail:smtpHost'),
  716. smtpPort: configManager.getConfig('mail:smtpPort'),
  717. smtpUser: configManager.getConfig('mail:smtpUser'),
  718. smtpPassword: configManager.getConfig('mail:smtpPassword'),
  719. sesAccessKeyId: configManager.getConfig('mail:sesAccessKeyId'),
  720. sesSecretAccessKey: configManager.getConfig('mail:sesSecretAccessKey'),
  721. };
  722. };
  723. /**
  724. * @swagger
  725. *
  726. * /app-settings/smtp-setting:
  727. * put:
  728. * tags: [AppSettings]
  729. * security:
  730. * - cookieAuth: []
  731. * summary: /app-settings/smtp-setting
  732. * description: Update smtp setting
  733. * requestBody:
  734. * required: true
  735. * content:
  736. * application/json:
  737. * schema:
  738. * $ref: '#/components/schemas/SmtpSettingParams'
  739. * responses:
  740. * 200:
  741. * description: Succeeded to update smtp setting
  742. * content:
  743. * application/json:
  744. * schema:
  745. * type: object
  746. * properties:
  747. * mailSettingParams:
  748. * type: object
  749. * $ref: '#/components/schemas/SmtpSettingResponseParams'
  750. */
  751. router.put(
  752. '/smtp-setting',
  753. accessTokenParser([SCOPE.WRITE.ADMIN.APP]),
  754. loginRequiredStrictly,
  755. adminRequired,
  756. addActivity,
  757. validator.smtpSetting,
  758. apiV3FormValidator,
  759. async (req, res) => {
  760. const requestMailSettingParams = {
  761. 'mail:from': req.body.fromAddress,
  762. 'mail:transmissionMethod': req.body.transmissionMethod,
  763. 'mail:smtpHost': req.body.smtpHost,
  764. 'mail:smtpPort': req.body.smtpPort,
  765. 'mail:smtpUser': req.body.smtpUser,
  766. 'mail:smtpPassword': req.body.smtpPassword,
  767. };
  768. try {
  769. const mailSettingParams = await updateMailSettinConfig(
  770. requestMailSettingParams,
  771. );
  772. const parameters = {
  773. action: SupportedAction.ACTION_ADMIN_MAIL_SMTP_UPDATE,
  774. };
  775. activityEvent.emit('update', res.locals.activity._id, parameters);
  776. return res.apiv3({ mailSettingParams });
  777. } catch (err) {
  778. const msg = 'Error occurred in updating smtp setting';
  779. logger.error('Error', err);
  780. return res.apiv3Err(new ErrorV3(msg, 'update-smtpSetting-failed'));
  781. }
  782. },
  783. );
  784. /**
  785. * @swagger
  786. *
  787. * /app-settings/smtp-test:
  788. * post:
  789. * tags: [AppSettings]
  790. * security:
  791. * - cookieAuth: []
  792. * summary: /app-settings/smtp-setting
  793. * description: Send test mail for smtp
  794. * responses:
  795. * 200:
  796. * description: Succeeded to send test mail for smtp
  797. * content:
  798. * application/json:
  799. * schema:
  800. * type: object
  801. * description: Empty object
  802. */
  803. router.post(
  804. '/smtp-test',
  805. accessTokenParser([SCOPE.WRITE.ADMIN.APP]),
  806. loginRequiredStrictly,
  807. adminRequired,
  808. addActivity,
  809. async (req, res) => {
  810. const { t } = await getTranslation({ lang: req.user.lang });
  811. try {
  812. await sendTestEmail(req.user.email);
  813. const parameters = {
  814. action: SupportedAction.ACTION_ADMIN_MAIL_TEST_SUBMIT,
  815. };
  816. activityEvent.emit('update', res.locals.activity._id, parameters);
  817. return res.apiv3({});
  818. } catch (err) {
  819. const msg = t('validation.failed_to_send_a_test_email');
  820. logger.error('Error', err);
  821. logger.debug('Error validate mail setting: ', err);
  822. return res.apiv3Err(new ErrorV3(msg, 'send-email-with-smtp-failed'));
  823. }
  824. },
  825. );
  826. /**
  827. * @swagger
  828. *
  829. * /app-settings/ses-setting:
  830. * put:
  831. * tags: [AppSettings]
  832. * security:
  833. * - cookieAuth: []
  834. * summary: /app-settings/ses-setting
  835. * description: Update ses setting
  836. * requestBody:
  837. * required: true
  838. * content:
  839. * application/json:
  840. * schema:
  841. * $ref: '#/components/schemas/SesSettingParams'
  842. * responses:
  843. * 200:
  844. * description: Succeeded to update ses setting
  845. * content:
  846. * application/json:
  847. * schema:
  848. * $ref: '#/components/schemas/SesSettingResponseParams'
  849. */
  850. router.put(
  851. '/ses-setting',
  852. accessTokenParser([SCOPE.WRITE.ADMIN.APP]),
  853. loginRequiredStrictly,
  854. adminRequired,
  855. addActivity,
  856. validator.sesSetting,
  857. apiV3FormValidator,
  858. async (req, res) => {
  859. const { mailService } = crowi;
  860. const requestSesSettingParams = {
  861. 'mail:from': req.body.fromAddress,
  862. 'mail:transmissionMethod': req.body.transmissionMethod,
  863. 'mail:sesAccessKeyId': req.body.sesAccessKeyId,
  864. 'mail:sesSecretAccessKey': req.body.sesSecretAccessKey,
  865. };
  866. let mailSettingParams: Awaited<ReturnType<typeof updateMailSettinConfig>>;
  867. try {
  868. mailSettingParams = await updateMailSettinConfig(
  869. requestSesSettingParams,
  870. );
  871. } catch (err) {
  872. const msg = 'Error occurred in updating ses setting';
  873. logger.error('Error', err);
  874. return res.apiv3Err(new ErrorV3(msg, 'update-ses-setting-failed'));
  875. }
  876. await mailService.initialize();
  877. mailService.publishUpdatedMessage();
  878. const parameters = {
  879. action: SupportedAction.ACTION_ADMIN_MAIL_SES_UPDATE,
  880. };
  881. activityEvent.emit('update', res.locals.activity._id, parameters);
  882. return res.apiv3({ mailSettingParams });
  883. },
  884. );
  885. router.use('/file-upload-setting', require('./file-upload-setting')(crowi));
  886. router.put(
  887. '/page-bulk-export-settings',
  888. accessTokenParser([SCOPE.WRITE.ADMIN.APP]),
  889. loginRequiredStrictly,
  890. adminRequired,
  891. addActivity,
  892. validator.pageBulkExportSettings,
  893. apiV3FormValidator,
  894. async (req, res) => {
  895. const requestParams = {
  896. 'app:isBulkExportPagesEnabled': req.body.isBulkExportPagesEnabled,
  897. 'app:bulkExportDownloadExpirationSeconds':
  898. req.body.bulkExportDownloadExpirationSeconds,
  899. };
  900. try {
  901. await configManager.updateConfigs(requestParams, { skipPubsub: true });
  902. const responseParams = {
  903. isBulkExportPagesEnabled: configManager.getConfig(
  904. 'app:isBulkExportPagesEnabled',
  905. ),
  906. bulkExportDownloadExpirationSeconds: configManager.getConfig(
  907. 'app:bulkExportDownloadExpirationSeconds',
  908. ),
  909. };
  910. const parameters = {
  911. action: SupportedAction.ACTION_ADMIN_APP_SETTINGS_UPDATE,
  912. };
  913. activityEvent.emit('update', res.locals.activity._id, parameters);
  914. return res.apiv3({ responseParams });
  915. } catch (err) {
  916. const msg = 'Error occurred in updating page bulk export settings';
  917. logger.error('Error', err);
  918. return res.apiv3Err(
  919. new ErrorV3(msg, 'update-page-bulk-export-settings-failed'),
  920. );
  921. }
  922. },
  923. );
  924. /**
  925. * @swagger
  926. *
  927. * /app-settings/v5-schema-migration:
  928. * post:
  929. * tags: [AppSettings]
  930. * security:
  931. * - bearer: []
  932. * - accessTokenInQuery: []
  933. * summary: AccessToken supported.
  934. * description: Update V5SchemaMigration
  935. * responses:
  936. * 200:
  937. * description: Succeeded to get V5SchemaMigration
  938. * content:
  939. * application/json:
  940. * schema:
  941. * type: object
  942. * properties:
  943. * isV5Compatible:
  944. * type: boolean
  945. * description: is V5 compatible, or not
  946. * example: true
  947. */
  948. router.post(
  949. '/v5-schema-migration',
  950. accessTokenParser([SCOPE.WRITE.ADMIN.APP], { acceptLegacy: true }),
  951. loginRequiredStrictly,
  952. adminRequired,
  953. async (req, res) => {
  954. const isMaintenanceMode = crowi.appService.isMaintenanceMode();
  955. if (!isMaintenanceMode) {
  956. return res.apiv3Err(
  957. new ErrorV3(
  958. 'GROWI is not maintenance mode. To import data, please activate the maintenance mode first.',
  959. 'not_maintenance_mode',
  960. ),
  961. );
  962. }
  963. const isV5Compatible = configManager.getConfig('app:isV5Compatible');
  964. try {
  965. if (!isV5Compatible) {
  966. // This method throws and emit socketIo event when error occurs
  967. crowi.pageService.normalizeAllPublicPages();
  968. }
  969. } catch (err) {
  970. return res.apiv3Err(
  971. new ErrorV3(`Failed to migrate pages: ${err.message}`),
  972. 500,
  973. );
  974. }
  975. return res.apiv3({ isV5Compatible });
  976. },
  977. );
  978. /**
  979. * @swagger
  980. *
  981. * /app-settings/maintenance-mode:
  982. * post:
  983. * tags: [AppSettings]
  984. * security:
  985. * - bearer: []
  986. * - accessTokenInQuery: []
  987. * summary: AccessToken supported.
  988. * description: Update MaintenanceMode
  989. * requestBody:
  990. * content:
  991. * application/json:
  992. * schema:
  993. * type: object
  994. * properties:
  995. * flag:
  996. * type: boolean
  997. * description: flag for maintenance mode
  998. * responses:
  999. * 200:
  1000. * description: Succeeded to update MaintenanceMode
  1001. * content:
  1002. * application/json:
  1003. * schema:
  1004. * type: object
  1005. * properties:
  1006. * flag:
  1007. * type: boolean
  1008. * description: true if maintenance mode is enabled
  1009. * example: true
  1010. */
  1011. router.post(
  1012. '/maintenance-mode',
  1013. accessTokenParser([SCOPE.WRITE.ADMIN.APP], { acceptLegacy: true }),
  1014. loginRequiredStrictly,
  1015. adminRequired,
  1016. addActivity,
  1017. validator.maintenanceMode,
  1018. apiV3FormValidator,
  1019. async (req, res) => {
  1020. const { flag } = req.body;
  1021. const parameters = {};
  1022. try {
  1023. if (flag) {
  1024. await crowi.appService.startMaintenanceMode();
  1025. Object.assign(parameters, {
  1026. action: SupportedAction.ACTION_ADMIN_MAINTENANCEMODE_ENABLED,
  1027. });
  1028. } else {
  1029. await crowi.appService.endMaintenanceMode();
  1030. Object.assign(parameters, {
  1031. action: SupportedAction.ACTION_ADMIN_MAINTENANCEMODE_DISABLED,
  1032. });
  1033. }
  1034. } catch (err) {
  1035. logger.error(err);
  1036. if (flag) {
  1037. res.apiv3Err(
  1038. new ErrorV3(
  1039. 'Failed to start maintenance mode',
  1040. 'failed_to_start_maintenance_mode',
  1041. ),
  1042. 500,
  1043. );
  1044. } else {
  1045. res.apiv3Err(
  1046. new ErrorV3(
  1047. 'Failed to end maintenance mode',
  1048. 'failed_to_end_maintenance_mode',
  1049. ),
  1050. 500,
  1051. );
  1052. }
  1053. }
  1054. if ('action' in parameters) {
  1055. activityEvent.emit('update', res.locals.activity._id, parameters);
  1056. }
  1057. res.apiv3({ flag });
  1058. },
  1059. );
  1060. return router;
  1061. };