diff --git a/lib/config/index.js b/lib/config/index.js index 21c5b2f0f..e69de29bb 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -1,209 +0,0 @@ - -'use strict' - -const crypto = require('crypto') -const fs = require('fs') -const path = require('path') -const { merge } = require('lodash') -const deepFreeze = require('deep-freeze') -const { Environment, Permission } = require('./enum') -const logger = require('../logger') -const { getGitCommit, getGitHubURL } = require('./utils') - -const appRootPath = path.resolve(__dirname, '../../') -const env = process.env.NODE_ENV || Environment.development -const debugConfig = { - debug: (env === Environment.development) -} - -// Get version string from package.json -const { version, repository } = require(path.join(appRootPath, 'package.json')) - -const commitID = getGitCommit(appRootPath) -const sourceURL = getGitHubURL(repository.url, commitID || version) -const fullversion = commitID ? `${version}-${commitID}` : version - -const packageConfig = { - version: version, - minimumCompatibleVersion: '0.5.0', - fullversion: fullversion, - sourceURL: sourceURL -} - -const configFilePath = path.resolve(appRootPath, process.env.CMD_CONFIG_FILE || -'config.json') -const fileConfig = fs.existsSync(configFilePath) ? require(configFilePath)[env] : undefined - -let config = require('./default') -merge(config, require('./defaultSSL')) -merge(config, require('./oldDefault')) -merge(config, debugConfig) -merge(config, packageConfig) -merge(config, fileConfig) -merge(config, require('./oldEnvironment')) -merge(config, require('./hackmdEnvironment')) -merge(config, require('./environment')) -merge(config, require('./dockerSecret')) - -if (['debug', 'verbose', 'info', 'warn', 'error'].includes(config.loglevel)) { - logger.level = config.loglevel -} else { - logger.error('Selected loglevel %s doesn\'t exist, using default level \'debug\'. Available options: debug, verbose, info, warn, error', config.loglevel) -} - -// load LDAP CA -if (config.ldap.tlsca) { - let ca = config.ldap.tlsca.split(',') - let caContent = [] - for (let i of ca) { - if (fs.existsSync(i)) { - caContent.push(fs.readFileSync(i, 'utf8')) - } - } - let tlsOptions = { - ca: caContent - } - config.ldap.tlsOptions = config.ldap.tlsOptions ? Object.assign(config.ldap.tlsOptions, tlsOptions) : tlsOptions -} - -// Permission -config.permission = Permission -if (!config.allowAnonymous && !config.allowAnonymousEdits) { - delete config.permission.freely -} -if (!(config.defaultPermission in config.permission)) { - config.defaultPermission = config.permission.editable -} - -// cache result, cannot change config in runtime!!! -config.isStandardHTTPsPort = (function isStandardHTTPsPort () { - return config.useSSL && config.port === 443 -})() -config.isStandardHTTPPort = (function isStandardHTTPPort () { - return !config.useSSL && config.port === 80 -})() - -// cache serverURL -config.serverURL = (function getserverurl () { - var url = '' - if (config.domain) { - var protocol = config.protocolUseSSL ? 'https://' : 'http://' - url = protocol + config.domain - if (config.urlAddPort) { - if (!config.isStandardHTTPPort || !config.isStandardHTTPsPort) { - url += ':' + config.port - } - } - } - if (config.urlPath) { - url += '/' + config.urlPath - } - return url -})() - -if (config.serverURL === '') { - logger.warn('Neither \'domain\' nor \'CMD_DOMAIN\' is configured. This can cause issues with various components.\nHint: Make sure \'protocolUseSSL\' and \'urlAddPort\' or \'CMD_PROTOCOL_USESSL\' and \'CMD_URL_ADDPORT\' are configured properly.') -} - -config.Environment = Environment - -// auth method -config.isFacebookEnable = config.facebook.clientID && config.facebook.clientSecret -config.isGoogleEnable = config.google.clientID && config.google.clientSecret -config.isDropboxEnable = config.dropbox.clientID && config.dropbox.clientSecret -config.isTwitterEnable = config.twitter.consumerKey && config.twitter.consumerSecret -config.isEmailEnable = config.email -config.isOpenIDEnable = config.openID -config.isGitHubEnable = config.github.clientID && config.github.clientSecret -config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret -config.isLDAPEnable = config.ldap.url -config.isSAMLEnable = config.saml.idpSsoUrl -config.isOAuth2Enable = config.oauth2.clientID && config.oauth2.clientSecret - -// Check gitlab api version -if (config.gitlab && config.gitlab.version !== 'v4' && config.gitlab.version !== 'v3') { - logger.warn('config.js contains wrong version (' + config.gitlab.version + ') for gitlab api; it should be \'v3\' or \'v4\'. Defaulting to v4') - config.gitlab.version = 'v4' -} -// If gitlab scope is api, enable snippets Export/import -config.isGitlabSnippetsEnable = (!config.gitlab.scope || config.gitlab.scope === 'api') && config.isGitLabEnable - -// Only update i18n files in development setups -config.updateI18nFiles = (env === Environment.development) - -// merge legacy values -let keys = Object.keys(config) -const uppercase = /[A-Z]/ -for (let i = keys.length; i--;) { - let lowercaseKey = keys[i].toLowerCase() - // if the config contains uppercase letters - // and a lowercase version of this setting exists - // and the config with uppercase is not set - // we set the new config using the old key. - if (uppercase.test(keys[i]) && - config[lowercaseKey] !== undefined && - fileConfig[keys[i]] === undefined) { - logger.warn('config.js contains deprecated lowercase setting for ' + keys[i] + '. Please change your config.js file to replace ' + lowercaseKey + ' with ' + keys[i]) - config[keys[i]] = config[lowercaseKey] - } -} - -// Notify users about the prefix change and inform them they use legacy prefix for environment variables -if (Object.keys(process.env).toString().indexOf('HMD_') !== -1) { - logger.warn('Using legacy HMD prefix for environment variables. Please change your variables in future. For details see: https://github.com/codimd/server#environment-variables-will-overwrite-other-server-configs') -} - -// Generate session secret if it stays on default values -if (config.sessionSecret === 'secret') { - logger.warn('Session secret not set. Using random generated one. Please set `sessionSecret` in your config.js file. All users will be logged out.') - config.sessionSecret = crypto.randomBytes(Math.ceil(config.sessionSecretLen / 2)) // generate crypto graphic random number - .toString('hex') // convert to hexadecimal format - .slice(0, config.sessionSecretLen) // return required number of characters -} - -// Validate upload upload providers -if (['filesystem', 's3', 'minio', 'imgur', 'azure', 'lutim'].indexOf(config.imageUploadType) === -1) { - logger.error('"imageuploadtype" is not correctly set. Please use "filesystem", "s3", "minio", "azure", "lutim" or "imgur". Defaulting to "filesystem"') - config.imageUploadType = 'filesystem' -} - -// figure out mime types for image uploads -switch (config.imageUploadType) { - case 'imgur': - config.allowedUploadMimeTypes = [ - 'image/jpeg', - 'image/png', - 'image/jpg', - 'image/gif' - ] - break - default: - config.allowedUploadMimeTypes = [ - 'image/jpeg', - 'image/png', - 'image/jpg', - 'image/gif', - 'image/svg+xml' - ] -} - -// generate correct path -config.sslCAPath.forEach(function (capath, i, array) { - array[i] = path.resolve(appRootPath, capath) -}) - -config.sslCertPath = path.resolve(appRootPath, config.sslCertPath) -config.sslKeyPath = path.resolve(appRootPath, config.sslKeyPath) -config.dhParamPath = path.resolve(appRootPath, config.dhParamPath) -config.viewPath = path.resolve(appRootPath, config.viewPath) -config.tmpPath = path.resolve(appRootPath, config.tmpPath) -config.publicPath = path.resolve(appRootPath, config.publicPath) -config.defaultNotePath = path.resolve(appRootPath, config.defaultNotePath) -config.docsPath = path.resolve(appRootPath, config.docsPath) -config.uploadsPath = path.resolve(appRootPath, config.uploadsPath) -config.localesPath = path.resolve(appRootPath, config.localesPath) - -// make config readonly -config = deepFreeze(config) - -module.exports = config diff --git a/lib/config/index.ts b/lib/config/index.ts new file mode 100644 index 000000000..3e4339363 --- /dev/null +++ b/lib/config/index.ts @@ -0,0 +1,204 @@ +import crypto from 'crypto' +import fs from 'fs' +import path from 'path' +import { merge } from 'lodash' +import deepFreeze = require('deep-freeze') +import { Environment, Permission } from './enum' +import { logger } from '../logger' +import { getGitCommit, getGitHubURL } from './utils' + +const appRootPath = path.resolve(__dirname, '../../') +const env = process.env.NODE_ENV || Environment.development +const debugConfig = { + debug: (env === Environment.development) +} + +// Get version string from package.json +const { version, repository } = require(path.join(appRootPath, 'package.json')) + +const commitID = getGitCommit(appRootPath) +const sourceURL = getGitHubURL(repository.url, commitID || version) +const fullversion = commitID ? `${version}-${commitID}` : version + +const packageConfig = { + version: version, + minimumCompatibleVersion: '0.5.0', + fullversion: fullversion, + sourceURL: sourceURL +} + +const configFilePath = path.resolve(appRootPath, process.env.CMD_CONFIG_FILE || +'config.json') +const fileConfig = fs.existsSync(configFilePath) ? require(configFilePath)[env] : undefined + +let defaultConfig = require('./default') +merge(defaultConfig, require('./defaultSSL')) +merge(defaultConfig, require('./oldDefault')) +merge(defaultConfig, debugConfig) +merge(defaultConfig, packageConfig) +merge(defaultConfig, fileConfig) +merge(defaultConfig, require('./oldEnvironment')) +merge(defaultConfig, require('./hackmdEnvironment')) +merge(defaultConfig, require('./environment')) +merge(defaultConfig, require('./dockerSecret')) + +if (['debug', 'verbose', 'info', 'warn', 'error'].includes(defaultConfig.loglevel)) { + logger.level = defaultConfig.loglevel +} else { + logger.error('Selected loglevel %s doesn\'t exist, using default level \'debug\'. Available options: debug, verbose, info, warn, error', defaultConfig.loglevel) +} + +// load LDAP CA +if (defaultConfig.ldap.tlsca) { + let ca = defaultConfig.ldap.tlsca.split(',') + let caContent: string[] = [] + for (let i of ca) { + if (fs.existsSync(i)) { + caContent.push(fs.readFileSync(i, 'utf8')) + } + } + let tlsOptions = { + ca: caContent + } + defaultConfig.ldap.tlsOptions = defaultConfig.ldap.tlsOptions ? Object.assign(defaultConfig.ldap.tlsOptions, tlsOptions) : tlsOptions +} + +// Permission +defaultConfig.permission = Permission +if (!defaultConfig.allowAnonymous && !defaultConfig.allowAnonymousEdits) { + delete defaultConfig.permission.freely +} +if (!(defaultConfig.defaultPermission in defaultConfig.permission)) { + defaultConfig.defaultPermission = defaultConfig.permission.editable +} + +// cache result, cannot change config in runtime!!! +defaultConfig.isStandardHTTPsPort = (function isStandardHTTPsPort () { + return defaultConfig.useSSL && defaultConfig.port === 443 +})() +defaultConfig.isStandardHTTPPort = (function isStandardHTTPPort () { + return !defaultConfig.useSSL && defaultConfig.port === 80 +})() + +// cache serverURL +defaultConfig.serverURL = (function getserverurl () { + var url = '' + if (defaultConfig.domain) { + var protocol = defaultConfig.protocolUseSSL ? 'https://' : 'http://' + url = protocol + defaultConfig.domain + if (defaultConfig.urlAddPort) { + if (!defaultConfig.isStandardHTTPPort || !defaultConfig.isStandardHTTPsPort) { + url += ':' + defaultConfig.port + } + } + } + if (defaultConfig.urlPath) { + url += '/' + defaultConfig.urlPath + } + return url +})() + +if (defaultConfig.serverURL === '') { + logger.warn('Neither \'domain\' nor \'CMD_DOMAIN\' is configured. This can cause issues with various components.\nHint: Make sure \'protocolUseSSL\' and \'urlAddPort\' or \'CMD_PROTOCOL_USESSL\' and \'CMD_URL_ADDPORT\' are configured properly.') +} + +defaultConfig.Environment = Environment + +// auth method +defaultConfig.isFacebookEnable = defaultConfig.facebook.clientID && defaultConfig.facebook.clientSecret +defaultConfig.isGoogleEnable = defaultConfig.google.clientID && defaultConfig.google.clientSecret +defaultConfig.isDropboxEnable = defaultConfig.dropbox.clientID && defaultConfig.dropbox.clientSecret +defaultConfig.isTwitterEnable = defaultConfig.twitter.consumerKey && defaultConfig.twitter.consumerSecret +defaultConfig.isEmailEnable = defaultConfig.email +defaultConfig.isOpenIDEnable = defaultConfig.openID +defaultConfig.isGitHubEnable = defaultConfig.github.clientID && defaultConfig.github.clientSecret +defaultConfig.isGitLabEnable = defaultConfig.gitlab.clientID && defaultConfig.gitlab.clientSecret +defaultConfig.isLDAPEnable = defaultConfig.ldap.url +defaultConfig.isSAMLEnable = defaultConfig.saml.idpSsoUrl +defaultConfig.isOAuth2Enable = defaultConfig.oauth2.clientID && defaultConfig.oauth2.clientSecret + +// Check gitlab api version +if (defaultConfig.gitlab && defaultConfig.gitlab.version !== 'v4' && defaultConfig.gitlab.version !== 'v3') { + logger.warn('config.js contains wrong version (' + defaultConfig.gitlab.version + ') for gitlab api; it should be \'v3\' or \'v4\'. Defaulting to v4') + defaultConfig.gitlab.version = 'v4' +} +// If gitlab scope is api, enable snippets Export/import +defaultConfig.isGitlabSnippetsEnable = (!defaultConfig.gitlab.scope || defaultConfig.gitlab.scope === 'api') && defaultConfig.isGitLabEnable + +// Only update i18n files in development setups +defaultConfig.updateI18nFiles = (env === Environment.development) + +// merge legacy values +let keys = Object.keys(defaultConfig) +const uppercase = /[A-Z]/ +for (let i = keys.length; i--;) { + let lowercaseKey = keys[i].toLowerCase() + // if the config contains uppercase letters + // and a lowercase version of this setting exists + // and the config with uppercase is not set + // we set the new config using the old key. + if (uppercase.test(keys[i]) && + defaultConfig[lowercaseKey] !== undefined && + fileConfig[keys[i]] === undefined) { + logger.warn('config.js contains deprecated lowercase setting for ' + keys[i] + '. Please change your config.js file to replace ' + lowercaseKey + ' with ' + keys[i]) + defaultConfig[keys[i]] = defaultConfig[lowercaseKey] + } +} + +// Notify users about the prefix change and inform them they use legacy prefix for environment variables +if (Object.keys(process.env).toString().indexOf('HMD_') !== -1) { + logger.warn('Using legacy HMD prefix for environment variables. Please change your variables in future. For details see: https://github.com/codimd/server#environment-variables-will-overwrite-other-server-configs') +} + +// Generate session secret if it stays on default values +if (defaultConfig.sessionSecret === 'secret') { + logger.warn('Session secret not set. Using random generated one. Please set `sessionSecret` in your config.js file. All users will be logged out.') + defaultConfig.sessionSecret = crypto.randomBytes(Math.ceil(defaultConfig.sessionSecretLen / 2)) // generate crypto graphic random number + .toString('hex') // convert to hexadecimal format + .slice(0, defaultConfig.sessionSecretLen) // return required number of characters +} + +// Validate upload upload providers +if (['filesystem', 's3', 'minio', 'imgur', 'azure', 'lutim'].indexOf(defaultConfig.imageUploadType) === -1) { + logger.error('"imageuploadtype" is not correctly set. Please use "filesystem", "s3", "minio", "azure", "lutim" or "imgur". Defaulting to "filesystem"') + defaultConfig.imageUploadType = 'filesystem' +} + +// figure out mime types for image uploads +switch (defaultConfig.imageUploadType) { + case 'imgur': + defaultConfig.allowedUploadMimeTypes = [ + 'image/jpeg', + 'image/png', + 'image/jpg', + 'image/gif' + ] + break + default: + defaultConfig.allowedUploadMimeTypes = [ + 'image/jpeg', + 'image/png', + 'image/jpg', + 'image/gif', + 'image/svg+xml' + ] +} + +// generate correct path +defaultConfig.sslCAPath.forEach(function (capath, i, array) { + array[i] = path.resolve(appRootPath, capath) +}) + +defaultConfig.sslCertPath = path.resolve(appRootPath, defaultConfig.sslCertPath) +defaultConfig.sslKeyPath = path.resolve(appRootPath, defaultConfig.sslKeyPath) +defaultConfig.dhParamPath = path.resolve(appRootPath, defaultConfig.dhParamPath) +defaultConfig.viewPath = path.resolve(appRootPath, defaultConfig.viewPath) +defaultConfig.tmpPath = path.resolve(appRootPath, defaultConfig.tmpPath) +defaultConfig.publicPath = path.resolve(appRootPath, defaultConfig.publicPath) +defaultConfig.defaultNotePath = path.resolve(appRootPath, defaultConfig.defaultNotePath) +defaultConfig.docsPath = path.resolve(appRootPath, defaultConfig.docsPath) +defaultConfig.uploadsPath = path.resolve(appRootPath, defaultConfig.uploadsPath) +defaultConfig.localesPath = path.resolve(appRootPath, defaultConfig.localesPath) + +// make config readonly +export const config = deepFreeze(defaultConfig)