From bb41ab482c7abdff838a397e926d5fe2e5b00e3c Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 29 May 2020 16:50:53 +0100 Subject: [PATCH] Rework the https refresh logic - puts the node version check first - validates the refresh interval and keeps it in valid range - simplifies the error messages - uses parseFloat not parseInt so we can use fractions of hour --- .../runtime/locales/en-US/runtime.json | 14 ++--- packages/node_modules/node-red/red.js | 58 +++++++++---------- packages/node_modules/node-red/settings.js | 17 ++++-- 3 files changed, 46 insertions(+), 43 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json index 140593451..fea4d5591 100644 --- a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json @@ -49,14 +49,12 @@ "headless-mode": "Running in headless mode", "httpadminauth-deprecated": "use of httpAdminAuth is deprecated. Use adminAuth instead", "https": { - "missing-fields": "Cannot refresh the https settings when the https property function doesn't return a 'key' and 'cert'", - "settings-refreshed": "The https settings have been refreshed", - "apply-failed": "Failed to apply the refreshed https settings: __message__", - "get-failed": "Failed to get the refreshed https settings: __message__", - "refresh-interval": "Refreshing https settings every __interval__ seconds", - "nodejs-version": "Cannot refresh the https settings automatically, because NodeJs version 11 or above is required", - "function-required": "Cannot refresh the https settings automatically (at httpsRefreshInterval), because the https property needs to be a function" - } + "refresh-interval": "Refreshing https settings every __interval__ hours", + "settings-refreshed": "Server https settings have been refreshed", + "refresh-failed": "Failed to refresh https settings: __message__", + "nodejs-version": "httpsRefreshInterval requires Node.js 11 or later", + "function-required": "httpsRefreshInterval requires https property to be a function" + } }, "api": { diff --git a/packages/node_modules/node-red/red.js b/packages/node_modules/node-red/red.js index ed370214f..6dfef03ff 100755 --- a/packages/node_modules/node-red/red.js +++ b/packages/node_modules/node-red/red.js @@ -148,49 +148,49 @@ var delayedLogItems = []; var startupHttps = settings.https; if (typeof startupHttps === "function") { // Get the result of the function, because createServer doesn't accept functions as input - startupHttps = startupHttps(); -} + startupHttps = startupHttps(); +} var httpsPromise = Promise.resolve(startupHttps); - + httpsPromise.then(function(startupHttps) { if (startupHttps) { server = https.createServer(startupHttps,function(req,res) {app(req,res);}); - - // Refresh https settings at intervals for NodeJs version 11 and above + if (settings.httpsRefreshInterval) { - if (typeof settings.https === "function") { - if (server.setSecureContext) { - delayedLogItems.push({type:"info", id:"server.https.refresh-interval", params:{interval:parseInt(settings.httpsRefreshInterval)}}); + var httpsRefreshInterval = parseFloat(settings.httpsRefreshInterval)||12; + if (httpsRefreshInterval > 596) { + // Max value based on (2^31-1)ms - the max that setInterval can accept + httpsRefreshInterval = 596; + } + // Check whether setSecureContext is available (Node.js 11+) + if (server.setSecureContext) { + // Check whether `http` is a callable function + if (typeof settings.https === "function") { + delayedLogItems.push({type:"info", id:"server.https.refresh-interval", params:{interval:httpsRefreshInterval}}); setInterval(function () { try { // Get the result of the function, because createServer doesn't accept functions as input - var httpsPromise = Promise.resolve(settings.https()); - - httpsPromise.then(function(refreshedHttps) { - // Use the refreshed https settings - if (!refreshedHttps.key || !refreshedHttps.cert) { - RED.log.warn(RED.log._("server.https.missing-fields")); - return; - } - - // Only update the credentials in the server when key or cert has changed - if(!server.key || !server.cert || !server.key.equals(refreshedHttps.key) || !server.cert.equals(refreshedHttps.cert)) { - server.setSecureContext(refreshedHttps); - RED.log.info(RED.log._("server.https.settings-refreshed")); + Promise.resolve(settings.https()).then(function(refreshedHttps) { + if (refreshedHttps) { + // Only update the credentials in the server when key or cert has changed + if(!server.key || !server.cert || !server.key.equals(refreshedHttps.key) || !server.cert.equals(refreshedHttps.cert)) { + server.setSecureContext(refreshedHttps); + RED.log.info(RED.log._("server.https.settings-refreshed")); + } } }).catch(function(err) { - RED.log.error(RED.log._("server.https.apply-failed",{message:err})); + RED.log.error(RED.log._("server.https.refresh-failed",{message:err})); }); } catch(err) { - RED.log.error(RED.log._("server.https.get-failed",{message:err})); + RED.log.error(RED.log._("server.https.refresh-failed",{message:err})); } - }, parseInt(settings.httpsRefreshInterval)*60*60*1000); + }, httpsRefreshInterval*60*60*1000); } else { - delayedLogItems.push({type:"warn", id:"server.https.nodejs-version", params:{}}); + delayedLogItems.push({type:"warn", id:"server.https.function-required"}); } } else { - delayedLogItems.push({type:"warn", id:"server.https.function-required", params:{}}); - } + delayedLogItems.push({type:"warn", id:"server.https.nodejs-version"}); + } } } else { server = http.createServer(function(req,res) {app(req,res);}); @@ -360,10 +360,10 @@ httpsPromise.then(function(startupHttps) { } process.exit(1); }); - + // Log all the delayed messages, since they can be translated at this point delayedLogItems.forEach(function (delayedLogItem, index) { - RED.log[delayedLogItem.type](RED.log._(delayedLogItem.id, delayedLogItem.params)); + RED.log[delayedLogItem.type](RED.log._(delayedLogItem.id, delayedLogItem.params||{})); }); server.listen(settings.uiPort,settings.uiHost,function() { diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index e3d9f5e19..718792579 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -140,19 +140,21 @@ module.exports = { // See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener // for details on its contents. // See the comment at the top of this file on how to load the `fs` module used by this setting. - // This property can be an object, containing both a (private) key and a (public) certificate: + // This property can be either an object, containing both a (private) key and a (public) certificate, + // or a function that returns such an object: + //// https object: //https: { // key: fs.readFileSync('privkey.pem'), // cert: fs.readFileSync('cert.pem') //}, - // This property can also be a function (e.g. to automatic refresh the https settings synchronously): + ////https synchronous function: //https: function() { // return { // key: fs.readFileSync('privkey.pem'), // cert: fs.readFileSync('cert.pem') // } //}, - // This property can also be a promise (e.g. to automatic refresh the https settings asynchronously): + //// https asynchronous function: //https: function() { // return Promise.resolve({ // key: fs.readFileSync('privkey.pem'), @@ -160,9 +162,12 @@ module.exports = { // }); //}, - // The following property can be used to refresh the https settings at regular time intervals (hours). - // Prerequisite: the 'https' property should be enabled (based on a function)! - // Caution: NodeJs version 11 or above is required to use this option! + // The following property can be used to refresh the https settings at a + // regular time interval in hours. + // This requires: + // - the `https` setting to be a function that can be called to get + // the refreshed settings. + // - Node.js 11 or later. //httpsRefreshInterval : 12, // The following property can be used to cause insecure HTTP connections to