From d3caa7316bc1bda8f9a43e0884bf5dee656ef149 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Wed, 10 Feb 2021 09:46:56 +0100 Subject: [PATCH 1/4] feat: https support --- app.js | 87 +++++++++++++++++++++++++++++++++++---- bin/www | 44 +------------------- build/webpack.dev.conf.js | 1 + package-lock.json | 50 +++++++++++++++++++--- package.json | 1 + 5 files changed, 128 insertions(+), 55 deletions(-) diff --git a/app.js b/app.js index be34c2dd580..a6acece331f 100644 --- a/app.js +++ b/app.js @@ -22,6 +22,7 @@ const path = require('path') const { storeDir } = reqlib('config/app.js') const renderIndex = reqlib('/lib/renderIndex') const archiver = require('archiver') +const { createCertificate } = require('pem').promisified const rateLimit = require('express-rate-limit') const storeLimiter = rateLimit({ @@ -45,7 +46,52 @@ let restarting = false // ### UTILS -function start (server) { +/** + * Start http/https server and all the manager + * + * @param {string} host + * @param {number} port + */ +async function startServer (host, port) { + let server + + if (process.env.HTTPS) { + const { cert, key } = await loadCertKey() + server = require('https').createServer({ + key, cert + }, app) + } else { + server = require('http').createServer(app) + } + + server.listen(port, host, function () { + const addr = server.address() + const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port + logger.info(`Listening on ${bind}`) + }) + + server.on('error', function (error) { + if (error.syscall !== 'listen') { + throw error + } + + const bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + logger.error(bind + ' requires elevated privileges') + process.exit(1) + case 'EADDRINUSE': + logger.error(bind + ' is already in use') + process.exit(1) + default: + throw error + } + }) + setupSocket(server) setupInterceptor() startGateway() @@ -71,6 +117,37 @@ function getSafePath (req) { return reqPath } +async function loadCertKey () { + const certFile = utils.joinPath(storeDir, 'cert.pem') + const keyFile = utils.joinPath(storeDir, 'key.pem') + + let key + let cert + + try { + cert = await fs.readFile(certFile) + key = await fs.readFile(keyFile) + } catch (error) {} + + if (!cert || !key) { + logger.info('Generating certificates...') + + const result = await createCertificate({ + days: 99999, + selfSigned: true + }) + + key = result.serviceKey + cert = result.certificate + + await fs.writeFile(keyFile, result.serviceKey) + await fs.writeFile(certFile, result.certificate) + logger.info('New cert and key created') + } + + return { cert, key } +} + function setupLogging (settings) { loggers.setupAll(settings ? settings.gateway : null) } @@ -151,12 +228,6 @@ app.use(cors()) * @param {HttpServer} server */ function setupSocket (server) { - server.on('listening', function () { - const addr = server.address() - const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port - logger.info(`Listening on ${bind}`) - }) - socketManager.bindServer(server) socketManager.on(inboundEvents.init, function (socket) { @@ -485,4 +556,4 @@ process.on('SIGINT', function () { }) }) -module.exports = { app, start } +module.exports = { app, startServer } diff --git a/bin/www b/bin/www index e885d74c008..814eb5f34bf 100644 --- a/bin/www +++ b/bin/www @@ -24,8 +24,7 @@ console.log(` // if jsonstore fails exit the application jsonStore.init(store) .then(() => { - const { app, start } = reqlib('app.js') - const http = require('http') + const { app, startServer } = reqlib('app.js') /** * Normalize a port into a number, string, or false. @@ -47,32 +46,6 @@ jsonStore.init(store) return false } - /** - * Event listener for HTTP server "error" event. - */ - - function onError (error) { - if (error.syscall !== 'listen') { - throw error - } - - const bind = typeof port === 'string' - ? 'Pipe ' + port - : 'Port ' + port - - // handle specific listen errors with friendly messages - switch (error.code) { - case 'EACCES': - console.error(bind + ' requires elevated privileges') - process.exit(1) - case 'EADDRINUSE': - console.error(bind + ' is already in use') - process.exit(1) - default: - throw error - } - } - /** * Get port from environment and store in Express. */ @@ -80,22 +53,9 @@ jsonStore.init(store) const port = normalizePort(process.env.PORT || conf.port) app.set('port', port) - /** - * Create HTTP server. - */ - - const server = http.createServer(app) - - /** - * Listen on provided port, on preferred network interfaces. - */ - const host = process.env.HOST || conf.host || '0.0.0.0' - server.listen(port, host) - server.on('error', onError) - - start(server) + return startServer(host, port) }) .catch(err => { console.error(err) diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js index 5c2c1a9fec0..9e02806f18e 100644 --- a/build/webpack.dev.conf.js +++ b/build/webpack.dev.conf.js @@ -29,6 +29,7 @@ const devWebpackConfig = merge(baseWebpackConfig, { clientLogLevel: 'warning', historyApiFallback: true, hot: true, + https: !!process.env.SERVER_SSL, contentBase: false, // since we use CopyWebpackPlugin. compress: true, disableHostCheck: true, diff --git a/package-lock.json b/package-lock.json index dc4ac60dc42..c81f134656f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8658,6 +8658,11 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -9378,6 +9383,11 @@ "which": "^2.0.1" } }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + }, "crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -10760,6 +10770,11 @@ "is-symbol": "^1.0.2" } }, + "es6-promisify": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.1.1.tgz", + "integrity": "sha512-HBL8I3mIki5C1Cc9QjKUenHtnG0A5/xA8Q/AllRcfiwl2CZFXGK7ddBiCoRwAix4i2KxcQfjtIVcrVbB3vbmwg==" + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -13509,8 +13524,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", @@ -14589,6 +14603,23 @@ "integrity": "sha512-wRJtOo1v1ch+gN8PRsj0IGJznk+kQ8mz13ds/nuhLI+Qyf/931ZlRpd92oq0IRPpZIb+bhX8pRjzIVdcPDKmiQ==", "dev": true }, + "md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "requires": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + } + } + }, "mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", @@ -16000,8 +16031,7 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "p-cancelable": { "version": "2.0.0", @@ -16393,6 +16423,17 @@ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, + "pem": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/pem/-/pem-1.14.4.tgz", + "integrity": "sha512-v8lH3NpirgiEmbOqhx0vwQTxwi0ExsiWBGYh0jYNq7K6mQuO4gI6UEFlr6fLAdv9TPXRt6GqiwE37puQdIDS8g==", + "requires": { + "es6-promisify": "^6.0.0", + "md5": "^2.2.1", + "os-tmpdir": "^1.0.1", + "which": "^2.0.2" + } + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -21949,7 +21990,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } diff --git a/package.json b/package.json index fbf1eeb4e7e..44fb6f92dc9 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,7 @@ "mqtt-nedb-store": "^0.1.1", "native-url": "^0.3.4", "nedb": "^1.8.0", + "pem": "^1.14.4", "prismjs": "^1.23.0", "serialport": "^9.0.6", "serve-favicon": "^2.5.0", From 22f90f79ef1ff4997bc9a797543416910e901549 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Wed, 10 Feb 2021 09:51:01 +0100 Subject: [PATCH 2/4] docs: document https env var --- docs/guide/env-vars.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/guide/env-vars.md b/docs/guide/env-vars.md index fecdd60a060..9f219669cb2 100644 --- a/docs/guide/env-vars.md +++ b/docs/guide/env-vars.md @@ -3,6 +3,7 @@ This is the list of the actually supported env vars: - `NETWORK_KEY`: Zwave Network key +- `HTTPS`: Enable https - `PORT`: The port to listen to for incoming requests. Default is `8091` - `HOST`: The host address to bind to. Default is `0.0.0.0` - `STORE_DIR`: The absolute path to the directory where all files will be stored. Default is `/store` From 1975485053d8df90279a5248ce77d31591d8476a Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Wed, 10 Feb 2021 11:10:16 +0100 Subject: [PATCH 3/4] fix: dev server over https --- build/webpack.dev.conf.js | 2 +- config/index.js | 8 ++++++-- docs/guide/env-vars.md | 7 +++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js index 9e02806f18e..4639ba695cf 100644 --- a/build/webpack.dev.conf.js +++ b/build/webpack.dev.conf.js @@ -29,7 +29,7 @@ const devWebpackConfig = merge(baseWebpackConfig, { clientLogLevel: 'warning', historyApiFallback: true, hot: true, - https: !!process.env.SERVER_SSL, + https: config.dev.https, contentBase: false, // since we use CopyWebpackPlugin. compress: true, disableHostCheck: true, diff --git a/config/index.js b/config/index.js index 75544f88d12..35684e29211 100644 --- a/config/index.js +++ b/config/index.js @@ -19,6 +19,7 @@ const proxyWSURL = process.env.SERVER_WS_URL ? process.env.SERVER_WS_URL : `${proxyWebSocketScheme}://${proxyHostname}:${proxyPort}` +// this props are used on build/ files as general settings for webpack module.exports = { dev: { // Paths @@ -26,12 +27,15 @@ module.exports = { assetsPublicPath: '/', proxyTable: { '/socket.io': { - target: proxyWSURL, - ws: true + target: proxyURL, + ws: true, + secure: false, + changeOrigin: true }, '/health': proxyURL, '/api': proxyURL }, + https: !!process.env.SERVER_SSL, // Various Dev Server settings host: 'localhost', // can be overwritten by process.env.HOST diff --git a/docs/guide/env-vars.md b/docs/guide/env-vars.md index 9f219669cb2..6c92e024c1f 100644 --- a/docs/guide/env-vars.md +++ b/docs/guide/env-vars.md @@ -7,3 +7,10 @@ This is the list of the actually supported env vars: - `PORT`: The port to listen to for incoming requests. Default is `8091` - `HOST`: The host address to bind to. Default is `0.0.0.0` - `STORE_DIR`: The absolute path to the directory where all files will be stored. Default is `/store` + +This env vars can be used when running webpack dev server with HMR (most users will not need them): + +- `SERVER_PORT`: The port the server is running. Default is `8091` +- `SERVER_SSL`: Set to 'true' if server is using HTTPS / WSS scheme +- `SERVER_HOST`: The host address the server is binded to. Default is `0.0.0.0` +- `SERVER_URL`: Complete URL to server. If not set a combination of `SERVER_SSL`, `SERVER_HOST` and `SERVER_PORT` will be used. From 48f53f23e55053d1dce93f9e871ccd80cdd6c6da Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Wed, 10 Feb 2021 11:39:28 +0100 Subject: [PATCH 4/4] fix: lint issues --- app.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app.js b/app.js index a6acece331f..2ac6f61177d 100644 --- a/app.js +++ b/app.js @@ -57,9 +57,13 @@ async function startServer (host, port) { if (process.env.HTTPS) { const { cert, key } = await loadCertKey() - server = require('https').createServer({ - key, cert - }, app) + server = require('https').createServer( + { + key, + cert + }, + app + ) } else { server = require('http').createServer(app) } @@ -75,9 +79,7 @@ async function startServer (host, port) { throw error } - const bind = typeof port === 'string' - ? 'Pipe ' + port - : 'Port ' + port + const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port // handle specific listen errors with friendly messages switch (error.code) {