config-loader.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. const debug = require('debug')('growi:service:ConfigLoader');
  2. const TYPES = {
  3. NUMBER: { parse: (v) => parseInt(v) },
  4. STRING: { parse: (v) => v },
  5. BOOLEAN: { parse: (v) => /^(true|1)$/i.test(v) }
  6. };
  7. /**
  8. * The following env vars are excluded because these are currently used before the configuration setup.
  9. * - MONGO_URI
  10. * - NODE_ENV
  11. * - PORT
  12. * - REDIS_URI
  13. * - SESSION_NAME
  14. * - PASSWORD_SEED
  15. * - SECRET_TOKEN
  16. *
  17. * The commented out item has not yet entered the migration work.
  18. * So, parameters of these are under consideration.
  19. */
  20. const ENV_VAR_NAME_TO_CONFIG_INFO = {
  21. // ELASTICSEARCH_URI: {
  22. // ns: ,
  23. // key: ,
  24. // type: ,
  25. // default:
  26. // },
  27. // FILE_UPLOAD: {
  28. // ns: ,
  29. // key: ,
  30. // type: ,
  31. // default:
  32. // },
  33. // HACKMD_URI: {
  34. // ns: ,
  35. // key: ,
  36. // type: ,
  37. // default:
  38. // },
  39. // HACKMD_URI_FOR_SERVER: {
  40. // ns: ,
  41. // key: ,
  42. // type: ,
  43. // default:
  44. // },
  45. // PLANTUML_URI: {
  46. // ns: ,
  47. // key: ,
  48. // type: ,
  49. // default:
  50. // },
  51. // BLOCKDIAG_URI: {
  52. // ns: ,
  53. // key: ,
  54. // type: ,
  55. // default:
  56. // },
  57. // OAUTH_GOOGLE_CLIENT_ID: {
  58. // ns: 'crowi',
  59. // key: 'security:passport-google:clientId',
  60. // type: ,
  61. // default:
  62. // },
  63. // OAUTH_GOOGLE_CLIENT_SECRET: {
  64. // ns: 'crowi',
  65. // key: 'security:passport-google:clientSecret',
  66. // type: ,
  67. // default:
  68. // },
  69. // OAUTH_GOOGLE_CALLBACK_URI: {
  70. // ns: 'crowi',
  71. // key: 'security:passport-google:callbackUrl',
  72. // type: ,
  73. // default:
  74. // },
  75. // OAUTH_GITHUB_CLIENT_ID: {
  76. // ns: 'crowi',
  77. // key: 'security:passport-github:clientId',
  78. // type: ,
  79. // default:
  80. // },
  81. // OAUTH_GITHUB_CLIENT_SECRET: {
  82. // ns: 'crowi',
  83. // key: 'security:passport-github:clientSecret',
  84. // type: ,
  85. // default:
  86. // },
  87. // OAUTH_GITHUB_CALLBACK_URI: {
  88. // ns: 'crowi',
  89. // key: 'security:passport-github:callbackUrl',
  90. // type: ,
  91. // default:
  92. // },
  93. // OAUTH_TWITTER_CONSUMER_KEY: {
  94. // ns: 'crowi',
  95. // key: 'security:passport-twitter:consumerKey',
  96. // type: ,
  97. // default:
  98. // },
  99. // OAUTH_TWITTER_CONSUMER_SECRET: {
  100. // ns: 'crowi',
  101. // key: 'security:passport-twitter:consumerSecret',
  102. // type: ,
  103. // default:
  104. // },
  105. // OAUTH_TWITTER_CALLBACK_URI: {
  106. // ns: 'crowi',
  107. // key: 'security:passport-twitter:callbackUrl',
  108. // type: ,
  109. // default:
  110. // },
  111. APP_SITE_URL: {
  112. ns: 'crowi',
  113. key: 'app:siteUrl',
  114. type: TYPES.STRING,
  115. default: null
  116. },
  117. SAML_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS: {
  118. ns: 'crowi',
  119. key: 'security:passport-saml:useOnlyEnvVarsForSomeOptions',
  120. type: TYPES.BOOLEAN,
  121. default: false
  122. },
  123. SAML_ENABLED: {
  124. ns: 'crowi',
  125. key: 'security:passport-saml:isEnabled',
  126. type: TYPES.BOOLEAN,
  127. default: null
  128. },
  129. SAML_ENTRY_POINT: {
  130. ns: 'crowi',
  131. key: 'security:passport-saml:entryPoint',
  132. type: TYPES.STRING,
  133. default: null
  134. },
  135. SAML_CALLBACK_URI: {
  136. ns: 'crowi',
  137. key: 'security:passport-saml:callbackUrl',
  138. type: TYPES.STRING,
  139. default: null
  140. },
  141. SAML_ISSUER: {
  142. ns: 'crowi',
  143. key: 'security:passport-saml:issuer',
  144. type: TYPES.STRING,
  145. default: null
  146. },
  147. SAML_ATTR_MAPPING_ID: {
  148. ns: 'crowi',
  149. key: 'security:passport-saml:attrMapId',
  150. type: TYPES.STRING,
  151. default: null
  152. },
  153. SAML_ATTR_MAPPING_USERNAME: {
  154. ns: 'crowi',
  155. key: 'security:passport-saml:attrMapUsername',
  156. type: TYPES.STRING,
  157. default: null
  158. },
  159. SAML_ATTR_MAPPING_MAIL: {
  160. ns: 'crowi',
  161. key: 'security:passport-saml:attrMapMail',
  162. type: TYPES.STRING,
  163. default: null
  164. },
  165. SAML_ATTR_MAPPING_FIRST_NAME: {
  166. ns: 'crowi',
  167. key: 'security:passport-saml:attrMapFirstName',
  168. type: TYPES.STRING,
  169. default: null
  170. },
  171. SAML_ATTR_MAPPING_LAST_NAME: {
  172. ns: 'crowi',
  173. key: 'security:passport-saml:attrMapLastName',
  174. type: TYPES.STRING,
  175. default: null
  176. },
  177. SAML_CERT: {
  178. ns: 'crowi',
  179. key: 'security:passport-saml:cert',
  180. type: TYPES.STRING,
  181. default: null
  182. }
  183. };
  184. class ConfigLoader {
  185. constructor(configModel) {
  186. this.configModel = configModel;
  187. }
  188. /**
  189. * return a config object
  190. */
  191. async load() {
  192. const configFromDB = await this.loadFromDB();
  193. const configFromEnvVars = this.loadFromEnvVars();
  194. // merge defaults
  195. let mergedConfigFromDB = Object.assign({'crowi': this.configModel.getDefaultCrowiConfigsObject()}, configFromDB);
  196. mergedConfigFromDB = Object.assign({'markdown': this.configModel.getDefaultMarkdownConfigsObject()}, mergedConfigFromDB);
  197. // In getConfig API, only null is used as a value to indicate that a config is not set.
  198. // So, if a value loaded from the database is emtpy,
  199. // it is converted to null because an empty string is used as the same meaning in the config model.
  200. // By this processing, whether a value is loaded from the database or from the environment variable,
  201. // only null indicates a config is not set.
  202. for (const namespace of Object.keys(mergedConfigFromDB)) {
  203. for (const key of Object.keys(mergedConfigFromDB[namespace])) {
  204. if (mergedConfigFromDB[namespace][key] === '') {
  205. mergedConfigFromDB[namespace][key] = null;
  206. }
  207. }
  208. }
  209. return {
  210. fromDB: mergedConfigFromDB,
  211. fromEnvVars: configFromEnvVars
  212. };
  213. }
  214. async loadFromDB() {
  215. const config = {};
  216. const docs = await this.configModel.find().exec();
  217. for (const doc of docs) {
  218. if (!config[doc.ns]) {
  219. config[doc.ns] = {};
  220. }
  221. config[doc.ns][doc.key] = JSON.parse(doc.value);
  222. }
  223. debug('ConfigLoader#loadFromDB', config);
  224. return config;
  225. }
  226. loadFromEnvVars() {
  227. const config = {};
  228. for (const ENV_VAR_NAME of Object.keys(ENV_VAR_NAME_TO_CONFIG_INFO)) {
  229. const configInfo = ENV_VAR_NAME_TO_CONFIG_INFO[ENV_VAR_NAME];
  230. if (config[configInfo.ns] === undefined) {
  231. config[configInfo.ns] = {};
  232. }
  233. if (process.env[ENV_VAR_NAME] === undefined) {
  234. config[configInfo.ns][configInfo.key] = configInfo.default;
  235. }
  236. else {
  237. config[configInfo.ns][configInfo.key] = configInfo.type.parse(process.env[ENV_VAR_NAME]);
  238. }
  239. }
  240. debug('ConfigLoader#loadFromEnvVars', config);
  241. return config;
  242. }
  243. }
  244. module.exports = ConfigLoader;