config-loader.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. import { envUtils } from '@growi/core/dist/utils';
  2. import { parseISO } from 'date-fns';
  3. import { GrowiServiceType } from '~/features/questionnaire/interfaces/growi-info';
  4. import loggerFactory from '~/utils/logger';
  5. import ConfigModel, {
  6. Config, defaultCrowiConfigs, defaultMarkdownConfigs, defaultNotificationConfigs,
  7. } from '../models/config';
  8. const logger = loggerFactory('growi:service:ConfigLoader');
  9. enum ValueType { NUMBER, STRING, BOOLEAN, DATE }
  10. interface ValueParser<T> {
  11. parse(value: string): T;
  12. }
  13. interface EnvConfig {
  14. ns: string,
  15. key: string,
  16. type: ValueType,
  17. default?: number | string | boolean | null,
  18. }
  19. type EnumDictionary<T extends string | symbol | number, U> = {
  20. [K in T]: U;
  21. };
  22. const parserDictionary: EnumDictionary<ValueType, ValueParser<number | string | boolean | Date>> = {
  23. [ValueType.NUMBER]: { parse: (v: string) => { return parseInt(v, 10) } },
  24. [ValueType.STRING]: { parse: (v: string) => { return v } },
  25. [ValueType.BOOLEAN]: { parse: (v: string) => { return envUtils.toBoolean(v) } },
  26. [ValueType.DATE]: { parse: (v: string) => { return parseISO(v) } },
  27. };
  28. /**
  29. * The following env vars are excluded because these are currently used before the configuration setup.
  30. * - MONGO_URI
  31. * - NODE_ENV
  32. * - PORT
  33. * - REDIS_URI
  34. * - SESSION_NAME
  35. * - PASSWORD_SEED
  36. * - SECRET_TOKEN
  37. *
  38. * The commented out item has not yet entered the migration work.
  39. * So, parameters of these are under consideration.
  40. */
  41. const ENV_VAR_NAME_TO_CONFIG_INFO = {
  42. FILE_UPLOAD: {
  43. ns: 'crowi',
  44. key: 'app:fileUploadType',
  45. type: ValueType.STRING,
  46. default: 'aws',
  47. },
  48. FILE_UPLOAD_USES_ONLY_ENV_VAR_FOR_FILE_UPLOAD_TYPE: {
  49. ns: 'crowi',
  50. key: 'app:useOnlyEnvVarForFileUploadType',
  51. type: ValueType.BOOLEAN,
  52. default: false,
  53. },
  54. HACKMD_URI: {
  55. ns: 'crowi',
  56. key: 'app:hackmdUri',
  57. type: ValueType.STRING,
  58. default: null,
  59. },
  60. HACKMD_URI_FOR_SERVER: {
  61. ns: 'crowi',
  62. key: 'app:hackmdUriForServer',
  63. type: ValueType.STRING,
  64. default: null,
  65. },
  66. // OAUTH_GOOGLE_CLIENT_ID: {
  67. // ns: 'crowi',
  68. // key: 'security:passport-google:clientId',
  69. // type: ,
  70. // default:
  71. // },
  72. // OAUTH_GOOGLE_CLIENT_SECRET: {
  73. // ns: 'crowi',
  74. // key: 'security:passport-google:clientSecret',
  75. // type: ,
  76. // default:
  77. // },
  78. // OAUTH_GOOGLE_CALLBACK_URI: {
  79. // ns: 'crowi',
  80. // key: 'security:passport-google:callbackUrl',
  81. // type: ,
  82. // default:
  83. // },
  84. // OAUTH_GITHUB_CLIENT_ID: {
  85. // ns: 'crowi',
  86. // key: 'security:passport-github:clientId',
  87. // type: ,
  88. // default:
  89. // },
  90. // OAUTH_GITHUB_CLIENT_SECRET: {
  91. // ns: 'crowi',
  92. // key: 'security:passport-github:clientSecret',
  93. // type: ,
  94. // default:
  95. // },
  96. // OAUTH_GITHUB_CALLBACK_URI: {
  97. // ns: 'crowi',
  98. // key: 'security:passport-github:callbackUrl',
  99. // type: ,
  100. // default:
  101. // },
  102. PLANTUML_URI: {
  103. ns: 'crowi',
  104. key: 'app:plantumlUri',
  105. type: ValueType.STRING,
  106. default: 'https://www.plantuml.com/plantuml',
  107. },
  108. DRAWIO_URI: {
  109. ns: 'crowi',
  110. key: 'app:drawioUri',
  111. type: ValueType.STRING,
  112. default: 'https://embed.diagrams.net/',
  113. },
  114. NCHAN_URI: {
  115. ns: 'crowi',
  116. key: 'app:nchanUri',
  117. type: ValueType.STRING,
  118. default: null,
  119. },
  120. APP_SITE_URL: {
  121. ns: 'crowi',
  122. key: 'app:siteUrl',
  123. type: ValueType.STRING,
  124. default: null,
  125. },
  126. APP_SITE_URL_USES_ONLY_ENV_VARS: {
  127. ns: 'crowi',
  128. key: 'app:siteUrl:useOnlyEnvVars',
  129. type: ValueType.BOOLEAN,
  130. default: false,
  131. },
  132. PUBLISH_OPEN_API: {
  133. ns: 'crowi',
  134. key: 'app:publishOpenAPI',
  135. type: ValueType.BOOLEAN,
  136. default: false,
  137. },
  138. IS_V5_COMPATIBLE: {
  139. ns: 'crowi',
  140. key: 'app:isV5Compatible',
  141. type: ValueType.BOOLEAN,
  142. default: undefined,
  143. },
  144. IS_MAINTENANCE_MODE: {
  145. ns: 'crowi',
  146. key: 'app:isMaintenanceMode',
  147. type: ValueType.BOOLEAN,
  148. default: false,
  149. },
  150. AUTO_INSTALL_ADMIN_USERNAME: {
  151. ns: 'crowi',
  152. key: 'autoInstall:adminUsername',
  153. type: ValueType.STRING,
  154. default: null,
  155. },
  156. AUTO_INSTALL_ADMIN_NAME: {
  157. ns: 'crowi',
  158. key: 'autoInstall:adminName',
  159. type: ValueType.STRING,
  160. default: null,
  161. },
  162. AUTO_INSTALL_ADMIN_EMAIL: {
  163. ns: 'crowi',
  164. key: 'autoInstall:adminEmail',
  165. type: ValueType.STRING,
  166. default: null,
  167. },
  168. AUTO_INSTALL_ADMIN_PASSWORD: {
  169. ns: 'crowi',
  170. key: 'autoInstall:adminPassword',
  171. type: ValueType.STRING,
  172. default: null,
  173. },
  174. AUTO_INSTALL_GLOBAL_LANG: {
  175. ns: 'crowi',
  176. key: 'autoInstall:globalLang',
  177. type: ValueType.STRING,
  178. default: null,
  179. },
  180. AUTO_INSTALL_ALLOW_GUEST_MODE: {
  181. ns: 'crowi',
  182. key: 'autoInstall:allowGuestMode',
  183. type: ValueType.BOOLEAN,
  184. default: false,
  185. },
  186. AUTO_INSTALL_SERVER_DATE: {
  187. ns: 'crowi',
  188. key: 'autoInstall:serverDate',
  189. type: ValueType.DATE,
  190. default: null,
  191. },
  192. S2SMSG_PUBSUB_SERVER_TYPE: {
  193. ns: 'crowi',
  194. key: 's2sMessagingPubsub:serverType',
  195. type: ValueType.STRING,
  196. default: null,
  197. },
  198. S2SMSG_PUBSUB_NCHAN_PUBLISH_PATH: {
  199. ns: 'crowi',
  200. key: 's2sMessagingPubsub:nchan:publishPath',
  201. type: ValueType.STRING,
  202. default: '/pubsub',
  203. },
  204. S2SMSG_PUBSUB_NCHAN_SUBSCRIBE_PATH: {
  205. ns: 'crowi',
  206. key: 's2sMessagingPubsub:nchan:subscribePath',
  207. type: ValueType.STRING,
  208. default: '/pubsub',
  209. },
  210. S2SMSG_PUBSUB_NCHAN_CHANNEL_ID: {
  211. ns: 'crowi',
  212. key: 's2sMessagingPubsub:nchan:channelId',
  213. type: ValueType.STRING,
  214. default: null,
  215. },
  216. S2CMSG_PUBSUB_CONNECTIONS_LIMIT: {
  217. ns: 'crowi',
  218. key: 's2cMessagingPubsub:connectionsLimit',
  219. type: ValueType.NUMBER,
  220. default: 5000,
  221. },
  222. S2CMSG_PUBSUB_CONNECTIONS_LIMIT_FOR_ADMIN: {
  223. ns: 'crowi',
  224. key: 's2cMessagingPubsub:connectionsLimitForAdmin',
  225. type: ValueType.NUMBER,
  226. default: 100,
  227. },
  228. S2CMSG_PUBSUB_CONNECTIONS_LIMIT_FOR_GUEST: {
  229. ns: 'crowi',
  230. key: 's2cMessagingPubsub:connectionsLimitForGuest',
  231. type: ValueType.NUMBER,
  232. default: 2000,
  233. },
  234. MAX_FILE_SIZE: {
  235. ns: 'crowi',
  236. key: 'app:maxFileSize',
  237. type: ValueType.NUMBER,
  238. default: Infinity,
  239. },
  240. FILE_UPLOAD_TOTAL_LIMIT: {
  241. ns: 'crowi',
  242. key: 'app:fileUploadTotalLimit',
  243. type: ValueType.NUMBER,
  244. default: Infinity,
  245. },
  246. FILE_UPLOAD_DISABLED: {
  247. ns: 'crowi',
  248. key: 'app:fileUploadDisabled',
  249. type: ValueType.BOOLEAN,
  250. default: false,
  251. },
  252. FILE_UPLOAD_LOCAL_USE_INTERNAL_REDIRECT: {
  253. ns: 'crowi',
  254. key: 'fileUpload:local:useInternalRedirect',
  255. type: ValueType.BOOLEAN,
  256. default: false,
  257. },
  258. FILE_UPLOAD_LOCAL_INTERNAL_REDIRECT_PATH: {
  259. ns: 'crowi',
  260. key: 'fileUpload:local:internalRedirectPath',
  261. type: ValueType.STRING,
  262. default: '/growi-internal/',
  263. },
  264. ELASTICSEARCH_VERSION: {
  265. ns: 'crowi',
  266. key: 'app:elasticsearchVersion',
  267. type: ValueType.NUMBER,
  268. default: 8,
  269. },
  270. ELASTICSEARCH_URI: {
  271. ns: 'crowi',
  272. key: 'app:elasticsearchUri',
  273. type: ValueType.STRING,
  274. default: null,
  275. },
  276. ELASTICSEARCH_REQUEST_TIMEOUT: {
  277. ns: 'crowi',
  278. key: 'app:elasticsearchRequestTimeout',
  279. type: ValueType.NUMBER,
  280. default: 8000, // msec
  281. },
  282. ELASTICSEARCH_REJECT_UNAUTHORIZED: {
  283. ns: 'crowi',
  284. key: 'app:elasticsearchRejectUnauthorized',
  285. type: ValueType.BOOLEAN,
  286. default: false,
  287. },
  288. ELASTICSEARCH_REINDEX_ON_BOOT: {
  289. ns: 'crowi',
  290. key: 'app:elasticsearchReindexOnBoot',
  291. type: ValueType.BOOLEAN,
  292. default: false,
  293. },
  294. MONGO_GRIDFS_TOTAL_LIMIT: {
  295. ns: 'crowi',
  296. key: 'gridfs:totalLimit',
  297. type: ValueType.NUMBER,
  298. default: null, // set null in default for backward compatibility
  299. // cz: Newer system respects FILE_UPLOAD_TOTAL_LIMIT.
  300. // If the default value of MONGO_GRIDFS_TOTAL_LIMIT is Infinity,
  301. // the system can't distinguish between "not specified" and "Infinity is specified".
  302. },
  303. FORCE_WIKI_MODE: {
  304. ns: 'crowi',
  305. key: 'security:wikiMode',
  306. type: ValueType.STRING,
  307. default: undefined,
  308. },
  309. SESSION_MAX_AGE: {
  310. ns: 'crowi',
  311. key: 'security:sessionMaxAge',
  312. type: ValueType.NUMBER,
  313. default: undefined,
  314. },
  315. USER_UPPER_LIMIT: {
  316. ns: 'crowi',
  317. key: 'security:userUpperLimit',
  318. type: ValueType.NUMBER,
  319. default: Infinity,
  320. },
  321. DISABLE_LINK_SHARING: {
  322. ns: 'crowi',
  323. key: 'security:disableLinkSharing',
  324. type: ValueType.BOOLEAN,
  325. default: false,
  326. },
  327. TRUST_PROXY_BOOL: {
  328. ns: 'crowi',
  329. key: 'security:trustProxyBool',
  330. type: ValueType.BOOLEAN,
  331. default: null,
  332. },
  333. TRUST_PROXY_CSV: {
  334. ns: 'crowi',
  335. key: 'security:trustProxyCsv',
  336. type: ValueType.STRING,
  337. default: null,
  338. },
  339. TRUST_PROXY_HOPS: {
  340. ns: 'crowi',
  341. key: 'security:trustProxyHops',
  342. type: ValueType.NUMBER,
  343. default: null,
  344. },
  345. LOCAL_STRATEGY_ENABLED: {
  346. ns: 'crowi',
  347. key: 'security:passport-local:isEnabled',
  348. type: ValueType.BOOLEAN,
  349. default: true,
  350. },
  351. LOCAL_STRATEGY_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS: {
  352. ns: 'crowi',
  353. key: 'security:passport-local:useOnlyEnvVarsForSomeOptions',
  354. type: ValueType.BOOLEAN,
  355. default: false,
  356. },
  357. LOCAL_STRATEGY_PASSWORD_RESET_ENABLED: {
  358. ns: 'crowi',
  359. key: 'security:passport-local:isPasswordResetEnabled',
  360. type: ValueType.BOOLEAN,
  361. default: true,
  362. },
  363. LOCAL_STRATEGY_EMAIL_AUTHENTICATION_ENABLED: {
  364. ns: 'crowi',
  365. key: 'security:passport-local:isEmailAuthenticationEnabled',
  366. type: ValueType.BOOLEAN,
  367. default: false,
  368. },
  369. SAML_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS: {
  370. ns: 'crowi',
  371. key: 'security:passport-saml:useOnlyEnvVarsForSomeOptions',
  372. type: ValueType.BOOLEAN,
  373. default: false,
  374. },
  375. SAML_ENABLED: {
  376. ns: 'crowi',
  377. key: 'security:passport-saml:isEnabled',
  378. type: ValueType.BOOLEAN,
  379. default: null,
  380. },
  381. SAML_ENTRY_POINT: {
  382. ns: 'crowi',
  383. key: 'security:passport-saml:entryPoint',
  384. type: ValueType.STRING,
  385. default: null,
  386. },
  387. SAML_CALLBACK_URI: {
  388. ns: 'crowi',
  389. key: 'security:passport-saml:callbackUrl',
  390. type: ValueType.STRING,
  391. default: null,
  392. },
  393. SAML_ISSUER: {
  394. ns: 'crowi',
  395. key: 'security:passport-saml:issuer',
  396. type: ValueType.STRING,
  397. default: null,
  398. },
  399. SAML_ATTR_MAPPING_ID: {
  400. ns: 'crowi',
  401. key: 'security:passport-saml:attrMapId',
  402. type: ValueType.STRING,
  403. default: null,
  404. },
  405. SAML_ATTR_MAPPING_USERNAME: {
  406. ns: 'crowi',
  407. key: 'security:passport-saml:attrMapUsername',
  408. type: ValueType.STRING,
  409. default: null,
  410. },
  411. SAML_ATTR_MAPPING_MAIL: {
  412. ns: 'crowi',
  413. key: 'security:passport-saml:attrMapMail',
  414. type: ValueType.STRING,
  415. default: null,
  416. },
  417. SAML_ATTR_MAPPING_FIRST_NAME: {
  418. ns: 'crowi',
  419. key: 'security:passport-saml:attrMapFirstName',
  420. type: ValueType.STRING,
  421. default: null,
  422. },
  423. SAML_ATTR_MAPPING_LAST_NAME: {
  424. ns: 'crowi',
  425. key: 'security:passport-saml:attrMapLastName',
  426. type: ValueType.STRING,
  427. default: null,
  428. },
  429. SAML_CERT: {
  430. ns: 'crowi',
  431. key: 'security:passport-saml:cert',
  432. type: ValueType.STRING,
  433. default: null,
  434. },
  435. SAML_ABLC_RULE: {
  436. ns: 'crowi',
  437. key: 'security:passport-saml:ABLCRule',
  438. type: ValueType.STRING,
  439. default: null,
  440. },
  441. OIDC_TIMEOUT_MULTIPLIER: {
  442. ns: 'crowi',
  443. key: 'security:passport-oidc:timeoutMultiplier',
  444. type: ValueType.NUMBER,
  445. default: 1.5,
  446. },
  447. OIDC_DISCOVERY_RETRIES: {
  448. ns: 'crowi',
  449. key: 'security:passport-oidc:discoveryRetries',
  450. type: ValueType.NUMBER,
  451. default: 3,
  452. },
  453. OIDC_CLIENT_CLOCK_TOLERANCE: {
  454. ns: 'crowi',
  455. key: 'security:passport-oidc:oidcClientClockTolerance',
  456. type: ValueType.NUMBER,
  457. default: 60,
  458. },
  459. OIDC_ISSUER_TIMEOUT_OPTION: {
  460. ns: 'crowi',
  461. key: 'security:passport-oidc:oidcIssuerTimeoutOption',
  462. type: ValueType.NUMBER,
  463. default: 5000,
  464. },
  465. S3_REFERENCE_FILE_WITH_RELAY_MODE: {
  466. ns: 'crowi',
  467. key: 'aws:referenceFileWithRelayMode',
  468. type: ValueType.BOOLEAN,
  469. default: false,
  470. },
  471. S3_LIFETIME_SEC_FOR_TEMPORARY_URL: {
  472. ns: 'crowi',
  473. key: 'aws:lifetimeSecForTemporaryUrl',
  474. type: ValueType.NUMBER,
  475. default: 120,
  476. },
  477. GCS_API_KEY_JSON_PATH: {
  478. ns: 'crowi',
  479. key: 'gcs:apiKeyJsonPath',
  480. type: ValueType.STRING,
  481. default: null,
  482. },
  483. GCS_BUCKET: {
  484. ns: 'crowi',
  485. key: 'gcs:bucket',
  486. type: ValueType.STRING,
  487. default: null,
  488. },
  489. GCS_UPLOAD_NAMESPACE: {
  490. ns: 'crowi',
  491. key: 'gcs:uploadNamespace',
  492. type: ValueType.STRING,
  493. default: null,
  494. },
  495. GCS_LIFETIME_SEC_FOR_TEMPORARY_URL: {
  496. ns: 'crowi',
  497. key: 'gcs:lifetimeSecForTemporaryUrl',
  498. type: ValueType.NUMBER,
  499. default: 120,
  500. },
  501. GCS_REFERENCE_FILE_WITH_RELAY_MODE: {
  502. ns: 'crowi',
  503. key: 'gcs:referenceFileWithRelayMode',
  504. type: ValueType.BOOLEAN,
  505. default: false,
  506. },
  507. GCS_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS: {
  508. ns: 'crowi',
  509. key: 'gcs:useOnlyEnvVarsForSomeOptions',
  510. type: ValueType.BOOLEAN,
  511. default: false,
  512. },
  513. GROWI_CLOUD_URI: {
  514. ns: 'crowi',
  515. key: 'app:growiCloudUri',
  516. type: ValueType.STRING,
  517. default: null,
  518. },
  519. GROWI_APP_ID_FOR_GROWI_CLOUD: {
  520. ns: 'crowi',
  521. key: 'app:growiAppIdForCloud',
  522. type: ValueType.STRING,
  523. default: null,
  524. },
  525. DEFAULT_EMAIL_PUBLISHED: {
  526. ns: 'crowi',
  527. key: 'customize:isEmailPublishedForNewUser',
  528. type: ValueType.BOOLEAN,
  529. default: true,
  530. },
  531. SLACKBOT_TYPE: {
  532. ns: 'crowi',
  533. key: 'slackbot:currentBotType', // enum SlackbotType
  534. type: ValueType.STRING,
  535. default: null,
  536. },
  537. SLACKBOT_INTEGRATION_PROXY_URI: {
  538. ns: 'crowi',
  539. key: 'slackbot:proxyUri',
  540. type: ValueType.STRING,
  541. default: null,
  542. },
  543. SLACKBOT_WITHOUT_PROXY_SIGNING_SECRET: {
  544. ns: 'crowi',
  545. key: 'slackbot:withoutProxy:signingSecret',
  546. type: ValueType.STRING,
  547. default: null,
  548. },
  549. SLACKBOT_WITHOUT_PROXY_BOT_TOKEN: {
  550. ns: 'crowi',
  551. key: 'slackbot:withoutProxy:botToken',
  552. type: ValueType.STRING,
  553. default: null,
  554. },
  555. SLACKBOT_WITHOUT_PROXY_COMMAND_PERMISSION: {
  556. ns: 'crowi',
  557. key: 'slackbot:withoutProxy:commandPermission',
  558. type: ValueType.STRING,
  559. default: null,
  560. },
  561. SLACKBOT_WITHOUT_PROXY_EVENT_ACTIONS_PERMISSION: {
  562. ns: 'crowi',
  563. key: 'slackbot:withoutProxy:eventActionsPermission',
  564. type: ValueType.STRING,
  565. default: null,
  566. },
  567. SLACKBOT_WITH_PROXY_SALT_FOR_GTOP: {
  568. ns: 'crowi',
  569. key: 'slackbot:withProxy:saltForGtoP',
  570. type: ValueType.STRING,
  571. default: 'gtop',
  572. },
  573. SLACKBOT_WITH_PROXY_SALT_FOR_PTOG: {
  574. ns: 'crowi',
  575. key: 'slackbot:withProxy:saltForPtoG',
  576. type: ValueType.STRING,
  577. default: 'ptog',
  578. },
  579. OGP_URI: {
  580. ns: 'crowi',
  581. key: 'app:ogpUri',
  582. type: ValueType.STRING,
  583. default: null,
  584. },
  585. MIN_PASSWORD_LENGTH: {
  586. ns: 'crowi',
  587. key: 'app:minPasswordLength',
  588. type: ValueType.NUMBER,
  589. default: 8,
  590. },
  591. AUDIT_LOG_ENABLED: {
  592. ns: 'crowi',
  593. key: 'app:auditLogEnabled',
  594. type: ValueType.BOOLEAN,
  595. default: false,
  596. },
  597. ACTIVITY_EXPIRATION_SECONDS: {
  598. ns: 'crowi',
  599. key: 'app:activityExpirationSeconds',
  600. type: ValueType.NUMBER,
  601. default: 2592000, // 30 days
  602. },
  603. AUDIT_LOG_ACTION_GROUP_SIZE: {
  604. ns: 'crowi',
  605. key: 'app:auditLogActionGroupSize',
  606. type: ValueType.STRING,
  607. default: 'SMALL',
  608. },
  609. AUDIT_LOG_ADDITIONAL_ACTIONS: {
  610. ns: 'crowi',
  611. key: 'app:auditLogAdditionalActions',
  612. type: ValueType.STRING,
  613. default: null,
  614. },
  615. AUDIT_LOG_EXCLUDE_ACTIONS: {
  616. ns: 'crowi',
  617. key: 'app:auditLogExcludeActions',
  618. type: ValueType.STRING,
  619. default: null,
  620. },
  621. QUESTIONNAIRE_SERVER_ORIGIN: {
  622. ns: 'crowi',
  623. key: 'app:questionnaireServerOrigin',
  624. type: ValueType.STRING,
  625. default: 'https://q.growi.org',
  626. },
  627. QUESTIONNAIRE_CRON_SCHEDULE: {
  628. ns: 'crowi',
  629. key: 'app:questionnaireCronSchedule',
  630. type: ValueType.STRING,
  631. default: '0 22 * * *',
  632. },
  633. QUESTIONNAIRE_CRON_MAX_HOURS_UNTIL_REQUEST: {
  634. ns: 'crowi',
  635. key: 'app:questionnaireCronMaxHoursUntilRequest',
  636. type: ValueType.NUMBER,
  637. default: 4,
  638. },
  639. QUESTIONNAIRE_IS_ENABLE_QUESTIONNAIRE: {
  640. ns: 'crowi',
  641. key: 'questionnaire:isQuestionnaireEnabled',
  642. type: ValueType.BOOLEAN,
  643. default: true,
  644. },
  645. QUESTIONNAIRE_IS_APP_SITE_URL_HASHED: {
  646. ns: 'crowi',
  647. key: 'questionnaire:isAppSiteUrlHashed',
  648. type: ValueType.BOOLEAN,
  649. default: false,
  650. },
  651. SERVICE_TYPE: {
  652. ns: 'crowi',
  653. key: 'app:serviceType',
  654. type: ValueType.STRING,
  655. default: GrowiServiceType.onPremise,
  656. },
  657. DEPLOYMENT_TYPE: {
  658. ns: 'crowi',
  659. key: 'app:deploymentType',
  660. type: ValueType.STRING,
  661. default: null,
  662. },
  663. SSR_MAX_REVISION_BODY_LENGTH: {
  664. ns: 'crowi',
  665. key: 'app:ssrMaxRevisionBodyLength',
  666. type: ValueType.NUMBER,
  667. default: 30000,
  668. },
  669. };
  670. /**
  671. * return whether env belongs to Security settings
  672. * @param key ex. 'security:passport-saml:isEnabled' is true
  673. * @returns
  674. */
  675. const isSecurityEnv = (key) => {
  676. const array = key.split(':');
  677. return (array[0] === 'security');
  678. };
  679. export interface ConfigObject extends Record<string, any> {
  680. fromDB: any,
  681. fromEnvVars: any,
  682. }
  683. export default class ConfigLoader {
  684. /**
  685. * return a config object
  686. */
  687. async load(): Promise<ConfigObject> {
  688. const configFromDB: any = await this.loadFromDB();
  689. const configFromEnvVars: any = this.loadFromEnvVars();
  690. // merge defaults per ns
  691. const mergedConfigFromDB = {
  692. crowi: Object.assign(defaultCrowiConfigs, configFromDB.crowi),
  693. markdown: Object.assign(defaultMarkdownConfigs, configFromDB.markdown),
  694. notification: Object.assign(defaultNotificationConfigs, configFromDB.notification),
  695. };
  696. // In getConfig API, only null is used as a value to indicate that a config is not set.
  697. // So, if a value loaded from the database is empty,
  698. // it is converted to null because an empty string is used as the same meaning in the config model.
  699. // By this processing, whether a value is loaded from the database or from the environment variable,
  700. // only null indicates a config is not set.
  701. for (const namespace of Object.keys(mergedConfigFromDB)) {
  702. for (const key of Object.keys(mergedConfigFromDB[namespace])) {
  703. if (mergedConfigFromDB[namespace][key] === '') {
  704. mergedConfigFromDB[namespace][key] = null;
  705. }
  706. }
  707. }
  708. return {
  709. fromDB: mergedConfigFromDB,
  710. fromEnvVars: configFromEnvVars,
  711. };
  712. }
  713. async loadFromDB(): Promise<any> {
  714. const config = {};
  715. const docs: Config[] = await ConfigModel.find().exec();
  716. for (const doc of docs) {
  717. if (!config[doc.ns]) {
  718. config[doc.ns] = {};
  719. }
  720. config[doc.ns][doc.key] = doc.value ? JSON.parse(doc.value) : null;
  721. }
  722. logger.debug('ConfigLoader#loadFromDB', config);
  723. return config;
  724. }
  725. loadFromEnvVars(): any {
  726. const config = {};
  727. for (const ENV_VAR_NAME of Object.keys(ENV_VAR_NAME_TO_CONFIG_INFO)) {
  728. const configInfo = ENV_VAR_NAME_TO_CONFIG_INFO[ENV_VAR_NAME];
  729. if (config[configInfo.ns] === undefined) {
  730. config[configInfo.ns] = {};
  731. }
  732. if (process.env[ENV_VAR_NAME] === undefined) {
  733. config[configInfo.ns][configInfo.key] = configInfo.default;
  734. }
  735. else {
  736. const parser: ValueParser<number | string | boolean> = parserDictionary[configInfo.type];
  737. config[configInfo.ns][configInfo.key] = parser.parse(process.env[ENV_VAR_NAME] as string);
  738. }
  739. }
  740. logger.debug('ConfigLoader#loadFromEnvVars', config);
  741. return config;
  742. }
  743. /**
  744. * get config from the environment variables for display admin page
  745. *
  746. * **use this only admin homepage.**
  747. */
  748. static getEnvVarsForDisplay(avoidSecurity = false): any {
  749. const config = {};
  750. for (const ENV_VAR_NAME of Object.keys(ENV_VAR_NAME_TO_CONFIG_INFO)) {
  751. const configInfo = ENV_VAR_NAME_TO_CONFIG_INFO[ENV_VAR_NAME];
  752. if (process.env[ENV_VAR_NAME] === undefined) {
  753. continue;
  754. }
  755. if (isSecurityEnv(configInfo.key) && avoidSecurity) {
  756. continue;
  757. }
  758. const parser: ValueParser<number | string | boolean> = parserDictionary[configInfo.type];
  759. config[ENV_VAR_NAME] = parser.parse(process.env[ENV_VAR_NAME] as string);
  760. }
  761. logger.debug('ConfigLoader#getEnvVarsForDisplay', config);
  762. return config;
  763. }
  764. }