app-settings.js 42 KB

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