diff --git a/packages/node_modules/node-red/red.js b/packages/node_modules/node-red/red.js index 9543176d5..ccbd6dc38 100755 --- a/packages/node_modules/node-red/red.js +++ b/packages/node_modules/node-red/red.js @@ -142,247 +142,278 @@ if (process.env.NODE_RED_ENABLE_PROJECTS) { settings.editorTheme.projects.enabled = !/^false$/i.test(process.env.NODE_RED_ENABLE_PROJECTS); } +var httpsPromise; if (settings.https) { var startupHttps = settings.https; - + if (typeof startupHttps === "function") { // Get the result of the function, because createServer doesn't accept functions as input startupHttps = startupHttps(); } - server = https.createServer(startupHttps,function(req,res) {app(req,res);}); + if (startupHttps.hasOwnProperty('then') && typeof startupHttps.then === 'function') { + // A promise was returned + httpsPromise = startupHttps; + } else { + // An object was returned - wrap in a promise + httpsPromise = Promise.resolve(startupHttps); + } +} +else { + // No https is enable - wrap null + httpsPromise = Promise.resolve(null); +} - // Refresh https settings at intervals for NodeJs version 11 and above - if (settings.httpsRefreshInterval) { - if (typeof startupHttps === "function") { - if (server.setSecureContext) { - console.log("Refreshing https settings every " + parseInt(settings.httpsRefreshInterval) + " seconds."); - setInterval(function () { - try { - // Get the result of the function, because createServer doesn't accept functions as input - var refreshedHttps = settings.https(); - - if (!refreshedHttps.key || !refreshedHttps.cert) { - console.log("Cannot refresh the https settings when the https property function doesn't return a 'key' and 'cert'."); - return; - } +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) { + console.log("Refreshing https settings every " + parseInt(settings.httpsRefreshInterval) + " seconds."); + setInterval(function () { + try { + // Get the result of the function, because createServer doesn't accept functions as input + var refreshedHttps = settings.https(); - // 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); - console.log("The https settings have been refreshed."); + var httpsPromise; + if (refreshedHttps.hasOwnProperty('then') && typeof refreshedHttps.then === 'function') { + // A promise was returned + httpsPromise = refreshedHttps; + } else { + // An object was returned - wrap in a promise + httpsPromise = Promise.resolve(refreshedHttps); + } + + httpsPromise.then(function(refreshedHttps) { + // Use the refreshed https settings + if (!refreshedHttps.key || !refreshedHttps.cert) { + console.log("Cannot refresh the https settings when the https property function doesn't return a 'key' and 'cert'."); + 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); + console.log("The https settings have been refreshed."); + } + }).catch(function(err) { + console.log("Failed to apply the refreshed https settings: " + err); + }); + } catch(err) { + console.log("Failed to get the refreshed https settings: " + err); } - } catch(err) { - console.log("Failed to refresh the https settings: " + err); - } - }, parseInt(settings.httpsRefreshInterval)*1000); - } else { - console.log("Cannot refresh the https settings automatically, because NodeJs version 11 or above is required."); - } - } else { - console.log("Cannot refresh the https settings automatically (at httpsRefreshInterval), because the https property needs to be a function."); - } - } -} else { - server = http.createServer(function(req,res) {app(req,res);}); -} -server.setMaxListeners(0); - -function formatRoot(root) { - if (root[0] != "/") { - root = "/" + root; - } - if (root.slice(-1) != "/") { - root = root + "/"; - } - return root; -} - -if (settings.httpRoot === false) { - settings.httpAdminRoot = false; - settings.httpNodeRoot = false; -} else { - settings.httpRoot = settings.httpRoot||"/"; - settings.disableEditor = settings.disableEditor||false; -} - -if (settings.httpAdminRoot !== false) { - settings.httpAdminRoot = formatRoot(settings.httpAdminRoot || settings.httpRoot || "/"); - settings.httpAdminAuth = settings.httpAdminAuth || settings.httpAuth; -} else { - settings.disableEditor = true; -} - -if (settings.httpNodeRoot !== false) { - settings.httpNodeRoot = formatRoot(settings.httpNodeRoot || settings.httpRoot || "/"); - settings.httpNodeAuth = settings.httpNodeAuth || settings.httpAuth; -} - -// if we got a port from command line, use it (even if 0) -// replicate (settings.uiPort = parsedArgs.port||settings.uiPort||1880;) but allow zero -if (parsedArgs.port !== undefined){ - settings.uiPort = parsedArgs.port; -} else { - if (settings.uiPort === undefined){ - settings.uiPort = 1880; - } -} - -settings.uiHost = settings.uiHost||"0.0.0.0"; - -if (flowFile) { - settings.flowFile = flowFile; -} -if (parsedArgs.userDir) { - settings.userDir = parsedArgs.userDir; -} - -try { - RED.init(server,settings); -} catch(err) { - if (err.code == "unsupported_version") { - console.log("Unsupported version of Node.js:",process.version); - console.log("Node-RED requires Node.js v8.9.0 or later"); - } else { - console.log("Failed to start server:"); - if (err.stack) { - console.log(err.stack); - } else { - console.log(err); - } - } - process.exit(1); -} - -function basicAuthMiddleware(user,pass) { - var basicAuth = require('basic-auth'); - var checkPassword; - var localCachedPassword; - if (pass.length == "32") { - // Assume its a legacy md5 password - checkPassword = function(p) { - return crypto.createHash('md5').update(p,'utf8').digest('hex') === pass; - } - } else { - checkPassword = function(p) { - return bcrypt.compareSync(p,pass); - } - } - - var checkPasswordAndCache = function(p) { - // For BasicAuth routes we know the password cannot change without - // a restart of Node-RED. This means we can cache the provided crypted - // version to save recalculating each time. - if (localCachedPassword === p) { - return true; - } - var result = checkPassword(p); - if (result) { - localCachedPassword = p; - } - return result; - } - - return function(req,res,next) { - if (req.method === 'OPTIONS') { - return next(); - } - var requestUser = basicAuth(req); - if (!requestUser || requestUser.name !== user || !checkPasswordAndCache(requestUser.pass)) { - res.set('WWW-Authenticate', 'Basic realm="Authorization Required"'); - return res.sendStatus(401); - } - next(); - } -} - -if (settings.httpAdminRoot !== false && settings.httpAdminAuth) { - RED.log.warn(RED.log._("server.httpadminauth-deprecated")); - app.use(settings.httpAdminRoot, basicAuthMiddleware(settings.httpAdminAuth.user,settings.httpAdminAuth.pass)); -} - -if (settings.httpAdminRoot !== false) { - app.use(settings.httpAdminRoot,RED.httpAdmin); -} -if (settings.httpNodeRoot !== false && settings.httpNodeAuth) { - app.use(settings.httpNodeRoot,basicAuthMiddleware(settings.httpNodeAuth.user,settings.httpNodeAuth.pass)); -} -if (settings.httpNodeRoot !== false) { - app.use(settings.httpNodeRoot,RED.httpNode); -} -if (settings.httpStatic) { - settings.httpStaticAuth = settings.httpStaticAuth || settings.httpAuth; - if (settings.httpStaticAuth) { - app.use("/",basicAuthMiddleware(settings.httpStaticAuth.user,settings.httpStaticAuth.pass)); - } - app.use("/",express.static(settings.httpStatic)); -} - -function getListenPath() { - var port = settings.serverPort; - if (port === undefined){ - port = settings.uiPort; - } - - var listenPath = 'http'+(settings.https?'s':'')+'://'+ - (settings.uiHost == '::'?'localhost':(settings.uiHost == '0.0.0.0'?'127.0.0.1':settings.uiHost))+ - ':'+port; - if (settings.httpAdminRoot !== false) { - listenPath += settings.httpAdminRoot; - } else if (settings.httpStatic) { - listenPath += "/"; - } - return listenPath; -} - -RED.start().then(function() { - if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) { - server.on('error', function(err) { - if (err.errno === "EADDRINUSE") { - RED.log.error(RED.log._("server.unable-to-listen", {listenpath:getListenPath()})); - RED.log.error(RED.log._("server.port-in-use")); - } else { - RED.log.error(RED.log._("server.uncaught-exception")); - if (err.stack) { - RED.log.error(err.stack); + }, parseInt(settings.httpsRefreshInterval)*1000); } else { - RED.log.error(err); + console.log("Cannot refresh the https settings automatically, because NodeJs version 11 or above is required."); } - } - process.exit(1); - }); - server.listen(settings.uiPort,settings.uiHost,function() { - if (settings.httpAdminRoot === false) { - RED.log.info(RED.log._("server.admin-ui-disabled")); - } - settings.serverPort = server.address().port; - process.title = parsedArgs.title || 'node-red'; - RED.log.info(RED.log._("server.now-running", {listenpath:getListenPath()})); - }); + } else { + console.log("Cannot refresh the https settings automatically (at httpsRefreshInterval), because the https property needs to be a function."); + } + } } else { - RED.log.info(RED.log._("server.headless-mode")); + server = http.createServer(function(req,res) {app(req,res);}); } -}).otherwise(function(err) { - RED.log.error(RED.log._("server.failed-to-start")); - if (err.stack) { - RED.log.error(err.stack); - } else { - RED.log.error(err); - } -}); + server.setMaxListeners(0); -process.on('uncaughtException',function(err) { - util.log('[red] Uncaught Exception:'); - if (err.stack) { - util.log(err.stack); - } else { - util.log(err); + function formatRoot(root) { + if (root[0] != "/") { + root = "/" + root; + } + if (root.slice(-1) != "/") { + root = root + "/"; + } + return root; } - process.exit(1); -}); -process.on('SIGINT', function () { - RED.stop().then(function() { - process.exit(); + if (settings.httpRoot === false) { + settings.httpAdminRoot = false; + settings.httpNodeRoot = false; + } else { + settings.httpRoot = settings.httpRoot||"/"; + settings.disableEditor = settings.disableEditor||false; + } + + if (settings.httpAdminRoot !== false) { + settings.httpAdminRoot = formatRoot(settings.httpAdminRoot || settings.httpRoot || "/"); + settings.httpAdminAuth = settings.httpAdminAuth || settings.httpAuth; + } else { + settings.disableEditor = true; + } + + if (settings.httpNodeRoot !== false) { + settings.httpNodeRoot = formatRoot(settings.httpNodeRoot || settings.httpRoot || "/"); + settings.httpNodeAuth = settings.httpNodeAuth || settings.httpAuth; + } + + // if we got a port from command line, use it (even if 0) + // replicate (settings.uiPort = parsedArgs.port||settings.uiPort||1880;) but allow zero + if (parsedArgs.port !== undefined){ + settings.uiPort = parsedArgs.port; + } else { + if (settings.uiPort === undefined){ + settings.uiPort = 1880; + } + } + + settings.uiHost = settings.uiHost||"0.0.0.0"; + + if (flowFile) { + settings.flowFile = flowFile; + } + if (parsedArgs.userDir) { + settings.userDir = parsedArgs.userDir; + } + + try { + RED.init(server,settings); + } catch(err) { + if (err.code == "unsupported_version") { + console.log("Unsupported version of Node.js:",process.version); + console.log("Node-RED requires Node.js v8.9.0 or later"); + } else { + console.log("Failed to start server:"); + if (err.stack) { + console.log(err.stack); + } else { + console.log(err); + } + } + process.exit(1); + } + + function basicAuthMiddleware(user,pass) { + var basicAuth = require('basic-auth'); + var checkPassword; + var localCachedPassword; + if (pass.length == "32") { + // Assume its a legacy md5 password + checkPassword = function(p) { + return crypto.createHash('md5').update(p,'utf8').digest('hex') === pass; + } + } else { + checkPassword = function(p) { + return bcrypt.compareSync(p,pass); + } + } + + var checkPasswordAndCache = function(p) { + // For BasicAuth routes we know the password cannot change without + // a restart of Node-RED. This means we can cache the provided crypted + // version to save recalculating each time. + if (localCachedPassword === p) { + return true; + } + var result = checkPassword(p); + if (result) { + localCachedPassword = p; + } + return result; + } + + return function(req,res,next) { + if (req.method === 'OPTIONS') { + return next(); + } + var requestUser = basicAuth(req); + if (!requestUser || requestUser.name !== user || !checkPasswordAndCache(requestUser.pass)) { + res.set('WWW-Authenticate', 'Basic realm="Authorization Required"'); + return res.sendStatus(401); + } + next(); + } + } + + if (settings.httpAdminRoot !== false && settings.httpAdminAuth) { + RED.log.warn(RED.log._("server.httpadminauth-deprecated")); + app.use(settings.httpAdminRoot, basicAuthMiddleware(settings.httpAdminAuth.user,settings.httpAdminAuth.pass)); + } + + if (settings.httpAdminRoot !== false) { + app.use(settings.httpAdminRoot,RED.httpAdmin); + } + if (settings.httpNodeRoot !== false && settings.httpNodeAuth) { + app.use(settings.httpNodeRoot,basicAuthMiddleware(settings.httpNodeAuth.user,settings.httpNodeAuth.pass)); + } + if (settings.httpNodeRoot !== false) { + app.use(settings.httpNodeRoot,RED.httpNode); + } + if (settings.httpStatic) { + settings.httpStaticAuth = settings.httpStaticAuth || settings.httpAuth; + if (settings.httpStaticAuth) { + app.use("/",basicAuthMiddleware(settings.httpStaticAuth.user,settings.httpStaticAuth.pass)); + } + app.use("/",express.static(settings.httpStatic)); + } + + function getListenPath() { + var port = settings.serverPort; + if (port === undefined){ + port = settings.uiPort; + } + + var listenPath = 'http'+(settings.https?'s':'')+'://'+ + (settings.uiHost == '::'?'localhost':(settings.uiHost == '0.0.0.0'?'127.0.0.1':settings.uiHost))+ + ':'+port; + if (settings.httpAdminRoot !== false) { + listenPath += settings.httpAdminRoot; + } else if (settings.httpStatic) { + listenPath += "/"; + } + return listenPath; + } + + RED.start().then(function() { + if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) { + server.on('error', function(err) { + if (err.errno === "EADDRINUSE") { + RED.log.error(RED.log._("server.unable-to-listen", {listenpath:getListenPath()})); + RED.log.error(RED.log._("server.port-in-use")); + } else { + RED.log.error(RED.log._("server.uncaught-exception")); + if (err.stack) { + RED.log.error(err.stack); + } else { + RED.log.error(err); + } + } + process.exit(1); + }); + server.listen(settings.uiPort,settings.uiHost,function() { + if (settings.httpAdminRoot === false) { + RED.log.info(RED.log._("server.admin-ui-disabled")); + } + settings.serverPort = server.address().port; + process.title = parsedArgs.title || 'node-red'; + RED.log.info(RED.log._("server.now-running", {listenpath:getListenPath()})); + }); + } else { + RED.log.info(RED.log._("server.headless-mode")); + } + }).otherwise(function(err) { + RED.log.error(RED.log._("server.failed-to-start")); + if (err.stack) { + RED.log.error(err.stack); + } else { + RED.log.error(err); + } + }); + + process.on('uncaughtException',function(err) { + util.log('[red] Uncaught Exception:'); + if (err.stack) { + util.log(err.stack); + } else { + util.log(err); + } + process.exit(1); + }); + + process.on('SIGINT', function () { + RED.stop().then(function() { + process.exit(); + }); }); });