mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-31 15:53:33 +00:00 
			
		
		
		
	Refactor configuration
- No longer use config npm package - Prefer config from env vars, though still has support for config file - No longer writes a config file for database config - Writes keys to a new file in /data folder - Removes a lot of cruft and improves config understanding
This commit is contained in:
		| @@ -2,6 +2,7 @@ const express     = require('express'); | |||||||
| const bodyParser  = require('body-parser'); | const bodyParser  = require('body-parser'); | ||||||
| const fileUpload  = require('express-fileupload'); | const fileUpload  = require('express-fileupload'); | ||||||
| const compression = require('compression'); | const compression = require('compression'); | ||||||
|  | const config      = require('./lib/config'); | ||||||
| const log         = require('./logger').express; | const log         = require('./logger').express; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -24,7 +25,7 @@ app.enable('trust proxy', ['loopback', 'linklocal', 'uniquelocal']); | |||||||
| app.enable('strict routing'); | app.enable('strict routing'); | ||||||
|  |  | ||||||
| // pretty print JSON when not live | // pretty print JSON when not live | ||||||
| if (process.env.NODE_ENV !== 'production') { | if (config.debug()) { | ||||||
| 	app.set('json spaces', 2); | 	app.set('json spaces', 2); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -65,7 +66,7 @@ app.use(function (err, req, res, next) { | |||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	if (process.env.NODE_ENV === 'development' || (req.baseUrl + req.path).includes('nginx/certificates')) { | 	if (config.debug() || (req.baseUrl + req.path).includes('nginx/certificates')) { | ||||||
| 		payload.debug = { | 		payload.debug = { | ||||||
| 			stack:    typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null, | 			stack:    typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null, | ||||||
| 			previous: err.previous | 			previous: err.previous | ||||||
| @@ -74,7 +75,7 @@ app.use(function (err, req, res, next) { | |||||||
|  |  | ||||||
| 	// Not every error is worth logging - but this is good for now until it gets annoying. | 	// Not every error is worth logging - but this is good for now until it gets annoying. | ||||||
| 	if (typeof err.stack !== 'undefined' && err.stack) { | 	if (typeof err.stack !== 'undefined' && err.stack) { | ||||||
| 		if (process.env.NODE_ENV === 'development' || process.env.DEBUG) { | 		if (config.debug()) { | ||||||
| 			log.debug(err.stack); | 			log.debug(err.stack); | ||||||
| 		} else if (typeof err.public == 'undefined' || !err.public) { | 		} else if (typeof err.public == 'undefined' || !err.public) { | ||||||
| 			log.warn(err.message); | 			log.warn(err.message); | ||||||
|   | |||||||
| @@ -1,33 +1,27 @@ | |||||||
| const config = require('config'); | const config = require('./lib/config'); | ||||||
|  |  | ||||||
| if (!config.has('database')) { | if (!config.has('database')) { | ||||||
| 	throw new Error('Database config does not exist! Please read the instructions: https://github.com/jc21/nginx-proxy-manager/blob/master/doc/INSTALL.md'); | 	throw new Error('Database config does not exist! Please read the instructions: https://nginxproxymanager.com/setup/'); | ||||||
| } | } | ||||||
|  |  | ||||||
| function generateDbConfig() { | function generateDbConfig() { | ||||||
| 	if (config.database.engine === 'knex-native') { | 	const cfg = config.get('database'); | ||||||
| 		return config.database.knex; | 	if (cfg.engine === 'knex-native') { | ||||||
| 	} else | 		return cfg.knex; | ||||||
| 		return { | 	} | ||||||
| 			client:     config.database.engine, | 	return { | ||||||
| 			connection: { | 		client:     cfg.engine, | ||||||
| 				host:     config.database.host, | 		connection: { | ||||||
| 				user:     config.database.user, | 			host:     cfg.host, | ||||||
| 				password: config.database.password, | 			user:     cfg.user, | ||||||
| 				database: config.database.name, | 			password: cfg.password, | ||||||
| 				port:     config.database.port | 			database: cfg.name, | ||||||
| 			}, | 			port:     cfg.port | ||||||
| 			migrations: { | 		}, | ||||||
| 				tableName: 'migrations' | 		migrations: { | ||||||
| 			} | 			tableName: 'migrations' | ||||||
| 		}; | 		} | ||||||
|  | 	}; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | module.exports = require('knex')(generateDbConfig()); | ||||||
| let data = generateDbConfig(); |  | ||||||
|  |  | ||||||
| if (typeof config.database.version !== 'undefined') { |  | ||||||
| 	data.version = config.database.version; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| module.exports = require('knex')(data); |  | ||||||
|   | |||||||
| @@ -1,11 +1,9 @@ | |||||||
| #!/usr/bin/env node | #!/usr/bin/env node | ||||||
|  |  | ||||||
|  | const fs     = require('fs'); | ||||||
| const logger = require('./logger').global; | const logger = require('./logger').global; | ||||||
|  |  | ||||||
| async function appStart () { | async function appStart () { | ||||||
| 	// Create config file db settings if environment variables have been set |  | ||||||
| 	await createDbConfigFromEnvironment(); |  | ||||||
|  |  | ||||||
| 	const migrate             = require('./migrate'); | 	const migrate             = require('./migrate'); | ||||||
| 	const setup               = require('./setup'); | 	const setup               = require('./setup'); | ||||||
| 	const app                 = require('./app'); | 	const app                 = require('./app'); | ||||||
| @@ -42,90 +40,6 @@ async function appStart () { | |||||||
| 		}); | 		}); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function createDbConfigFromEnvironment() { |  | ||||||
| 	return new Promise((resolve, reject) => { |  | ||||||
| 		const envMysqlHost = process.env.DB_MYSQL_HOST || null; |  | ||||||
| 		const envMysqlPort = process.env.DB_MYSQL_PORT || null; |  | ||||||
| 		const envMysqlUser = process.env.DB_MYSQL_USER || null; |  | ||||||
| 		const envMysqlName = process.env.DB_MYSQL_NAME || null; |  | ||||||
| 		let envSqliteFile  = process.env.DB_SQLITE_FILE || null; |  | ||||||
|  |  | ||||||
| 		const fs       = require('fs'); |  | ||||||
| 		const filename = (process.env.NODE_CONFIG_DIR || './config') + '/' + (process.env.NODE_ENV || 'default') + '.json'; |  | ||||||
| 		let configData = {}; |  | ||||||
|  |  | ||||||
| 		try { |  | ||||||
| 			configData = require(filename); |  | ||||||
| 		} catch (err) { |  | ||||||
| 			// do nothing |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if (configData.database && configData.database.engine && !configData.database.fromEnv) { |  | ||||||
| 			logger.info('Manual db configuration already exists, skipping config creation from environment variables'); |  | ||||||
| 			resolve(); |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if ((!envMysqlHost || !envMysqlPort || !envMysqlUser || !envMysqlName) && !envSqliteFile){ |  | ||||||
| 			envSqliteFile = '/data/database.sqlite'; |  | ||||||
| 			logger.info(`No valid environment variables for database provided, using default SQLite file '${envSqliteFile}'`); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if (envMysqlHost && envMysqlPort && envMysqlUser && envMysqlName) { |  | ||||||
| 			const newConfig = { |  | ||||||
| 				fromEnv:  true, |  | ||||||
| 				engine:   'mysql', |  | ||||||
| 				host:     envMysqlHost, |  | ||||||
| 				port:     envMysqlPort, |  | ||||||
| 				user:     envMysqlUser, |  | ||||||
| 				password: process.env.DB_MYSQL_PASSWORD, |  | ||||||
| 				name:     envMysqlName, |  | ||||||
| 			}; |  | ||||||
|  |  | ||||||
| 			if (JSON.stringify(configData.database) === JSON.stringify(newConfig)) { |  | ||||||
| 				// Config is unchanged, skip overwrite |  | ||||||
| 				resolve(); |  | ||||||
| 				return; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			logger.info('Generating MySQL knex configuration from environment variables'); |  | ||||||
| 			configData.database = newConfig; |  | ||||||
|  |  | ||||||
| 		} else { |  | ||||||
| 			const newConfig = { |  | ||||||
| 				fromEnv: true, |  | ||||||
| 				engine:  'knex-native', |  | ||||||
| 				knex:    { |  | ||||||
| 					client:     'sqlite3', |  | ||||||
| 					connection: { |  | ||||||
| 						filename: envSqliteFile |  | ||||||
| 					}, |  | ||||||
| 					useNullAsDefault: true |  | ||||||
| 				} |  | ||||||
| 			}; |  | ||||||
| 			if (JSON.stringify(configData.database) === JSON.stringify(newConfig)) { |  | ||||||
| 				// Config is unchanged, skip overwrite |  | ||||||
| 				resolve(); |  | ||||||
| 				return; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			logger.info('Generating SQLite knex configuration'); |  | ||||||
| 			configData.database = newConfig; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Write config |  | ||||||
| 		fs.writeFile(filename, JSON.stringify(configData, null, 2), (err) => { |  | ||||||
| 			if (err) { |  | ||||||
| 				logger.error('Could not write db config to config file: ' + filename); |  | ||||||
| 				reject(err); |  | ||||||
| 			} else { |  | ||||||
| 				logger.debug('Wrote db configuration to config file: ' + filename); |  | ||||||
| 				resolve(); |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| try { | try { | ||||||
| 	appStart(); | 	appStart(); | ||||||
| } catch (err) { | } catch (err) { | ||||||
|   | |||||||
| @@ -1,22 +1,24 @@ | |||||||
| const _                  = require('lodash'); | const _                = require('lodash'); | ||||||
| const fs                 = require('fs'); | const fs               = require('fs'); | ||||||
| const https              = require('https'); | const https            = require('https'); | ||||||
| const tempWrite          = require('temp-write'); | const tempWrite        = require('temp-write'); | ||||||
| const moment             = require('moment'); | const moment           = require('moment'); | ||||||
| const logger             = require('../logger').ssl; | const logger           = require('../logger').ssl; | ||||||
| const error              = require('../lib/error'); | const config           = require('../lib/config'); | ||||||
| const utils              = require('../lib/utils'); | const error            = require('../lib/error'); | ||||||
| const certificateModel   = require('../models/certificate'); | const utils            = require('../lib/utils'); | ||||||
| const dnsPlugins         = require('../global/certbot-dns-plugins'); | const certificateModel = require('../models/certificate'); | ||||||
| const internalAuditLog   = require('./audit-log'); | const dnsPlugins       = require('../global/certbot-dns-plugins'); | ||||||
| const internalNginx      = require('./nginx'); | const internalAuditLog = require('./audit-log'); | ||||||
| const internalHost       = require('./host'); | const internalNginx    = require('./nginx'); | ||||||
| const letsencryptStaging = process.env.NODE_ENV !== 'production'; | const internalHost     = require('./host'); | ||||||
|  | const archiver         = require('archiver'); | ||||||
|  | const path             = require('path'); | ||||||
|  | const { isArray }      = require('lodash'); | ||||||
|  |  | ||||||
|  | const letsencryptStaging = config.useLetsencryptStaging(); | ||||||
| const letsencryptConfig  = '/etc/letsencrypt.ini'; | const letsencryptConfig  = '/etc/letsencrypt.ini'; | ||||||
| const certbotCommand     = 'certbot'; | const certbotCommand     = 'certbot'; | ||||||
| const archiver           = require('archiver'); |  | ||||||
| const path               = require('path'); |  | ||||||
| const { isArray }        = require('lodash'); |  | ||||||
|  |  | ||||||
| function omissions() { | function omissions() { | ||||||
| 	return ['is_deleted']; | 	return ['is_deleted']; | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| const _          = require('lodash'); | const _      = require('lodash'); | ||||||
| const fs         = require('fs'); | const fs     = require('fs'); | ||||||
| const logger     = require('../logger').nginx; | const logger = require('../logger').nginx; | ||||||
| const utils      = require('../lib/utils'); | const config = require('../lib/config'); | ||||||
| const error      = require('../lib/error'); | const utils  = require('../lib/utils'); | ||||||
| const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG; | const error  = require('../lib/error'); | ||||||
|  |  | ||||||
| const internalNginx = { | const internalNginx = { | ||||||
|  |  | ||||||
| @@ -65,7 +65,7 @@ const internalNginx = { | |||||||
| 							} | 							} | ||||||
| 						}); | 						}); | ||||||
|  |  | ||||||
| 						if (debug_mode) { | 						if (config.debug()) { | ||||||
| 							logger.error('Nginx test failed:', valid_lines.join('\n')); | 							logger.error('Nginx test failed:', valid_lines.join('\n')); | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| @@ -101,7 +101,7 @@ const internalNginx = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	test: () => { | 	test: () => { | ||||||
| 		if (debug_mode) { | 		if (config.debug()) { | ||||||
| 			logger.info('Testing Nginx configuration'); | 			logger.info('Testing Nginx configuration'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -184,7 +184,7 @@ const internalNginx = { | |||||||
| 	generateConfig: (host_type, host) => { | 	generateConfig: (host_type, host) => { | ||||||
| 		const nice_host_type = internalNginx.getFileFriendlyHostType(host_type); | 		const nice_host_type = internalNginx.getFileFriendlyHostType(host_type); | ||||||
|  |  | ||||||
| 		if (debug_mode) { | 		if (config.debug()) { | ||||||
| 			logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2)); | 			logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -239,7 +239,7 @@ const internalNginx = { | |||||||
| 					.then((config_text) => { | 					.then((config_text) => { | ||||||
| 						fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); | 						fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); | ||||||
|  |  | ||||||
| 						if (debug_mode) { | 						if (config.debug()) { | ||||||
| 							logger.success('Wrote config:', filename, config_text); | 							logger.success('Wrote config:', filename, config_text); | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| @@ -249,7 +249,7 @@ const internalNginx = { | |||||||
| 						resolve(true); | 						resolve(true); | ||||||
| 					}) | 					}) | ||||||
| 					.catch((err) => { | 					.catch((err) => { | ||||||
| 						if (debug_mode) { | 						if (config.debug()) { | ||||||
| 							logger.warn('Could not write ' + filename + ':', err.message); | 							logger.warn('Could not write ' + filename + ':', err.message); | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| @@ -268,7 +268,7 @@ const internalNginx = { | |||||||
| 	 * @returns {Promise} | 	 * @returns {Promise} | ||||||
| 	 */ | 	 */ | ||||||
| 	generateLetsEncryptRequestConfig: (certificate) => { | 	generateLetsEncryptRequestConfig: (certificate) => { | ||||||
| 		if (debug_mode) { | 		if (config.debug()) { | ||||||
| 			logger.info('Generating LetsEncrypt Request Config:', certificate); | 			logger.info('Generating LetsEncrypt Request Config:', certificate); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -292,14 +292,14 @@ const internalNginx = { | |||||||
| 				.then((config_text) => { | 				.then((config_text) => { | ||||||
| 					fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); | 					fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); | ||||||
|  |  | ||||||
| 					if (debug_mode) { | 					if (config.debug()) { | ||||||
| 						logger.success('Wrote config:', filename, config_text); | 						logger.success('Wrote config:', filename, config_text); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					resolve(true); | 					resolve(true); | ||||||
| 				}) | 				}) | ||||||
| 				.catch((err) => { | 				.catch((err) => { | ||||||
| 					if (debug_mode) { | 					if (config.debug()) { | ||||||
| 						logger.warn('Could not write ' + filename + ':', err.message); | 						logger.warn('Could not write ' + filename + ':', err.message); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| @@ -416,8 +416,8 @@ const internalNginx = { | |||||||
| 	 * @param   {string}  config | 	 * @param   {string}  config | ||||||
| 	 * @returns {boolean} | 	 * @returns {boolean} | ||||||
| 	 */ | 	 */ | ||||||
| 	advancedConfigHasDefaultLocation: function (config) { | 	advancedConfigHasDefaultLocation: function (cfg) { | ||||||
| 		return !!config.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im); | 		return !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
|   | |||||||
							
								
								
									
										181
									
								
								backend/lib/config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								backend/lib/config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | |||||||
|  | const fs      = require('fs'); | ||||||
|  | const NodeRSA = require('node-rsa'); | ||||||
|  | const { config } = require('process'); | ||||||
|  | const logger  = require('../logger').global; | ||||||
|  |  | ||||||
|  | const keysFile = '/data/keys.json'; | ||||||
|  |  | ||||||
|  | let instance = null; | ||||||
|  |  | ||||||
|  | // 1. Load from config file first (not recommended anymore) | ||||||
|  | // 2. Use config env variables next | ||||||
|  | const configure = () => { | ||||||
|  | 	const filename = (process.env.NODE_CONFIG_DIR || './config') + '/' + (process.env.NODE_ENV || 'default') + '.json'; | ||||||
|  | 	if (fs.existsSync(filename)) { | ||||||
|  | 		let configData; | ||||||
|  | 		try { | ||||||
|  | 			configData = require(filename); | ||||||
|  | 		} catch (err) { | ||||||
|  | 			// do nothing | ||||||
|  | 		} | ||||||
|  | 		if (configData?.database && configData?.database?.engine) { | ||||||
|  | 			logger.info(`Using configuration from file: ${filename}`); | ||||||
|  | 			instance = configData; | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	const envMysqlHost = process.env.DB_MYSQL_HOST || null; | ||||||
|  | 	const envMysqlUser = process.env.DB_MYSQL_USER || null; | ||||||
|  | 	const envMysqlName = process.env.DB_MYSQL_NAME || null; | ||||||
|  | 	if (envMysqlHost && envMysqlUser && envMysqlName) { | ||||||
|  | 		// we have enough mysql creds to go with mysql | ||||||
|  | 		logger.info('Using MySQL configuration'); | ||||||
|  | 		instance = { | ||||||
|  | 			database: { | ||||||
|  | 				engine:   'mysql', | ||||||
|  | 				host:     envMysqlHost, | ||||||
|  | 				port:     process.env.DB_MYSQL_PORT || 3306, | ||||||
|  | 				user:     envMysqlUser, | ||||||
|  | 				password: process.env.DB_MYSQL_PASSWORD, | ||||||
|  | 				name:     envMysqlName, | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite'; | ||||||
|  | 	logger.info(`Using Sqlite: ${envSqliteFile}`); | ||||||
|  | 	instance = { | ||||||
|  | 		database: { | ||||||
|  | 			engine:  'knex-native', | ||||||
|  | 			knex:    { | ||||||
|  | 				client:     'sqlite3', | ||||||
|  | 				connection: { | ||||||
|  | 					filename: envSqliteFile | ||||||
|  | 				}, | ||||||
|  | 				useNullAsDefault: true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	// Get keys from file | ||||||
|  | 	if (!fs.existsSync(keysFile)) { | ||||||
|  | 		generateKeys(); | ||||||
|  | 	} else if (!!process.env.DEBUG) { | ||||||
|  | 		logger.info('Keys file exists OK'); | ||||||
|  | 	} | ||||||
|  | 	try { | ||||||
|  | 		instance.keys = require(keysFile); | ||||||
|  | 	} catch (err) { | ||||||
|  | 		logger.error('Could not read JWT key pair from config file: ' + keysFile, err); | ||||||
|  | 		process.exit(1); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	logger.debug('Configuration: ' + JSON.stringify(instance, null, 2)); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const generateKeys = () => { | ||||||
|  | 	logger.info('Creating a new JWT key pair...'); | ||||||
|  | 	// Now create the keys and save them in the config. | ||||||
|  | 	const key = new NodeRSA({ b: 2048 }); | ||||||
|  | 	key.generateKeyPair(); | ||||||
|  |  | ||||||
|  | 	const keys = { | ||||||
|  | 		key: key.exportKey('private').toString(), | ||||||
|  | 		pub: key.exportKey('public').toString(), | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	// Write keys config | ||||||
|  | 	try { | ||||||
|  | 		fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2)); | ||||||
|  | 	} catch (err) { | ||||||
|  | 		logger.error('Could not write JWT key pair to config file: ' + keysFile + ': ' . err.message); | ||||||
|  | 		process.exit(1); | ||||||
|  | 	} | ||||||
|  | 	logger.info('Wrote JWT key pair to config file: ' + keysFile); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * | ||||||
|  | 	 * @param   {string}  key   ie: 'database' or 'database.engine' | ||||||
|  | 	 * @returns {boolean} | ||||||
|  | 	 */ | ||||||
|  | 	has: function(key) { | ||||||
|  | 		instance === null && configure(); | ||||||
|  | 		const keys = key.split('.'); | ||||||
|  | 		let level = instance; | ||||||
|  | 		let has = true; | ||||||
|  | 		keys.forEach((keyItem) =>{ | ||||||
|  | 			if (typeof level[keyItem] === 'undefined') { | ||||||
|  | 				has = false; | ||||||
|  | 			} else { | ||||||
|  | 				level = level[keyItem]; | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		return has; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Gets a specific key from the top level | ||||||
|  | 	 * | ||||||
|  | 	 * @param {string} key | ||||||
|  | 	 * @returns {*} | ||||||
|  | 	 */ | ||||||
|  | 	get: function (key) { | ||||||
|  | 		instance === null && configure(); | ||||||
|  | 		if (key && typeof instance[key] !== 'undefined') { | ||||||
|  | 			return instance[key]; | ||||||
|  | 		} | ||||||
|  | 		return instance; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Is this a sqlite configuration? | ||||||
|  | 	 * | ||||||
|  | 	 * @returns {boolean} | ||||||
|  | 	 */ | ||||||
|  | 	isSqlite: function () { | ||||||
|  | 		instance === null && configure(); | ||||||
|  | 		return instance.database?.knex && instance.database?.knex?.client === 'sqlite3'; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Are we running in debug mdoe? | ||||||
|  | 	 * | ||||||
|  | 	 * @returns {boolean} | ||||||
|  | 	 */ | ||||||
|  | 	debug: function () { | ||||||
|  | 		return !!process.env.DEBUG; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Returns a public key | ||||||
|  | 	 * | ||||||
|  | 	 * @returns {string} | ||||||
|  | 	 */ | ||||||
|  | 	getPublicKey: function () { | ||||||
|  | 		instance === null && configure(); | ||||||
|  | 		return instance?.keys?.pub | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Returns a private key | ||||||
|  | 	 * | ||||||
|  | 	 * @returns {string} | ||||||
|  | 	 */ | ||||||
|  | 	getPrivateKey: function () { | ||||||
|  | 		instance === null && configure(); | ||||||
|  | 		return instance?.keys?.key; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * @returns {boolean} | ||||||
|  | 	 */ | ||||||
|  | 	useLetsencryptStaging: function () { | ||||||
|  | 		return !!process.env.LE_STAGING; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
| @@ -5,7 +5,7 @@ const definitions = require('../../schema/definitions.json'); | |||||||
| RegExp.prototype.toJSON = RegExp.prototype.toString; | RegExp.prototype.toJSON = RegExp.prototype.toString; | ||||||
|  |  | ||||||
| const ajv = require('ajv')({ | const ajv = require('ajv')({ | ||||||
| 	verbose:     true, //process.env.NODE_ENV === 'development', | 	verbose:     true, | ||||||
| 	allErrors:   true, | 	allErrors:   true, | ||||||
| 	format:      'full',  // strict regexes for format checks | 	format:      'full',  // strict regexes for format checks | ||||||
| 	coerceTypes: true, | 	coerceTypes: true, | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| const db     = require('../db'); | const db     = require('../db'); | ||||||
| const config = require('config'); | const config = require('../lib/config'); | ||||||
| const Model  = require('objection').Model; | const Model  = require('objection').Model; | ||||||
|  |  | ||||||
| Model.knex(db); | Model.knex(db); | ||||||
|  |  | ||||||
| module.exports = function () { | module.exports = function () { | ||||||
| 	if (config.database.knex && config.database.knex.client === 'sqlite3') { | 	if (config.isSqlite()) { | ||||||
| 		// eslint-disable-next-line | 		// eslint-disable-next-line | ||||||
| 		return Model.raw("datetime('now','localtime')"); | 		return Model.raw("datetime('now','localtime')"); | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -6,44 +6,36 @@ | |||||||
| const _      = require('lodash'); | const _      = require('lodash'); | ||||||
| const jwt    = require('jsonwebtoken'); | const jwt    = require('jsonwebtoken'); | ||||||
| const crypto = require('crypto'); | const crypto = require('crypto'); | ||||||
|  | const config = require('../lib/config'); | ||||||
| const error  = require('../lib/error'); | const error  = require('../lib/error'); | ||||||
|  | const logger = require('../logger').global; | ||||||
| const ALGO   = 'RS256'; | const ALGO   = 'RS256'; | ||||||
|  |  | ||||||
| let public_key  = null; |  | ||||||
| let private_key = null; |  | ||||||
|  |  | ||||||
| function checkJWTKeyPair() { |  | ||||||
| 	if (!public_key || !private_key) { |  | ||||||
| 		let config  = require('config'); |  | ||||||
| 		public_key  = config.get('jwt.pub'); |  | ||||||
| 		private_key = config.get('jwt.key'); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| module.exports = function () { | module.exports = function () { | ||||||
|  |  | ||||||
| 	let token_data = {}; | 	let token_data = {}; | ||||||
|  |  | ||||||
| 	let self = { | 	const self = { | ||||||
| 		/** | 		/** | ||||||
| 		 * @param {Object}  payload | 		 * @param {Object}  payload | ||||||
| 		 * @returns {Promise} | 		 * @returns {Promise} | ||||||
| 		 */ | 		 */ | ||||||
| 		create: (payload) => { | 		create: (payload) => { | ||||||
|  | 			if (!config.getPrivateKey()) { | ||||||
|  | 				logger.error('Private key is empty!') | ||||||
|  | 			} | ||||||
| 			// sign with RSA SHA256 | 			// sign with RSA SHA256 | ||||||
| 			let options = { | 			const options = { | ||||||
| 				algorithm: ALGO, | 				algorithm: ALGO, | ||||||
| 				expiresIn: payload.expiresIn || '1d' | 				expiresIn: payload.expiresIn || '1d' | ||||||
| 			}; | 			}; | ||||||
|  |  | ||||||
| 			payload.jti = crypto.randomBytes(12) | 			payload.jti = crypto.randomBytes(12) | ||||||
| 				.toString('base64') | 				.toString('base64') | ||||||
| 				.substr(-8); | 				.substring(-8); | ||||||
|  |  | ||||||
| 			checkJWTKeyPair(); |  | ||||||
|  |  | ||||||
| 			return new Promise((resolve, reject) => { | 			return new Promise((resolve, reject) => { | ||||||
| 				jwt.sign(payload, private_key, options, (err, token) => { | 				jwt.sign(payload, config.getPrivateKey(), options, (err, token) => { | ||||||
| 					if (err) { | 					if (err) { | ||||||
| 						reject(err); | 						reject(err); | ||||||
| 					} else { | 					} else { | ||||||
| @@ -62,13 +54,15 @@ module.exports = function () { | |||||||
| 		 * @returns {Promise} | 		 * @returns {Promise} | ||||||
| 		 */ | 		 */ | ||||||
| 		load: function (token) { | 		load: function (token) { | ||||||
|  | 			if (!config.getPublicKey()) { | ||||||
|  | 				logger.error('Public key is empty!') | ||||||
|  | 			} | ||||||
| 			return new Promise((resolve, reject) => { | 			return new Promise((resolve, reject) => { | ||||||
| 				checkJWTKeyPair(); |  | ||||||
| 				try { | 				try { | ||||||
| 					if (!token || token === null || token === 'null') { | 					if (!token || token === null || token === 'null') { | ||||||
| 						reject(new error.AuthError('Empty token')); | 						reject(new error.AuthError('Empty token')); | ||||||
| 					} else { | 					} else { | ||||||
| 						jwt.verify(token, public_key, {ignoreExpiration: false, algorithms: [ALGO]}, (err, result) => { | 						jwt.verify(token, config.getPublicKey(), {ignoreExpiration: false, algorithms: [ALGO]}, (err, result) => { | ||||||
| 							if (err) { | 							if (err) { | ||||||
|  |  | ||||||
| 								if (err.name === 'TokenExpiredError') { | 								if (err.name === 'TokenExpiredError') { | ||||||
| @@ -132,7 +126,7 @@ module.exports = function () { | |||||||
| 		 * @returns {Integer} | 		 * @returns {Integer} | ||||||
| 		 */ | 		 */ | ||||||
| 		getUserId: (default_value) => { | 		getUserId: (default_value) => { | ||||||
| 			let attrs = self.get('attrs'); | 			const attrs = self.get('attrs'); | ||||||
| 			if (attrs && typeof attrs.id !== 'undefined' && attrs.id) { | 			if (attrs && typeof attrs.id !== 'undefined' && attrs.id) { | ||||||
| 				return attrs.id; | 				return attrs.id; | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -10,7 +10,6 @@ | |||||||
| 		"bcrypt": "^5.0.0", | 		"bcrypt": "^5.0.0", | ||||||
| 		"body-parser": "^1.19.0", | 		"body-parser": "^1.19.0", | ||||||
| 		"compression": "^1.7.4", | 		"compression": "^1.7.4", | ||||||
| 		"config": "^3.3.1", |  | ||||||
| 		"express": "^4.17.3", | 		"express": "^4.17.3", | ||||||
| 		"express-fileupload": "^1.1.9", | 		"express-fileupload": "^1.1.9", | ||||||
| 		"gravatar": "^1.8.0", | 		"gravatar": "^1.8.0", | ||||||
|   | |||||||
| @@ -1,6 +1,4 @@ | |||||||
| const fs                  = require('fs'); | const config              = require('./lib/config'); | ||||||
| const NodeRSA             = require('node-rsa'); |  | ||||||
| const config              = require('config'); |  | ||||||
| const logger              = require('./logger').setup; | const logger              = require('./logger').setup; | ||||||
| const certificateModel    = require('./models/certificate'); | const certificateModel    = require('./models/certificate'); | ||||||
| const userModel           = require('./models/user'); | const userModel           = require('./models/user'); | ||||||
| @@ -9,62 +7,6 @@ const utils               = require('./lib/utils'); | |||||||
| const authModel           = require('./models/auth'); | const authModel           = require('./models/auth'); | ||||||
| const settingModel        = require('./models/setting'); | const settingModel        = require('./models/setting'); | ||||||
| const dns_plugins         = require('./global/certbot-dns-plugins'); | const dns_plugins         = require('./global/certbot-dns-plugins'); | ||||||
| const debug_mode          = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Creates a new JWT RSA Keypair if not alread set on the config |  | ||||||
|  * |  | ||||||
|  * @returns {Promise} |  | ||||||
|  */ |  | ||||||
| const setupJwt = () => { |  | ||||||
| 	return new Promise((resolve, reject) => { |  | ||||||
| 		// Now go and check if the jwt gpg keys have been created and if not, create them |  | ||||||
| 		if (!config.has('jwt') || !config.has('jwt.key') || !config.has('jwt.pub')) { |  | ||||||
| 			logger.info('Creating a new JWT key pair...'); |  | ||||||
|  |  | ||||||
| 			// jwt keys are not configured properly |  | ||||||
| 			const filename  = config.util.getEnv('NODE_CONFIG_DIR') + '/' + (config.util.getEnv('NODE_ENV') || 'default') + '.json'; |  | ||||||
| 			let config_data = {}; |  | ||||||
|  |  | ||||||
| 			try { |  | ||||||
| 				config_data = require(filename); |  | ||||||
| 			} catch (err) { |  | ||||||
| 				// do nothing |  | ||||||
| 				if (debug_mode) { |  | ||||||
| 					logger.debug(filename + ' config file could not be required'); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Now create the keys and save them in the config. |  | ||||||
| 			let key = new NodeRSA({ b: 2048 }); |  | ||||||
| 			key.generateKeyPair(); |  | ||||||
|  |  | ||||||
| 			config_data.jwt = { |  | ||||||
| 				key: key.exportKey('private').toString(), |  | ||||||
| 				pub: key.exportKey('public').toString(), |  | ||||||
| 			}; |  | ||||||
|  |  | ||||||
| 			// Write config |  | ||||||
| 			fs.writeFile(filename, JSON.stringify(config_data, null, 2), (err) => { |  | ||||||
| 				if (err) { |  | ||||||
| 					logger.error('Could not write JWT key pair to config file: ' + filename); |  | ||||||
| 					reject(err); |  | ||||||
| 				} else { |  | ||||||
| 					logger.info('Wrote JWT key pair to config file: ' + filename); |  | ||||||
| 					delete require.cache[require.resolve('config')]; |  | ||||||
| 					resolve(); |  | ||||||
| 				} |  | ||||||
| 			}); |  | ||||||
| 		} else { |  | ||||||
| 			// JWT key pair exists |  | ||||||
| 			if (debug_mode) { |  | ||||||
| 				logger.debug('JWT Keypair already exists'); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			resolve(); |  | ||||||
| 		} |  | ||||||
| 	}); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Creates a default admin users if one doesn't already exist in the database |  * Creates a default admin users if one doesn't already exist in the database | ||||||
| @@ -119,8 +61,8 @@ const setupDefaultUser = () => { | |||||||
| 					.then(() => { | 					.then(() => { | ||||||
| 						logger.info('Initial admin setup completed'); | 						logger.info('Initial admin setup completed'); | ||||||
| 					}); | 					}); | ||||||
| 			} else if (debug_mode) { | 			} else if (config.debug()) { | ||||||
| 				logger.debug('Admin user setup not required'); | 				logger.info('Admin user setup not required'); | ||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
| }; | }; | ||||||
| @@ -151,8 +93,8 @@ const setupDefaultSettings = () => { | |||||||
| 						logger.info('Default settings added'); | 						logger.info('Default settings added'); | ||||||
| 					}); | 					}); | ||||||
| 			} | 			} | ||||||
| 			if (debug_mode) { | 			if (config.debug()) { | ||||||
| 				logger.debug('Default setting setup not required'); | 				logger.info('Default setting setup not required'); | ||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
| }; | }; | ||||||
| @@ -225,8 +167,7 @@ const setupLogrotation = () => { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| module.exports = function () { | module.exports = function () { | ||||||
| 	return setupJwt() | 	return setupDefaultUser() | ||||||
| 		.then(setupDefaultUser) |  | ||||||
| 		.then(setupDefaultSettings) | 		.then(setupDefaultSettings) | ||||||
| 		.then(setupCertbotPlugins) | 		.then(setupCertbotPlugins) | ||||||
| 		.then(setupLogrotation); | 		.then(setupLogrotation); | ||||||
|   | |||||||
| @@ -677,13 +677,6 @@ concat-map@0.0.1: | |||||||
|   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" |   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" | ||||||
|   integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= |   integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= | ||||||
|  |  | ||||||
| config@^3.3.1: |  | ||||||
|   version "3.3.1" |  | ||||||
|   resolved "https://registry.yarnpkg.com/config/-/config-3.3.1.tgz#b6a70e2908a43b98ed20be7e367edf0cc8ed5a19" |  | ||||||
|   integrity sha512-+2/KaaaAzdwUBE3jgZON11L1ggLLhpf2FsGrfqYFHZW22ySGv/HqYIXrBwKKvn+XZh1UBUjHwAcrfsSkSygT+Q== |  | ||||||
|   dependencies: |  | ||||||
|     json5 "^2.1.1" |  | ||||||
|  |  | ||||||
| configstore@^5.0.1: | configstore@^5.0.1: | ||||||
|   version "5.0.1" |   version "5.0.1" | ||||||
|   resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" |   resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" | ||||||
| @@ -1769,11 +1762,6 @@ json-stable-stringify-without-jsonify@^1.0.1: | |||||||
|   resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" |   resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" | ||||||
|   integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= |   integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= | ||||||
|  |  | ||||||
| json5@^2.1.1: |  | ||||||
|   version "2.2.3" |  | ||||||
|   resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" |  | ||||||
|   integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== |  | ||||||
|  |  | ||||||
| jsonwebtoken@^9.0.0: | jsonwebtoken@^9.0.0: | ||||||
|   version "9.0.0" |   version "9.0.0" | ||||||
|   resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" |   resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" | ||||||
|   | |||||||
| @@ -16,14 +16,17 @@ services: | |||||||
|     environment: |     environment: | ||||||
|       PUID: 1000 |       PUID: 1000 | ||||||
|       PGID: 1000 |       PGID: 1000 | ||||||
|       NODE_ENV: "development" |  | ||||||
|       FORCE_COLOR: 1 |       FORCE_COLOR: 1 | ||||||
|       DEVELOPMENT: "true" |       # specifically for dev: | ||||||
|       DB_MYSQL_HOST: "db" |       DEBUG: 'true' | ||||||
|       DB_MYSQL_PORT: 3306 |       DEVELOPMENT: 'true' | ||||||
|       DB_MYSQL_USER: "npm" |       LE_STAGING: 'true' | ||||||
|       DB_MYSQL_PASSWORD: "npm" |       # db: | ||||||
|       DB_MYSQL_NAME: "npm" |       DB_MYSQL_HOST: 'db' | ||||||
|  |       DB_MYSQL_PORT: '3306' | ||||||
|  |       DB_MYSQL_USER: 'npm' | ||||||
|  |       DB_MYSQL_PASSWORD: 'npm' | ||||||
|  |       DB_MYSQL_NAME: 'npm' | ||||||
|       # DB_SQLITE_FILE: "/data/database.sqlite" |       # DB_SQLITE_FILE: "/data/database.sqlite" | ||||||
|       # DISABLE_IPV6: "true" |       # DISABLE_IPV6: "true" | ||||||
|     volumes: |     volumes: | ||||||
| @@ -44,10 +47,10 @@ services: | |||||||
|     networks: |     networks: | ||||||
|       - nginx_proxy_manager |       - nginx_proxy_manager | ||||||
|     environment: |     environment: | ||||||
|       MYSQL_ROOT_PASSWORD: "npm" |       MYSQL_ROOT_PASSWORD: 'npm' | ||||||
|       MYSQL_DATABASE: "npm" |       MYSQL_DATABASE: 'npm' | ||||||
|       MYSQL_USER: "npm" |       MYSQL_USER: 'npm' | ||||||
|       MYSQL_PASSWORD: "npm" |       MYSQL_PASSWORD: 'npm' | ||||||
|     volumes: |     volumes: | ||||||
|       - db_data:/var/lib/mysql |       - db_data:/var/lib/mysql | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user