├── Node-RED ├── .config.runtime.json ├── flows_cred.json ├── .config.runtime.json.backup ├── package.json ├── .config.users.json ├── flows.json ├── .flows.json.backup ├── .config.nodes.json └── .config.nodes.json.backup ├── logs └── log_files_will_be_stored_here.txt ├── protectedFiles.json ├── sharedResources.json ├── startServerWithoutDomains.bat ├── .gitattributes ├── initialFlows.png ├── startServerWithoutDomains ├── public └── local-server.org │ ├── img │ └── Mandelbrot_240x240.png │ └── index.html ├── startServerWithDomains.bat ├── startServerWithDomains ├── registeredUsers.json ├── ContentSecurityPolicies.json ├── certificates ├── localhost.cnf ├── local-server.org.cnf ├── localhost │ ├── fullchain.pem │ └── privkey.pem └── local-server.org │ ├── fullchain.pem │ └── privkey.pem ├── package.json ├── LICENSE.md ├── .gitignore ├── generateSaltAndHash ├── WebServer.js └── README.md /Node-RED/.config.runtime.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /logs/log_files_will_be_stored_here.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /protectedFiles.json: -------------------------------------------------------------------------------- 1 | { 2 | "\\/\\..*":"*" 3 | } -------------------------------------------------------------------------------- /Node-RED/flows_cred.json: -------------------------------------------------------------------------------- 1 | {"$":"796270a31ce2ca03381b82a39f93dddblpM="} -------------------------------------------------------------------------------- /sharedResources.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "PathPattern":"^.*$", "OriginList":null } 3 | ] -------------------------------------------------------------------------------- /startServerWithoutDomains.bat: -------------------------------------------------------------------------------- 1 | node WebServer.js public/local-server.org 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /initialFlows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-within-express/HEAD/initialFlows.png -------------------------------------------------------------------------------- /startServerWithoutDomains: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | node WebServer.js public/local-server.org 3 | -------------------------------------------------------------------------------- /Node-RED/.config.runtime.json.backup: -------------------------------------------------------------------------------- 1 | { 2 | "_credentialSecret": "44be227e31320f44007447a221df32e51013d043d7dd97f8d2b78f9374dabb14" 3 | } -------------------------------------------------------------------------------- /Node-RED/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-project", 3 | "description": "A Node-RED Project", 4 | "version": "0.0.1", 5 | "private": true 6 | } -------------------------------------------------------------------------------- /public/local-server.org/img/Mandelbrot_240x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rozek/node-red-within-express/HEAD/public/local-server.org/img/Mandelbrot_240x240.png -------------------------------------------------------------------------------- /startServerWithDomains.bat: -------------------------------------------------------------------------------- 1 | node WebServer.js --server-port=8443 --redirection-port=8000 \ 2 | --domain=local-server.org --allow-subdomains --pbkdf2-iterations=100000 \ 3 | public 4 | -------------------------------------------------------------------------------- /startServerWithDomains: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | node WebServer.js --server-port=8443 --redirection-port=8000 \ 3 | --domain=local-server.org --allow-subdomains --pbkdf2-iterations=100000 \ 4 | public 5 | -------------------------------------------------------------------------------- /registeredUsers.json: -------------------------------------------------------------------------------- 1 | { 2 | "node-red":{ 3 | "Roles":["node-red"], 4 | "Salt":"4486e8d35b8275020b1301226cc77963", 5 | "Hash":"ab2b740ea9148aa4f320af3f3ba60ee2e33bb8039c57eea2b29579ff3f3b16bec2401f19e3c6ed8ad36de432b80b6f973a12c41af5d50738e4bb902d0117df53" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /public/local-server.org/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Web Page served from local Web Server

8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Node-RED/.config.users.json: -------------------------------------------------------------------------------- 1 | { 2 | "_": { 3 | "editor": { 4 | "view": { 5 | "view-show-grid": true, 6 | "view-snap-grid": true, 7 | "view-grid-size": 20, 8 | "view-node-status": true, 9 | "view-node-show-label": true, 10 | "view-show-tips": true 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ContentSecurityPolicies.json: -------------------------------------------------------------------------------- 1 | { 2 | "script-src": ["'self'","'unsafe-inline'","'unsafe-eval'","data:","filesystem:","blob:"], 3 | "script-src-attr": ["'self'","'unsafe-inline'","'unsafe-eval'"], 4 | "style-src": ["'self'","'unsafe-inline'"], 5 | "img-src": ["'self'","data:","http:","https:"], 6 | "media-src": ["'self'"], 7 | "connect-src": ["'self'","https://catalogue.nodered.org"], 8 | "default-src": ["'self'"], 9 | "frame-ancestors": ["'self'"] 10 | } 11 | -------------------------------------------------------------------------------- /certificates/localhost.cnf: -------------------------------------------------------------------------------- 1 | [req] 2 | distinguished_name = subject 3 | req_extensions = req_ext 4 | prompt = no 5 | encrypt_key = no 6 | 7 | [ subject ] 8 | C = DE 9 | ST = state 10 | L = city 11 | O = organization 12 | OU = organizational-unit 13 | CN = localhost 14 | 15 | [ req_ext ] 16 | basicConstraints = CA:FALSE 17 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 18 | subjectAltName = @alt_names 19 | 20 | [alt_names] 21 | DNS.1 = 127.0.0.1 22 | DNS.2 = ::1 23 | -------------------------------------------------------------------------------- /certificates/local-server.org.cnf: -------------------------------------------------------------------------------- 1 | [req] 2 | distinguished_name = subject 3 | req_extensions = req_ext 4 | prompt = no 5 | encrypt_key = no 6 | 7 | [ subject ] 8 | C = DE 9 | ST = state 10 | L = city 11 | O = organization 12 | OU = organizational-unit 13 | CN = local-server.org 14 | 15 | [ req_ext ] 16 | basicConstraints = CA:FALSE 17 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 18 | subjectAltName = @alt_names 19 | 20 | [alt_names] 21 | DNS.1 = *.local-server.org 22 | DNS.2 = localhost 23 | DNS.3 = 127.0.0.1 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-within-express", 3 | "version": "0.1.0", 4 | "description": "embedding Node-RED within a Server built with Node.js and Express", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/rozek/node-red-within-restana.git" 11 | }, 12 | "keywords": [ 13 | "web-server", 14 | "node-red", 15 | "express-js" 16 | ], 17 | "author": "Andreas Rozek (https://www.rozek.de/)", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/rozek/node-red-within-express/issues" 21 | }, 22 | "homepage": "https://github.com/rozek/node-red-within-express#readme", 23 | "dependencies": { 24 | "commander": "^8.1.0", 25 | "express": "^4.17.1", 26 | "helmet": "^4.6.0", 27 | "morgan": "^1.10.0", 28 | "node-red": "^2.0.5", 29 | "serve-static": "^1.14.1" 30 | }, 31 | "devDependencies": { 32 | "@types/node": "^16.7.6" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-present Andreas Rozek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # nyc test coverage 20 | .nyc_output 21 | 22 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # Bower dependency directory (https://bower.io/) 26 | bower_components 27 | 28 | # node-waf configuration 29 | .lock-wscript 30 | 31 | # Compiled binary addons (https://nodejs.org/api/addons.html) 32 | build/Release 33 | 34 | # Dependency directories 35 | node_modules/ 36 | jspm_packages/ 37 | 38 | # TypeScript v1 declaration files 39 | typings/ 40 | 41 | # Optional npm cache directory 42 | .npm 43 | 44 | # Optional eslint cache 45 | .eslintcache 46 | 47 | # Optional REPL history 48 | .node_repl_history 49 | 50 | # Output of 'npm pack' 51 | *.tgz 52 | 53 | # Yarn Integrity file 54 | .yarn-integrity 55 | 56 | # dotenv environment variables file 57 | .env 58 | 59 | # parcel-bundler cache (https://parceljs.org/) 60 | .cache 61 | 62 | # next.js build output 63 | .next 64 | 65 | # nuxt.js build output 66 | .nuxt 67 | 68 | # vuepress build output 69 | .vuepress/dist 70 | 71 | # Serverless directories 72 | .serverless 73 | 74 | # FuseBox cache 75 | .fusebox/ 76 | 77 | .DS_Store 78 | package-lock.json 79 | -------------------------------------------------------------------------------- /certificates/localhost/fullchain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFZjCCA04CCQC/ZGwVHrIG8DANBgkqhkiG9w0BAQsFADB1MQswCQYDVQQGEwJE 3 | RTEOMAwGA1UECAwFc3RhdGUxDTALBgNVBAcMBGNpdHkxFTATBgNVBAoMDG9yZ2Fu 4 | aXphdGlvbjEcMBoGA1UECwwTb3JnYW5pemF0aW9uYWwtdW5pdDESMBAGA1UEAwwJ 5 | bG9jYWxob3N0MB4XDTIxMDkwMTA1MTQzOVoXDTMxMDgzMDA1MTQzOVowdTELMAkG 6 | A1UEBhMCREUxDjAMBgNVBAgMBXN0YXRlMQ0wCwYDVQQHDARjaXR5MRUwEwYDVQQK 7 | DAxvcmdhbml6YXRpb24xHDAaBgNVBAsME29yZ2FuaXphdGlvbmFsLXVuaXQxEjAQ 8 | BgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB 9 | AJwokJ/55EYw8nLQOLdO84Lyvyj1buoRV9OZ668l5FIPOWB9mxKgJmtXdYkGuv+p 10 | hDAnMHIx2dBJ3Bi1e01jVg4DuZfOosDnr0s89Qkh2ZKw03RhqrOsgf3ex8H7MHEb 11 | 3vVM5hJA5KCyCPa58u2+EDRKpSxiwzREJG6Isu+VNDnL9ZGpaH+pUrzazi0NOFUz 12 | wV4ks/4i+sDvSNzojbELBjWFQRjgHjuETRpuUyQFgnewe22TMFZZc/wgWjZ0d+bj 13 | Iyno/8OZL+B2LwpCqISRv+yfACgF0zD5VbA3m/CYFQNAIoxlLDGs5r0eNHjuHEH0 14 | xxPhM+luP7NBISpCkZPW9VCN0epAkrvCos1i69vxQJQzkWwSpOCUFLU5eCEqPJ8x 15 | iiBaV86/sZgvAVOfED2F+Thk4bsiVjMuFNXwVBimDYZjVC9ENvuz6A63FvC4KyFo 16 | XzJSlM4i7ppjn13qVN6zXmfKVw25vYIoTLlby8DKrjdyUXmZbK+UvsEFYUshuW4D 17 | VsPNf9mQdY8OiTgap+SMAPj9KdpaLXktQPWNAUWfczKfN0ksyp564On2Wg/bdCFX 18 | zhDrCPHlx5RVpkJ6DsazgXaNnvb/hZD8+QlC8SJ/UEAV5R7d1/1S8pZ7Hy3U8wws 19 | jRSFNTKDc3GGT3UrGQFsN/5AEJOSi82xOf3lrlyFbVHtAgMBAAEwDQYJKoZIhvcN 20 | AQELBQADggIBAB2Kj0VtywB3k49kT3luJ1vrr6A0ZCsalibOUjSo+oxIOLsaSKF8 21 | scZ4u2VI1eidgiYX9exosfj8E/WQELxzctQTlwohcvkDuCWD3AV7+ykpJZQ79woK 22 | FWEPAb6+oYASaExbhOXEt2oW00+dN9pu9GqvXLdAUlS3P40zWvWq3RuxZIYKW8u/ 23 | lpo0naRRcZWf/xvFI8D6KJEvIkwvktq7c/iaXBfrVyjc0gwVLWoc0zHESMP6gQca 24 | 1I0CyDtMMKQAZMsXnGNIux8yazXmjFfIz+ph5X5ne4SR37wPNsWD6o0PVe+cpsN0 25 | xbaW1istZ4MCbG3x7RdqurBfrFJijNh+6zYPrBiwtUIFu9Z3EI/9auu6u3VdPfv/ 26 | LiC6dcOqYEiS+M3p7lLQHqI8zRnAvcrY7ljUCHPeKwRDMh68fFuBBHOoXOcy3x8s 27 | fIyAQG3Jp2rQI3lNfczkP4R/u0ArHkubpLqESmU6Ka/MIf7aM/phy2fd9inIXSbj 28 | IHI+Lc5KoQDDoSh41lA8HKIzmY6O1nDMMExg0MQoT1BCSTtMReK1KL3pSi5lq9av 29 | 6SD50zSfnJB/vbYzMEmlUmiSU4HeyNDqEb3BmOf5sSJTkoagCBaQqqRjeOU39TON 30 | DZVJ7lS73raUVkD/KrnCQYnkNSDfK1zfz72Ht2a8ou7zPbSoQJ9pw+WT 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /certificates/local-server.org/fullchain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFdDCCA1wCCQDaPuHbD1iTdTANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJE 3 | RTEOMAwGA1UECAwFc3RhdGUxDTALBgNVBAcMBGNpdHkxFTATBgNVBAoMDG9yZ2Fu 4 | aXphdGlvbjEcMBoGA1UECwwTb3JnYW5pemF0aW9uYWwtdW5pdDEZMBcGA1UEAwwQ 5 | bG9jYWwtc2VydmVyLm9yZzAeFw0yMTA5MDEwNTE0MTRaFw0zMTA4MzAwNTE0MTRa 6 | MHwxCzAJBgNVBAYTAkRFMQ4wDAYDVQQIDAVzdGF0ZTENMAsGA1UEBwwEY2l0eTEV 7 | MBMGA1UECgwMb3JnYW5pemF0aW9uMRwwGgYDVQQLDBNvcmdhbml6YXRpb25hbC11 8 | bml0MRkwFwYDVQQDDBBsb2NhbC1zZXJ2ZXIub3JnMIICIjANBgkqhkiG9w0BAQEF 9 | AAOCAg8AMIICCgKCAgEA4Lc+2209IltR6T5zf69h2aYYMESAwcFSVSTbKIIMw5SG 10 | qnn1aCDgRN2OL17VTLGOk5BxowBuwGFruhphxbuPa74pZK1rQUSgTRaPsOLmiHeH 11 | uXGTLXE05PcOqJJQ/OqcyVhxlH1apPtA62hMJ9ErtV8uOsLy87fvRnolD+f69TNt 12 | Vsdh1Nm6V9QadanmVeGtbWm/FL+HMp+5gzK6LXonpegi9xAh6iVJYTwwAiEfHBk5 13 | WB4oeDmlEahaUuckUAu11oaMAc0O0x2qLw5UdWt5uBtapXf1fVtmrW6iOzR5osL5 14 | inc+WB/xu3ErDGmFAqdk64pmr58ej0qifLcI3uGpt0bWmfY36cFAvfcKnbd8HejS 15 | 62OYTF1+WQmNRB8ze3mIO4AfZEvCKFw93bqdZp8BnVfCpY1chm1tZS4Bf8+He6oZ 16 | l/+OqvVREVe1+uaIE0pQRBUQr5DCKA85AKqLZMiL9b6lQjTclBX9AGekIkYv+BJ1 17 | bjMUyNfQCZynLLb7iS+qwph+ST0PxrawAeb0RufzDTJUv1ml6u+d4e4S77SDos0A 18 | PjsG4sZ7wHFPZbW7jF5ymIL9ePiVJRIfqNEoRLNvCCGKjBd0ZeMMdApAMneM6ka1 19 | a8og9jmhfC2ZAihBod78woT3hwObZvVI/DOZWeRC752L+f4f/eicNdbtH+t9pksC 20 | AwEAATANBgkqhkiG9w0BAQsFAAOCAgEAZ4xWzrqn3oZMUvlSx6BkCEnB3f3qx5aH 21 | qqQnIH+ewvhl0hUHNJGuxkgByCTwvKixqdYKcpzgDxUiuh5rJySFwHcfAMp9afdJ 22 | TeGC+tDLpwGTClar8FfMwaRSyqiPOF6ieGeh7Oxy2hUj7M0Y6CkX36SFnW55vJgz 23 | KTEIQKizMD//TnoDGFgcCQ+XSAhOynywtwHuCssSg0fztsKQr9+iRhNUrnzQeXIP 24 | 1/4IeY/UM+9oAPJx0hfCiaGTM9BBitlCdzLfbmKkXh8t/0a+JaH3vO3ybcS3OaD7 25 | n38aRlFYJPSdn7Hux6el8GxG1AmMrBQCqg5E1fI3F9i/H+581RRc4BXWkQCIUDDx 26 | Yzub1dRVhxN6Wj8uudLKOwBI5157f6FUajehqxsQTo1fJmOPCuxTepY/t2C50ZdK 27 | 0En26rk7aswksUrcs58T70tdCShAzUTXg23JFCE5HohNTVAEEZ19hxpPfHsXLrQE 28 | PvCgxiA7dDnM7UKSo+VVRe52ZR0xJBSfaFJVogLBHuZWmNz5y8KnRlhKwcCvw6Wk 29 | RyuyXqsoVx20NzdpPupCczWmMd7JIDBzO2sxIIsNAdrlCnotiPlUSRcmjmIwPRPf 30 | Uj2CuoDSZRv7veWH9J1FI9dVcxAddJNvDCPZvpyVusaVKmHS4y/C8i619H9rJa4F 31 | 99C4/mr81Gw= 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /generateSaltAndHash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const crypto = require('crypto') 3 | 4 | function parsePBKDF2Iterations (Value) { 5 | Value = Value.trim() 6 | if (Value === '') { throw 'no PBKDF2 iteration given' } 7 | 8 | let Count = parseInt(Value,10) 9 | if (isFinite(Count) && (Count > 0)) { 10 | return Count 11 | } else { 12 | throw 'invalid PBKDF2 iteration given' 13 | } 14 | } 15 | 16 | const Arguments = require('commander') 17 | Arguments 18 | .usage('[options]') 19 | .option('--pbkdf2-iterations ', 'PBKDF2 iteration count', parsePBKDF2Iterations) 20 | 21 | const PBKDF2Iterations = Arguments.opts().pbkdf2Iterations || 100000 22 | 23 | const stdin = process.stdin 24 | const stdout = process.stdout 25 | 26 | function readPassword (Prompt, CallBack) { 27 | if (Prompt != null) { 28 | stdout.write(Prompt) 29 | } 30 | 31 | stdin.resume() 32 | stdin.setRawMode(true) 33 | stdin.resume() 34 | stdin.setEncoding('utf8') 35 | 36 | let Password = '' 37 | stdin.on('data', function (readChar) { 38 | readChar = readChar.toString('utf8') 39 | switch (readChar) { 40 | case '\n': 41 | case '\r': 42 | case '\u0004': 43 | stdout.write('\n') 44 | stdin.setRawMode(false) 45 | stdin.pause() 46 | return CallBack(null,Password) 47 | case '\u0003': // ctrl-c 48 | return CallBack(new Error('aborted')) 49 | case '\u007f': // backspace 50 | Password = Password.slice(0, Password.length-1) 51 | stdout.clearLine() 52 | stdout.cursorTo(0) 53 | if (Prompt != null) { stdout.write(Prompt) } 54 | stdout.write(Password.split('').map(() => '*').join('')) 55 | break 56 | default: 57 | stdout.write('*') 58 | Password += readChar 59 | } 60 | }) 61 | } 62 | 63 | readPassword('enter your password: ', (Error,Password) => { 64 | if (Error == null) { 65 | let PasswordSalt = crypto.randomBytes(16) 66 | let PasswordHash = crypto.pbkdf2Sync( 67 | Password, PasswordSalt, PBKDF2Iterations, 64, 'sha512' 68 | ) 69 | 70 | stdout.write('"Salt":"' + PasswordSalt.toString('hex') + '"\n') 71 | stdout.write('"Hash":"' + PasswordHash.toString('hex') + '"\n') 72 | } else { 73 | if (Error.message === 'aborted') { 74 | stdout.write('\n') 75 | process.exit() 76 | } else { 77 | throw Error 78 | } 79 | } 80 | }) 81 | -------------------------------------------------------------------------------- /certificates/localhost/privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 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 51 | +UtlBQN0HlNQDOq/ubSYbkaeMmmwZQ== 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /certificates/local-server.org/privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 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 51 | odtHgO9Vqn7gRfpg9tM4yjoLicOpLBc= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /Node-RED/flows.json: -------------------------------------------------------------------------------- 1 | [{"id":"a68b460c7cb2c5b3","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"56af3abb553264d3","type":"tab","label":"Flow 2","disabled":false,"info":""},{"id":"21cdf84eb7666cd4","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"/hello-world","method":"get","upload":false,"swaggerDoc":"","x":100,"y":160,"wires":[["17f9c70568bd8c46"]]},{"id":"fd52461d2dcacfc7","type":"comment","z":"a68b460c7cb2c5b3","name":"HTTP Endpoint Example","info":"","x":130,"y":100,"wires":[]},{"id":"17f9c70568bd8c46","type":"change","z":"a68b460c7cb2c5b3","name":"create Response","rules":[{"t":"set","p":"payload","pt":"msg","to":"Hello, World!","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":160,"wires":[["19316dac4d7022b2"]]},{"id":"19316dac4d7022b2","type":"http response","z":"a68b460c7cb2c5b3","name":"","statusCode":"200","headers":{},"x":500,"y":160,"wires":[]},{"id":"3b19c11ce044e751","type":"comment","z":"a68b460c7cb2c5b3","name":"show Request on Console (at /show-request)","info":"","x":190,"y":220,"wires":[]},{"id":"6ef2072a8470ed71","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"show-request","method":"get","upload":false,"swaggerDoc":"","x":110,"y":280,"wires":[["7860b328926aa62d","1081fc61bdcdf351"]]},{"id":"a1cc7b1b1831f5cd","type":"http response","z":"a68b460c7cb2c5b3","name":"","statusCode":"200","headers":{},"x":520,"y":440,"wires":[]},{"id":"1e995a639261be26","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"show-request","method":"post","upload":true,"swaggerDoc":"","x":110,"y":320,"wires":[["7860b328926aa62d","1081fc61bdcdf351"]]},{"id":"bedb272dbd0bab27","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"show-request","method":"put","upload":false,"swaggerDoc":"","x":110,"y":360,"wires":[["7860b328926aa62d","1081fc61bdcdf351"]]},{"id":"cc3dd3e662f6769f","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"show-request","method":"delete","upload":false,"swaggerDoc":"","x":120,"y":400,"wires":[["7860b328926aa62d","1081fc61bdcdf351"]]},{"id":"b01e9d1db5485fc7","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"show-request","method":"patch","upload":false,"swaggerDoc":"","x":120,"y":440,"wires":[["7860b328926aa62d","1081fc61bdcdf351"]]},{"id":"7860b328926aa62d","type":"debug","z":"a68b460c7cb2c5b3","name":"","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"'see console'","statusType":"jsonata","x":350,"y":280,"wires":[]},{"id":"1081fc61bdcdf351","type":"change","z":"a68b460c7cb2c5b3","name":"create Response","rules":[{"t":"set","p":"payload","pt":"msg","to":"(see Node-RED debug console)","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":380,"wires":[["a1cc7b1b1831f5cd"]]},{"id":"02625fa57b810b92","type":"comment","z":"a68b460c7cb2c5b3","name":"Node-RED does not like \"null\" responses","info":"","x":320,"y":480,"wires":[]},{"id":"199b22c34871e556","type":"comment","z":"a68b460c7cb2c5b3","name":"Examples for Servers without specific Domains","info":"","x":200,"y":40,"wires":[]},{"id":"63fbe15166ae53bb","type":"comment","z":"a68b460c7cb2c5b3","name":"Examples for Servers with specific Domains (e.g., local-server.org)","info":"","x":260,"y":560,"wires":[]},{"id":"40a88187a58c6717","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"local-server.org/hello-world","method":"get","upload":false,"swaggerDoc":"","x":150,"y":680,"wires":[["73538576a1fad326"]]},{"id":"486d16e435898bd7","type":"comment","z":"a68b460c7cb2c5b3","name":"HTTP Endpoint Example","info":"","x":130,"y":620,"wires":[]},{"id":"73538576a1fad326","type":"change","z":"a68b460c7cb2c5b3","name":"create Response","rules":[{"t":"set","p":"payload","pt":"msg","to":"Hello, World!","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":740,"wires":[["78a71aeab092b1a7"]]},{"id":"78a71aeab092b1a7","type":"http response","z":"a68b460c7cb2c5b3","name":"","statusCode":"200","headers":{},"x":520,"y":740,"wires":[]},{"id":"f52e2d15b13fcb10","type":"comment","z":"a68b460c7cb2c5b3","name":"show Request on Console (at /show-request)","info":"","x":190,"y":800,"wires":[]},{"id":"595cce94df1623c0","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"local-server.org/show-request","method":"get","upload":false,"swaggerDoc":"","x":160,"y":860,"wires":[["61269aa63b5e3e66","c6962d0b01be90e5"]]},{"id":"39202932731c29ce","type":"http response","z":"a68b460c7cb2c5b3","name":"","statusCode":"200","headers":{},"x":520,"y":1020,"wires":[]},{"id":"f2f5a7750bae902f","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"local-server.org/show-request","method":"post","upload":true,"swaggerDoc":"","x":160,"y":900,"wires":[["61269aa63b5e3e66","c6962d0b01be90e5"]]},{"id":"cde57e3993db7b5a","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"local-server.org/show-request","method":"put","upload":false,"swaggerDoc":"","x":160,"y":940,"wires":[["61269aa63b5e3e66","c6962d0b01be90e5"]]},{"id":"802cd607f1cdbb73","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"local-server.org/show-request","method":"delete","upload":false,"swaggerDoc":"","x":170,"y":980,"wires":[["61269aa63b5e3e66","c6962d0b01be90e5"]]},{"id":"5d62e05b591a0141","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"local-server.org/show-request","method":"patch","upload":false,"swaggerDoc":"","x":170,"y":1020,"wires":[["61269aa63b5e3e66","c6962d0b01be90e5"]]},{"id":"61269aa63b5e3e66","type":"debug","z":"a68b460c7cb2c5b3","name":"","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"'see console'","statusType":"jsonata","x":490,"y":860,"wires":[]},{"id":"c6962d0b01be90e5","type":"change","z":"a68b460c7cb2c5b3","name":"create Response","rules":[{"t":"set","p":"payload","pt":"msg","to":"(see Node-RED debug console)","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":960,"wires":[["39202932731c29ce"]]},{"id":"e0e4dfabd6383880","type":"comment","z":"a68b460c7cb2c5b3","name":"Node-RED does not like \"null\" responses","info":"","x":320,"y":1060,"wires":[]},{"id":"f77d8bb14ed19c89","type":"comment","z":"56af3abb553264d3","name":"Contents of the global Context","info":"","x":150,"y":40,"wires":[]},{"id":"72e954655f06cdde","type":"inject","z":"56af3abb553264d3","name":"show Contents","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":120,"y":100,"wires":[["e9de84d6f0646db7"]]},{"id":"b1ea56523f3abbe0","type":"debug","z":"56af3abb553264d3","name":"show","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":430,"y":100,"wires":[]},{"id":"e9de84d6f0646db7","type":"function","z":"56af3abb553264d3","name":"collect","func":"msg.payload = (\n 'ServerPort: ' + global.get('ServerPort') + '\\n' +\n 'RedirectionPort: ' + (global.get('RedirectionPort') || '-') + '\\n' +\n 'behindProxy: ' + global.get('behindProxy') + '\\n' +\n 'primaryDomain: ' + (global.get('primaryDomain') || '-') + '\\n' +\n 'virtualHosts: ' + global.get('virtualHosts').join(',') + '\\n' +\n 'allowSubdomains: ' + global.get('allowSubdomains') + '\\n' +\n 'PBKDF2Iterations:' + global.get('PBKDF2Iterations') + '\\n' +\n 'FileRoot: \"' + global.get('FileRoot') + '\"\\n' +\n 'ConfigRoot: \"' + global.get('ConfigRoot') + '\"\\n' +\n 'LogRoot: \"' + global.get('LogRoot') + '\"\\n'\n)\n\nlet CORSRegistry = global.get('CORSRegistry')\n if (CORSRegistry.length === 0) {\n msg.payload += 'CORSRegistry: -\\n'\n } else {\n msg.payload += 'CORSRegistry:\\n'\n for (let i = 0, l = CORSRegistry.length ; i < l; i++) {\n let Rule = CORSRegistry[i]\n msg.payload += (\n ' - \"' + Rule.PathPattern + '\": ' + (\n Array.isArray(Rule.OriginList)\n ? '[' + Rule.OriginList.join(',') + ']'\n : '*'\n ) + '\\n'\n )\n }\n }\n\nlet UserRegistry = global.get('UserRegistry')\n msg.payload += 'UserRegistry:\\n'\n for (let UserName in UserRegistry) {\n let Roles = UserRegistry[UserName].Roles\n msg.payload += (\n ' - \"' + UserName + '\": ' + (\n Array.isArray(Roles)\n ? '[' + Roles.join(',') + ']'\n : '[]'\n )\n ) + '\\n'\n }\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":290,"y":100,"wires":[["b1ea56523f3abbe0"]]}] -------------------------------------------------------------------------------- /Node-RED/.flows.json.backup: -------------------------------------------------------------------------------- 1 | [{"id":"a68b460c7cb2c5b3","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"56af3abb553264d3","type":"tab","label":"Flow 2","disabled":false,"info":""},{"id":"21cdf84eb7666cd4","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"/hello-world","method":"get","upload":false,"swaggerDoc":"","x":100,"y":160,"wires":[["17f9c70568bd8c46"]]},{"id":"fd52461d2dcacfc7","type":"comment","z":"a68b460c7cb2c5b3","name":"HTTP Endpoint Example","info":"","x":130,"y":100,"wires":[]},{"id":"17f9c70568bd8c46","type":"change","z":"a68b460c7cb2c5b3","name":"create Response","rules":[{"t":"set","p":"payload","pt":"msg","to":"Hello, World!","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":160,"wires":[["19316dac4d7022b2"]]},{"id":"19316dac4d7022b2","type":"http response","z":"a68b460c7cb2c5b3","name":"","statusCode":"200","headers":{},"x":500,"y":160,"wires":[]},{"id":"3b19c11ce044e751","type":"comment","z":"a68b460c7cb2c5b3","name":"show Request on Console (at /show-request)","info":"","x":190,"y":220,"wires":[]},{"id":"6ef2072a8470ed71","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"show-request","method":"get","upload":false,"swaggerDoc":"","x":110,"y":280,"wires":[["7860b328926aa62d","1081fc61bdcdf351"]]},{"id":"a1cc7b1b1831f5cd","type":"http response","z":"a68b460c7cb2c5b3","name":"","statusCode":"200","headers":{},"x":520,"y":440,"wires":[]},{"id":"1e995a639261be26","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"show-request","method":"post","upload":true,"swaggerDoc":"","x":110,"y":320,"wires":[["7860b328926aa62d","1081fc61bdcdf351"]]},{"id":"bedb272dbd0bab27","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"show-request","method":"put","upload":false,"swaggerDoc":"","x":110,"y":360,"wires":[["7860b328926aa62d","1081fc61bdcdf351"]]},{"id":"cc3dd3e662f6769f","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"show-request","method":"delete","upload":false,"swaggerDoc":"","x":120,"y":400,"wires":[["7860b328926aa62d","1081fc61bdcdf351"]]},{"id":"b01e9d1db5485fc7","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"show-request","method":"patch","upload":false,"swaggerDoc":"","x":120,"y":440,"wires":[["7860b328926aa62d","1081fc61bdcdf351"]]},{"id":"7860b328926aa62d","type":"debug","z":"a68b460c7cb2c5b3","name":"","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"'see console'","statusType":"jsonata","x":350,"y":280,"wires":[]},{"id":"1081fc61bdcdf351","type":"change","z":"a68b460c7cb2c5b3","name":"create Response","rules":[{"t":"set","p":"payload","pt":"msg","to":"(see Node-RED debug console)","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":380,"wires":[["a1cc7b1b1831f5cd"]]},{"id":"02625fa57b810b92","type":"comment","z":"a68b460c7cb2c5b3","name":"Node-RED does not like \"null\" responses","info":"","x":320,"y":480,"wires":[]},{"id":"199b22c34871e556","type":"comment","z":"a68b460c7cb2c5b3","name":"Examples for Servers without specific Domains","info":"","x":200,"y":40,"wires":[]},{"id":"63fbe15166ae53bb","type":"comment","z":"a68b460c7cb2c5b3","name":"Examples for Servers with specific Domains (e.g., local-server.org)","info":"","x":260,"y":560,"wires":[]},{"id":"40a88187a58c6717","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"local-server.org/hello-world","method":"get","upload":false,"swaggerDoc":"","x":150,"y":680,"wires":[["73538576a1fad326"]]},{"id":"486d16e435898bd7","type":"comment","z":"a68b460c7cb2c5b3","name":"HTTP Endpoint Example","info":"","x":130,"y":620,"wires":[]},{"id":"73538576a1fad326","type":"change","z":"a68b460c7cb2c5b3","name":"create Response","rules":[{"t":"set","p":"payload","pt":"msg","to":"Hello, World!","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":740,"wires":[["78a71aeab092b1a7"]]},{"id":"78a71aeab092b1a7","type":"http response","z":"a68b460c7cb2c5b3","name":"","statusCode":"200","headers":{},"x":520,"y":740,"wires":[]},{"id":"f52e2d15b13fcb10","type":"comment","z":"a68b460c7cb2c5b3","name":"show Request on Console (at /show-request)","info":"","x":190,"y":800,"wires":[]},{"id":"595cce94df1623c0","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"local-server.org/show-request","method":"get","upload":false,"swaggerDoc":"","x":160,"y":860,"wires":[["61269aa63b5e3e66","c6962d0b01be90e5"]]},{"id":"39202932731c29ce","type":"http response","z":"a68b460c7cb2c5b3","name":"","statusCode":"200","headers":{},"x":520,"y":1020,"wires":[]},{"id":"f2f5a7750bae902f","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"local-server.org/show-request","method":"post","upload":true,"swaggerDoc":"","x":160,"y":900,"wires":[["61269aa63b5e3e66","c6962d0b01be90e5"]]},{"id":"cde57e3993db7b5a","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"local-server.org/show-request","method":"put","upload":false,"swaggerDoc":"","x":160,"y":940,"wires":[["61269aa63b5e3e66","c6962d0b01be90e5"]]},{"id":"802cd607f1cdbb73","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"local-server.org/show-request","method":"delete","upload":false,"swaggerDoc":"","x":170,"y":980,"wires":[["61269aa63b5e3e66","c6962d0b01be90e5"]]},{"id":"5d62e05b591a0141","type":"http in","z":"a68b460c7cb2c5b3","name":"","url":"local-server.org/show-request","method":"patch","upload":false,"swaggerDoc":"","x":170,"y":1020,"wires":[["61269aa63b5e3e66","c6962d0b01be90e5"]]},{"id":"61269aa63b5e3e66","type":"debug","z":"a68b460c7cb2c5b3","name":"","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"'see console'","statusType":"jsonata","x":490,"y":860,"wires":[]},{"id":"c6962d0b01be90e5","type":"change","z":"a68b460c7cb2c5b3","name":"create Response","rules":[{"t":"set","p":"payload","pt":"msg","to":"(see Node-RED debug console)","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":960,"wires":[["39202932731c29ce"]]},{"id":"e0e4dfabd6383880","type":"comment","z":"a68b460c7cb2c5b3","name":"Node-RED does not like \"null\" responses","info":"","x":320,"y":1060,"wires":[]},{"id":"f77d8bb14ed19c89","type":"comment","z":"56af3abb553264d3","name":"Contents of the global Context","info":"","x":150,"y":40,"wires":[]},{"id":"72e954655f06cdde","type":"inject","z":"56af3abb553264d3","name":"show Contents","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":120,"y":100,"wires":[["e9de84d6f0646db7"]]},{"id":"b1ea56523f3abbe0","type":"debug","z":"56af3abb553264d3","name":"show","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":430,"y":100,"wires":[]},{"id":"e9de84d6f0646db7","type":"function","z":"56af3abb553264d3","name":"collect","func":"msg.payload = (\n 'ServerPort: ' + global.get('ServerPort') + '\\n' +\n 'RedirectionPort: ' + (global.get('RedirectionPort') || '-') + '\\n' +\n 'behindProxy: ' + global.get('behindProxy') + '\\n' +\n 'primaryDomain: ' + (global.get('primaryDomain') || '-') + '\\n' +\n 'virtualHosts: ' + global.get('virtualHosts').join(',') + '\\n' +\n 'allowSubdomains: ' + global.get('allowSubdomains') + '\\n' +\n 'PBKDF2Iterations:' + global.get('PBKDF2Iterations') + '\\n' +\n 'FileRoot: \"' + global.get('FileRoot') + '\"\\n' +\n 'ConfigRoot: \"' + global.get('ConfigRoot') + '\"\\n' +\n 'LogRoot: \"' + global.get('LogRoot') + '\"\\n'\n)\n\nlet CORSRegistry = global.get('CORSRegistry')\n if (CORSRegistry.length === 0) {\n msg.payload += 'CORSRegistry: -\\n'\n } else {\n msg.payload += 'CORSRegistry:\\n'\n for (let i = 0, l = CORSRegistry.length ; i < l; i++) {\n let Rule = CORSRegistry[i]\n msg.payload += (\n ' - \"' + Rule.PathPattern + '\": ' + (\n Array.isArray(Rule.OriginList)\n ? '[' + Rule.OriginList.join(',') + ']'\n : '[]'\n ) + '\\n'\n )\n }\n }\n\nlet UserRegistry = global.get('UserRegistry')\n msg.payload += 'UserRegistry:\\n'\n for (let UserName in UserRegistry) {\n let Roles = UserRegistry[UserName].Roles\n msg.payload += (\n ' - \"' + UserName + '\": ' + (\n Array.isArray(Roles)\n ? '[' + Roles.join(',') + ']'\n : '[]'\n )\n ) + '\\n'\n }\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":290,"y":100,"wires":[["b1ea56523f3abbe0"]]}] -------------------------------------------------------------------------------- /Node-RED/.config.nodes.json: -------------------------------------------------------------------------------- 1 | { 2 | "node-red": { 3 | "name": "node-red", 4 | "version": "2.0.6-git", 5 | "local": false, 6 | "user": false, 7 | "nodes": { 8 | "inject": { 9 | "name": "inject", 10 | "types": [ 11 | "inject" 12 | ], 13 | "enabled": true, 14 | "local": false, 15 | "user": false, 16 | "module": "node-red", 17 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/20-inject.js" 18 | }, 19 | "debug": { 20 | "name": "debug", 21 | "types": [ 22 | "debug" 23 | ], 24 | "enabled": true, 25 | "local": false, 26 | "user": false, 27 | "module": "node-red", 28 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/21-debug.js" 29 | }, 30 | "complete": { 31 | "name": "complete", 32 | "types": [ 33 | "complete" 34 | ], 35 | "enabled": true, 36 | "local": false, 37 | "user": false, 38 | "module": "node-red", 39 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/24-complete.js" 40 | }, 41 | "catch": { 42 | "name": "catch", 43 | "types": [ 44 | "catch" 45 | ], 46 | "enabled": true, 47 | "local": false, 48 | "user": false, 49 | "module": "node-red", 50 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/25-catch.js" 51 | }, 52 | "status": { 53 | "name": "status", 54 | "types": [ 55 | "status" 56 | ], 57 | "enabled": true, 58 | "local": false, 59 | "user": false, 60 | "module": "node-red", 61 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/25-status.js" 62 | }, 63 | "link": { 64 | "name": "link", 65 | "types": [ 66 | "link in", 67 | "link out" 68 | ], 69 | "enabled": true, 70 | "local": false, 71 | "user": false, 72 | "module": "node-red", 73 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/60-link.js" 74 | }, 75 | "comment": { 76 | "name": "comment", 77 | "types": [ 78 | "comment" 79 | ], 80 | "enabled": true, 81 | "local": false, 82 | "user": false, 83 | "module": "node-red", 84 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/90-comment.js" 85 | }, 86 | "unknown": { 87 | "name": "unknown", 88 | "types": [ 89 | "unknown" 90 | ], 91 | "enabled": true, 92 | "local": false, 93 | "user": false, 94 | "module": "node-red", 95 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/98-unknown.js" 96 | }, 97 | "function": { 98 | "name": "function", 99 | "types": [ 100 | "function" 101 | ], 102 | "enabled": true, 103 | "local": false, 104 | "user": false, 105 | "module": "node-red", 106 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/10-function.js" 107 | }, 108 | "switch": { 109 | "name": "switch", 110 | "types": [ 111 | "switch" 112 | ], 113 | "enabled": true, 114 | "local": false, 115 | "user": false, 116 | "module": "node-red", 117 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/10-switch.js" 118 | }, 119 | "change": { 120 | "name": "change", 121 | "types": [ 122 | "change" 123 | ], 124 | "enabled": true, 125 | "local": false, 126 | "user": false, 127 | "module": "node-red", 128 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/15-change.js" 129 | }, 130 | "range": { 131 | "name": "range", 132 | "types": [ 133 | "range" 134 | ], 135 | "enabled": true, 136 | "local": false, 137 | "user": false, 138 | "module": "node-red", 139 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/16-range.js" 140 | }, 141 | "template": { 142 | "name": "template", 143 | "types": [ 144 | "template" 145 | ], 146 | "enabled": true, 147 | "local": false, 148 | "user": false, 149 | "module": "node-red", 150 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/80-template.js" 151 | }, 152 | "delay": { 153 | "name": "delay", 154 | "types": [ 155 | "delay" 156 | ], 157 | "enabled": true, 158 | "local": false, 159 | "user": false, 160 | "module": "node-red", 161 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/89-delay.js" 162 | }, 163 | "trigger": { 164 | "name": "trigger", 165 | "types": [ 166 | "trigger" 167 | ], 168 | "enabled": true, 169 | "local": false, 170 | "user": false, 171 | "module": "node-red", 172 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/89-trigger.js" 173 | }, 174 | "exec": { 175 | "name": "exec", 176 | "types": [ 177 | "exec" 178 | ], 179 | "enabled": true, 180 | "local": false, 181 | "user": false, 182 | "module": "node-red", 183 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/90-exec.js" 184 | }, 185 | "rbe": { 186 | "name": "rbe", 187 | "types": [ 188 | "rbe" 189 | ], 190 | "enabled": true, 191 | "local": false, 192 | "user": false, 193 | "module": "node-red", 194 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/rbe.js" 195 | }, 196 | "tls": { 197 | "name": "tls", 198 | "types": [ 199 | "tls-config" 200 | ], 201 | "enabled": true, 202 | "local": false, 203 | "user": false, 204 | "module": "node-red", 205 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/05-tls.js" 206 | }, 207 | "httpproxy": { 208 | "name": "httpproxy", 209 | "types": [ 210 | "http proxy" 211 | ], 212 | "enabled": true, 213 | "local": false, 214 | "user": false, 215 | "module": "node-red", 216 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/06-httpproxy.js" 217 | }, 218 | "mqtt": { 219 | "name": "mqtt", 220 | "types": [ 221 | "mqtt in", 222 | "mqtt out", 223 | "mqtt-broker" 224 | ], 225 | "enabled": true, 226 | "local": false, 227 | "user": false, 228 | "module": "node-red", 229 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/10-mqtt.js" 230 | }, 231 | "httpin": { 232 | "name": "httpin", 233 | "types": [ 234 | "http in", 235 | "http response" 236 | ], 237 | "enabled": true, 238 | "local": false, 239 | "user": false, 240 | "module": "node-red", 241 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/21-httpin.js" 242 | }, 243 | "httprequest": { 244 | "name": "httprequest", 245 | "types": [ 246 | "http request" 247 | ], 248 | "enabled": true, 249 | "local": false, 250 | "user": false, 251 | "module": "node-red", 252 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/21-httprequest.js" 253 | }, 254 | "websocket": { 255 | "name": "websocket", 256 | "types": [ 257 | "websocket in", 258 | "websocket out", 259 | "websocket-listener", 260 | "websocket-client" 261 | ], 262 | "enabled": true, 263 | "local": false, 264 | "user": false, 265 | "module": "node-red", 266 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/22-websocket.js" 267 | }, 268 | "tcpin": { 269 | "name": "tcpin", 270 | "types": [ 271 | "tcp in", 272 | "tcp out", 273 | "tcp request" 274 | ], 275 | "enabled": true, 276 | "local": false, 277 | "user": false, 278 | "module": "node-red", 279 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/31-tcpin.js" 280 | }, 281 | "udp": { 282 | "name": "udp", 283 | "types": [ 284 | "udp in", 285 | "udp out" 286 | ], 287 | "enabled": true, 288 | "local": false, 289 | "user": false, 290 | "module": "node-red", 291 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/32-udp.js" 292 | }, 293 | "CSV": { 294 | "name": "CSV", 295 | "types": [ 296 | "csv" 297 | ], 298 | "enabled": true, 299 | "local": false, 300 | "user": false, 301 | "module": "node-red", 302 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/parsers/70-CSV.js" 303 | }, 304 | "HTML": { 305 | "name": "HTML", 306 | "types": [ 307 | "html" 308 | ], 309 | "enabled": true, 310 | "local": false, 311 | "user": false, 312 | "module": "node-red", 313 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/parsers/70-HTML.js" 314 | }, 315 | "JSON": { 316 | "name": "JSON", 317 | "types": [ 318 | "json" 319 | ], 320 | "enabled": true, 321 | "local": false, 322 | "user": false, 323 | "module": "node-red", 324 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/parsers/70-JSON.js" 325 | }, 326 | "XML": { 327 | "name": "XML", 328 | "types": [ 329 | "xml" 330 | ], 331 | "enabled": true, 332 | "local": false, 333 | "user": false, 334 | "module": "node-red", 335 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/parsers/70-XML.js" 336 | }, 337 | "YAML": { 338 | "name": "YAML", 339 | "types": [ 340 | "yaml" 341 | ], 342 | "enabled": true, 343 | "local": false, 344 | "user": false, 345 | "module": "node-red", 346 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/parsers/70-YAML.js" 347 | }, 348 | "split": { 349 | "name": "split", 350 | "types": [ 351 | "split", 352 | "join" 353 | ], 354 | "enabled": true, 355 | "local": false, 356 | "user": false, 357 | "module": "node-red", 358 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/sequence/17-split.js" 359 | }, 360 | "sort": { 361 | "name": "sort", 362 | "types": [ 363 | "sort" 364 | ], 365 | "enabled": true, 366 | "local": false, 367 | "user": false, 368 | "module": "node-red", 369 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/sequence/18-sort.js" 370 | }, 371 | "batch": { 372 | "name": "batch", 373 | "types": [ 374 | "batch" 375 | ], 376 | "enabled": true, 377 | "local": false, 378 | "user": false, 379 | "module": "node-red", 380 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/sequence/19-batch.js" 381 | }, 382 | "file": { 383 | "name": "file", 384 | "types": [ 385 | "file", 386 | "file in" 387 | ], 388 | "enabled": true, 389 | "local": false, 390 | "user": false, 391 | "module": "node-red", 392 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/storage/10-file.js" 393 | }, 394 | "watch": { 395 | "name": "watch", 396 | "types": [ 397 | "watch" 398 | ], 399 | "enabled": true, 400 | "local": false, 401 | "user": false, 402 | "module": "node-red", 403 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/storage/23-watch.js" 404 | } 405 | } 406 | } 407 | } -------------------------------------------------------------------------------- /Node-RED/.config.nodes.json.backup: -------------------------------------------------------------------------------- 1 | { 2 | "node-red": { 3 | "name": "node-red", 4 | "version": "2.0.5-git", 5 | "local": false, 6 | "user": false, 7 | "nodes": { 8 | "inject": { 9 | "name": "inject", 10 | "types": [ 11 | "inject" 12 | ], 13 | "enabled": true, 14 | "local": false, 15 | "user": false, 16 | "module": "node-red", 17 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/20-inject.js" 18 | }, 19 | "debug": { 20 | "name": "debug", 21 | "types": [ 22 | "debug" 23 | ], 24 | "enabled": true, 25 | "local": false, 26 | "user": false, 27 | "module": "node-red", 28 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/21-debug.js" 29 | }, 30 | "complete": { 31 | "name": "complete", 32 | "types": [ 33 | "complete" 34 | ], 35 | "enabled": true, 36 | "local": false, 37 | "user": false, 38 | "module": "node-red", 39 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/24-complete.js" 40 | }, 41 | "catch": { 42 | "name": "catch", 43 | "types": [ 44 | "catch" 45 | ], 46 | "enabled": true, 47 | "local": false, 48 | "user": false, 49 | "module": "node-red", 50 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/25-catch.js" 51 | }, 52 | "status": { 53 | "name": "status", 54 | "types": [ 55 | "status" 56 | ], 57 | "enabled": true, 58 | "local": false, 59 | "user": false, 60 | "module": "node-red", 61 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/25-status.js" 62 | }, 63 | "link": { 64 | "name": "link", 65 | "types": [ 66 | "link in", 67 | "link out" 68 | ], 69 | "enabled": true, 70 | "local": false, 71 | "user": false, 72 | "module": "node-red", 73 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/60-link.js" 74 | }, 75 | "comment": { 76 | "name": "comment", 77 | "types": [ 78 | "comment" 79 | ], 80 | "enabled": true, 81 | "local": false, 82 | "user": false, 83 | "module": "node-red", 84 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/90-comment.js" 85 | }, 86 | "unknown": { 87 | "name": "unknown", 88 | "types": [ 89 | "unknown" 90 | ], 91 | "enabled": true, 92 | "local": false, 93 | "user": false, 94 | "module": "node-red", 95 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/common/98-unknown.js" 96 | }, 97 | "function": { 98 | "name": "function", 99 | "types": [ 100 | "function" 101 | ], 102 | "enabled": true, 103 | "local": false, 104 | "user": false, 105 | "module": "node-red", 106 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/10-function.js" 107 | }, 108 | "switch": { 109 | "name": "switch", 110 | "types": [ 111 | "switch" 112 | ], 113 | "enabled": true, 114 | "local": false, 115 | "user": false, 116 | "module": "node-red", 117 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/10-switch.js" 118 | }, 119 | "change": { 120 | "name": "change", 121 | "types": [ 122 | "change" 123 | ], 124 | "enabled": true, 125 | "local": false, 126 | "user": false, 127 | "module": "node-red", 128 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/15-change.js" 129 | }, 130 | "range": { 131 | "name": "range", 132 | "types": [ 133 | "range" 134 | ], 135 | "enabled": true, 136 | "local": false, 137 | "user": false, 138 | "module": "node-red", 139 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/16-range.js" 140 | }, 141 | "template": { 142 | "name": "template", 143 | "types": [ 144 | "template" 145 | ], 146 | "enabled": true, 147 | "local": false, 148 | "user": false, 149 | "module": "node-red", 150 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/80-template.js" 151 | }, 152 | "delay": { 153 | "name": "delay", 154 | "types": [ 155 | "delay" 156 | ], 157 | "enabled": true, 158 | "local": false, 159 | "user": false, 160 | "module": "node-red", 161 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/89-delay.js" 162 | }, 163 | "trigger": { 164 | "name": "trigger", 165 | "types": [ 166 | "trigger" 167 | ], 168 | "enabled": true, 169 | "local": false, 170 | "user": false, 171 | "module": "node-red", 172 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/89-trigger.js" 173 | }, 174 | "exec": { 175 | "name": "exec", 176 | "types": [ 177 | "exec" 178 | ], 179 | "enabled": true, 180 | "local": false, 181 | "user": false, 182 | "module": "node-red", 183 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/90-exec.js" 184 | }, 185 | "rbe": { 186 | "name": "rbe", 187 | "types": [ 188 | "rbe" 189 | ], 190 | "enabled": true, 191 | "local": false, 192 | "user": false, 193 | "module": "node-red", 194 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/function/rbe.js" 195 | }, 196 | "tls": { 197 | "name": "tls", 198 | "types": [ 199 | "tls-config" 200 | ], 201 | "enabled": true, 202 | "local": false, 203 | "user": false, 204 | "module": "node-red", 205 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/05-tls.js" 206 | }, 207 | "httpproxy": { 208 | "name": "httpproxy", 209 | "types": [ 210 | "http proxy" 211 | ], 212 | "enabled": true, 213 | "local": false, 214 | "user": false, 215 | "module": "node-red", 216 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/06-httpproxy.js" 217 | }, 218 | "mqtt": { 219 | "name": "mqtt", 220 | "types": [ 221 | "mqtt in", 222 | "mqtt out", 223 | "mqtt-broker" 224 | ], 225 | "enabled": true, 226 | "local": false, 227 | "user": false, 228 | "module": "node-red", 229 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/10-mqtt.js" 230 | }, 231 | "httpin": { 232 | "name": "httpin", 233 | "types": [ 234 | "http in", 235 | "http response" 236 | ], 237 | "enabled": true, 238 | "local": false, 239 | "user": false, 240 | "module": "node-red", 241 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/21-httpin.js" 242 | }, 243 | "httprequest": { 244 | "name": "httprequest", 245 | "types": [ 246 | "http request" 247 | ], 248 | "enabled": true, 249 | "local": false, 250 | "user": false, 251 | "module": "node-red", 252 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/21-httprequest.js" 253 | }, 254 | "websocket": { 255 | "name": "websocket", 256 | "types": [ 257 | "websocket in", 258 | "websocket out", 259 | "websocket-listener", 260 | "websocket-client" 261 | ], 262 | "enabled": true, 263 | "local": false, 264 | "user": false, 265 | "module": "node-red", 266 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/22-websocket.js" 267 | }, 268 | "tcpin": { 269 | "name": "tcpin", 270 | "types": [ 271 | "tcp in", 272 | "tcp out", 273 | "tcp request" 274 | ], 275 | "enabled": true, 276 | "local": false, 277 | "user": false, 278 | "module": "node-red", 279 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/31-tcpin.js" 280 | }, 281 | "udp": { 282 | "name": "udp", 283 | "types": [ 284 | "udp in", 285 | "udp out" 286 | ], 287 | "enabled": true, 288 | "local": false, 289 | "user": false, 290 | "module": "node-red", 291 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/network/32-udp.js" 292 | }, 293 | "CSV": { 294 | "name": "CSV", 295 | "types": [ 296 | "csv" 297 | ], 298 | "enabled": true, 299 | "local": false, 300 | "user": false, 301 | "module": "node-red", 302 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/parsers/70-CSV.js" 303 | }, 304 | "HTML": { 305 | "name": "HTML", 306 | "types": [ 307 | "html" 308 | ], 309 | "enabled": true, 310 | "local": false, 311 | "user": false, 312 | "module": "node-red", 313 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/parsers/70-HTML.js" 314 | }, 315 | "JSON": { 316 | "name": "JSON", 317 | "types": [ 318 | "json" 319 | ], 320 | "enabled": true, 321 | "local": false, 322 | "user": false, 323 | "module": "node-red", 324 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/parsers/70-JSON.js" 325 | }, 326 | "XML": { 327 | "name": "XML", 328 | "types": [ 329 | "xml" 330 | ], 331 | "enabled": true, 332 | "local": false, 333 | "user": false, 334 | "module": "node-red", 335 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/parsers/70-XML.js" 336 | }, 337 | "YAML": { 338 | "name": "YAML", 339 | "types": [ 340 | "yaml" 341 | ], 342 | "enabled": true, 343 | "local": false, 344 | "user": false, 345 | "module": "node-red", 346 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/parsers/70-YAML.js" 347 | }, 348 | "split": { 349 | "name": "split", 350 | "types": [ 351 | "split", 352 | "join" 353 | ], 354 | "enabled": true, 355 | "local": false, 356 | "user": false, 357 | "module": "node-red", 358 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/sequence/17-split.js" 359 | }, 360 | "sort": { 361 | "name": "sort", 362 | "types": [ 363 | "sort" 364 | ], 365 | "enabled": true, 366 | "local": false, 367 | "user": false, 368 | "module": "node-red", 369 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/sequence/18-sort.js" 370 | }, 371 | "batch": { 372 | "name": "batch", 373 | "types": [ 374 | "batch" 375 | ], 376 | "enabled": true, 377 | "local": false, 378 | "user": false, 379 | "module": "node-red", 380 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/sequence/19-batch.js" 381 | }, 382 | "file": { 383 | "name": "file", 384 | "types": [ 385 | "file", 386 | "file in" 387 | ], 388 | "enabled": true, 389 | "local": false, 390 | "user": false, 391 | "module": "node-red", 392 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/storage/10-file.js" 393 | }, 394 | "watch": { 395 | "name": "watch", 396 | "types": [ 397 | "watch" 398 | ], 399 | "enabled": true, 400 | "local": false, 401 | "user": false, 402 | "module": "node-red", 403 | "file": "/Users/andreas/Rozek/GitHub/node-red-within-express/node_modules/@node-red/nodes/core/storage/23-watch.js" 404 | } 405 | } 406 | } 407 | } -------------------------------------------------------------------------------- /WebServer.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * * 3 | * WebServer with embedded Node-RED Instance * 4 | * * 5 | *******************************************************************************/ 6 | 7 | const Version = '0.1.0' 8 | 9 | const fs = require('fs') 10 | const path = require('path') 11 | const crypto = require('crypto') 12 | const url = require('url') 13 | const http = require('http') 14 | const https = require('https') 15 | 16 | const express = require('express') 17 | const RED = require('node-red') 18 | 19 | /**** Node-RED configuration ****/ 20 | 21 | const REDSettings = { 22 | httpAdminRoot: '/.Node-RED/Editor', // Node-RED Editor, must start with '/' 23 | // will only be served by primary domain 24 | httpNodeRoot: '/', // Node-RED "HTTP in" nodes, must start with '/' 25 | userDir: './Node-RED', // will be appended to "ConfigRoot" later 26 | flowFile: 'flows.json', // independent of host name 27 | credentialSecret: 'not-so-secret', 28 | flowFilePretty: true, 29 | 30 | functionExternalModules: true, 31 | functionGlobalContext: {}, 32 | } 33 | 34 | //------------------------------------------------------------------------------ 35 | //-- Command-Line Argument Parser -- 36 | //------------------------------------------------------------------------------ 37 | 38 | /**** parsePortNumber ****/ 39 | 40 | function parsePortNumber (Value) { 41 | Value = Value.trim() 42 | if (Value === '') { throw 'no given' } 43 | 44 | let PortNumber = parseInt(Value,10) 45 | if (isNaN(PortNumber)) { throw 'illegal given' } 46 | 47 | if ((PortNumber < 0) || (PortNumber > 65535)) { 48 | throw 'invalid given' 49 | } 50 | 51 | return PortNumber 52 | } 53 | 54 | /**** parsePBKDF2Iterations ****/ 55 | 56 | function parsePBKDF2Iterations (Value) { 57 | Value = Value.trim() 58 | if (Value === '') { throw 'no PBKDF2 iteration given' } 59 | 60 | let Count = parseInt(Value,10) 61 | if (isFinite(Count) && (Count > 0)) { 62 | return Count 63 | } else { 64 | throw 'invalid PBKDF2 iteration given' 65 | } 66 | } 67 | 68 | /**** parseDomain ****/ 69 | 70 | function parseDomain (Value) { 71 | Value = Value.trim() 72 | if (Value === '') { throw 'no given' } 73 | 74 | if (/^[a-z][a-z0-9]*([-.][a-z][a-z0-9]*)+$/i.test(Value)) { 75 | return Value 76 | } else { 77 | throw 'invalid given (' + Value + ')' 78 | } 79 | } 80 | 81 | /**** parseVirtualHosts ****/ 82 | 83 | function parseVirtualHosts (Value) { 84 | Value = Value.trim() 85 | if (Value === '') { return [] } 86 | 87 | let DomainList = Value 88 | .replace(/\s*,\s*/g,',') // eliminate white-space around ',' 89 | .replace(/,,+/g,',') // reduce multiple consecutive commas 90 | .split(',') // split at commas 91 | DomainList.forEach((Domain) => { 92 | if (/^[a-z][a-z0-9]*([-.][a-z][a-z0-9]*)+$/i.test(Domain)) { 93 | return Domain 94 | } else { 95 | throw 'invalid given (' + Domain + ')' 96 | } 97 | }) 98 | 99 | return DomainList 100 | } 101 | 102 | /**** parseTrustProxy ****/ 103 | 104 | function parseTrustProxy (Value) { 105 | Value = Value.trim() 106 | switch (Value) { 107 | case '': throw 'no given' 108 | case 'true': return true 109 | case 'false': return false 110 | default: 111 | if (/^[0-9]+$/.test(Value)) { 112 | return parseInt(Value,10) 113 | } else { 114 | return Value // no further validations here, leave it up to Express 115 | } 116 | } 117 | } 118 | 119 | let Arguments = require('commander'), RootFolder, ConfigFolder, LogFolder 120 | 121 | Arguments 122 | .version(Version, '-v, --version') 123 | .usage('[options] [file-root [configuration-folder [log-folder]]]') 124 | .option('--server-port ', 'server port number', parsePortNumber) 125 | .option('--redirection-port ', 'redirection port number', parsePortNumber) 126 | .option('--proxy ', 'trusted proxy', parseTrustProxy) 127 | .option('--domain ', '(primary) domain', parseDomain) 128 | .option('--virtual-hosts ', 'list of virtual hosts', parseVirtualHosts) 129 | .option('--allow-subdomains', 'map subdomains to subfolders') 130 | .option('--ignore-www', 'avoid separate "www." subdomain') 131 | .option('--cert-folder ','folder with server certificate files') 132 | .option('--pbkdf2-iterations ', 'PBKDF2 iteration count', parsePBKDF2Iterations) 133 | .option('--log-format ', 'morgan-compatible log format') 134 | .arguments(' [configuration-folder [log-folder]]') 135 | .action(function (FileRoot, ConfigRoot, LogRoot) { 136 | RootFolder = path.resolve(process.cwd(), FileRoot) 137 | ConfigFolder = ( ConfigRoot == null 138 | ? null 139 | : path.resolve(process.cwd(), ConfigFolder) 140 | ) 141 | LogFolder = ( LogRoot == null 142 | ? null 143 | : path.resolve(process.cwd(), LogFolder) 144 | ) 145 | }) 146 | .parse(process.argv) 147 | 148 | const Options = Arguments.opts() 149 | const ServerPort = Options.serverPort || 8443 150 | const RedirectionPort = Options.redirectionPort // no default! 151 | if (ServerPort === RedirectionPort) { 152 | throw 'Invalid Argument: and must not be identical' 153 | } 154 | const behindProxy = Options.proxy || false 155 | const primaryDomain = Options.domain 156 | const virtualHosts = Options.virtualHosts || [] 157 | const allowSubdomains = Options.allowSubdomains || false 158 | if (primaryDomain == null) { 159 | if (virtualHosts.length > 0) { 160 | throw '"--virtual-hosts" may only be used if a "--domain" is given' 161 | } 162 | 163 | if (allowSubdomains) { 164 | throw '"--allow-subdomains" may only be used if a "--domain" is given' 165 | } 166 | } 167 | 168 | if ((virtualHosts.length > 0) || allowSubdomains) { 169 | if ((primaryDomain != null) && (virtualHosts.indexOf(primaryDomain) < 0)) { 170 | virtualHosts.unshift(primaryDomain) 171 | } 172 | } 173 | const ignoreWWW = (Options.ignoreWww == null ? true : Options.ignoreWww) 174 | const PBKDF2Iterations = Options.pbkdf2Iterations || 100000 175 | const LogFormat = Options.logFormat || 'common' 176 | const FileRoot = RootFolder || path.join(process.cwd(),'public') 177 | const ConfigRoot = ConfigFolder || process.cwd() 178 | const LogRoot = LogFolder || path.join(process.cwd(),'logs') 179 | 180 | const CERTFolder = Options.certFolder || ( 181 | primaryDomain == null 182 | ? path.join(ConfigRoot,'certificates/localhost') 183 | : path.join(ConfigRoot,'certificates',primaryDomain) 184 | ) 185 | 186 | //------------------------------------------------------------------------------ 187 | //-- Helmet -- 188 | //------------------------------------------------------------------------------ 189 | 190 | const helmet = require('helmet') 191 | 192 | let ContentSecurityPolicies 193 | try { 194 | ContentSecurityPolicies = Object.assign(Object.create(null), JSON.parse( 195 | fs.readFileSync(path.join(ConfigRoot, 'ContentSecurityPolicies.json'), 'utf8') 196 | )) 197 | } catch (Signal) { 198 | console.error('could not load Content Security Policies',Signal) 199 | process.exit(1) 200 | } 201 | 202 | const configuredHelmet = helmet({ 203 | contentSecurityPolicy: { 204 | directives: { 205 | ...helmet.contentSecurityPolicy.getDefaultDirectives(), 206 | ...ContentSecurityPolicies 207 | } 208 | } 209 | }) 210 | 211 | //------------------------------------------------------------------------------ 212 | //-- Path Normalizer -- 213 | //------------------------------------------------------------------------------ 214 | 215 | let EditorDomain = primaryDomain || virtualHosts[0] || '' // Node-RED Editor 216 | 217 | function PathNormalizer (Request, Response, next) { 218 | let URLPath = url.parse(Request.url).pathname 219 | if (URLPath[0] !== '/') { URLPath = '/' + URLPath } 220 | 221 | let RootFolder = FileRoot 222 | if (virtualHosts.length > 0) { 223 | let virtualHost = Request.headers[':authority'] || Request.headers.host 224 | if (behindProxy && (Request.headers['x-forwarded-host'] != null)) { 225 | virtualHost = Request.headers['x-forwarded-host'] 226 | } 227 | 228 | if (virtualHost == null) { // no virtual host given 229 | return Response.sendStatus(404) 230 | } else { 231 | virtualHost = virtualHost.replace(/:\d+$/,'') 232 | } 233 | 234 | Request.virtualHost = virtualHost; virtualHost = '' 235 | virtualHosts.forEach((Candidate) => { 236 | if ( 237 | (Request.virtualHost === Candidate) || 238 | allowSubdomains && Request.virtualHost.endsWith('.' + Candidate) 239 | ) { 240 | virtualHost = ( 241 | ignoreWWW && (Request.virtualHost === 'www.' + Candidate) 242 | ? Candidate 243 | : Request.virtualHost 244 | ) 245 | } 246 | }) 247 | if (virtualHost === '') { // the given virtual host is not supported 248 | return Response.sendStatus(404) 249 | } 250 | 251 | /**** Node-RED Editor does not like URL normalization ****/ 252 | 253 | if ( 254 | URLPath.startsWith(REDSettings.httpAdminRoot) && 255 | (virtualHost === EditorDomain) 256 | ) { return next() } 257 | 258 | /**** but all other entry points have to ****/ 259 | 260 | RootFolder = path.join(FileRoot, virtualHost) // host-specific root folder 261 | URLPath = '/' + virtualHost + URLPath 262 | } 263 | 264 | /**** keep clients within their root folders ****/ 265 | 266 | let FilePath = path.normalize(path.join(FileRoot,URLPath)) 267 | if (FilePath.startsWith(RootFolder) && (FilePath !== RootFolder)) { 268 | Request.url = URLPath // poor hack for getting host-specific root folders 269 | } else { 270 | Response.sendStatus(404) 271 | } 272 | 273 | return next() 274 | } 275 | 276 | //------------------------------------------------------------------------------ 277 | //-- CORS -- 278 | //------------------------------------------------------------------------------ 279 | 280 | const cors = require('cors') 281 | 282 | let CORSRegistry 283 | try { 284 | CORSRegistry = JSON.parse( 285 | fs.readFileSync(path.join(ConfigRoot, 'sharedResources.json'), 'utf8') 286 | ) 287 | 288 | for (let i = 0, l = CORSRegistry.length ; i< l; i++) { 289 | let Rule = CORSRegistry[i] 290 | Rule.PathPattern = new RegExp(Rule.PathPattern) 291 | } 292 | } catch (Signal) { 293 | console.error('could not load CORS registry',Signal) 294 | process.exit(1) 295 | } 296 | 297 | function validateCORS (Request, CallBack) { 298 | let URLPath = url.parse(Request.url).pathname 299 | if (URLPath[0] !== '/') { URLPath = '/' + URLPath } 300 | 301 | let Origin = (Request.header('Origin') || '') // trim protocol and port 302 | .replace(/^[^:]+.\/\//,'').replace(/:\d+$/,'') 303 | 304 | for (let i = 0, l = CORSRegistry.length ; i < l; i++) { 305 | let Rule = CORSRegistry[i] 306 | if (Rule.PathPattern.test(URLPath)) { 307 | let allowSharing = ( 308 | (Rule.OriginList == null) || 309 | (Rule.OriginList.indexOf(Origin) >= 0) 310 | ) 311 | return CallBack(null,{ origin:allowSharing }) 312 | } 313 | } 314 | 315 | CallBack(null,{ origin:false }) 316 | } 317 | 318 | //------------------------------------------------------------------------------ 319 | //-- Authentication -- 320 | //------------------------------------------------------------------------------ 321 | 322 | const BasicAuth = require('basic-auth') 323 | 324 | let UserRegistry 325 | try { 326 | UserRegistry = Object.assign(Object.create(null), JSON.parse( 327 | fs.readFileSync(path.join(ConfigRoot, 'registeredUsers.json'), 'utf8') 328 | )) 329 | } catch (Signal) { 330 | console.error('could not load user registry',Signal) 331 | process.exit(1) 332 | } 333 | 334 | function authenticateUser (Request, Response, next) { 335 | function withAuthenticationFailure () { 336 | Response.set('WWW-Authenticate','Basic realm="' + Request.virtualHost + '"') 337 | return Response.sendStatus(401) 338 | } 339 | 340 | let Credentials = BasicAuth(Request) 341 | if (Credentials == null) { return withAuthenticationFailure() } 342 | 343 | let UserId = Credentials.name 344 | let Password = Credentials.pass 345 | 346 | if ( 347 | ! (UserId in UserRegistry) || (UserRegistry[UserId] == null) 348 | ) { return withAuthenticationFailure() } 349 | 350 | let UserSpecs = UserRegistry[UserId] 351 | if (UserSpecs.Password === Password) { // internal optimization 352 | Request.authenticatedUser = UserId 353 | return next(Request,Response) 354 | } 355 | 356 | crypto.pbkdf2( 357 | Password, Buffer.from(UserSpecs.Salt,'hex'), PBKDF2Iterations, 64, 'sha512', 358 | function (Error, computedHash) { 359 | if (Error == null) { 360 | if (computedHash.toString('hex') === UserSpecs.Hash) { 361 | UserSpecs.Password = Password // speeds up future auth. requests 362 | Request.authenticatedUser = UserId 363 | return next(Request,Response) 364 | } 365 | } 366 | return withAuthenticationFailure() 367 | } 368 | ) 369 | } 370 | 371 | //------------------------------------------------------------------------------ 372 | //-- Node-RED Protection -- 373 | //------------------------------------------------------------------------------ 374 | 375 | function protectNodeRED (Request, Response, next) { 376 | authenticateUser(Request, Response, (Request, Response) => { 377 | let authenticatedUser = Request.authenticatedUser 378 | let authorizedRoles = ( 379 | authenticatedUser == null ? null : UserRegistry[authenticatedUser].Roles 380 | ) 381 | if ( 382 | Array.isArray(authorizedRoles) && 383 | (authorizedRoles.indexOf('node-red') >= 0) 384 | ) { 385 | return next() 386 | } else { 387 | Response.set('WWW-Authenticate','Basic realm="' + Request.virtualHost + '"') 388 | return Response.sendStatus(401) 389 | } 390 | }) 391 | } 392 | 393 | //------------------------------------------------------------------------------ 394 | //-- static File Protection -- 395 | //------------------------------------------------------------------------------ 396 | 397 | let ProtectionRegistry 398 | try { 399 | ProtectionRegistry = Object.assign(Object.create(null), JSON.parse( 400 | fs.readFileSync(path.join(ConfigRoot, 'protectedFiles.json'), 'utf8') 401 | )) 402 | 403 | for (let PathPattern in ProtectionRegistry) { 404 | let UserList = ProtectionRegistry[PathPattern].trim().replace(/\s+/g,' ').split(' ') 405 | switch (UserList.length) { 406 | case 0: UserList = null; break 407 | case 1: if (UserList[0] === '*') { UserList = null }; break 408 | } 409 | 410 | ProtectionRegistry[PathPattern] = { 411 | PathPattern: new RegExp(PathPattern), 412 | permittedUsers:UserList 413 | } 414 | } 415 | } catch (Signal) { 416 | console.error('could not load file protection registry',Signal) 417 | process.exit(1) 418 | } 419 | 420 | function protectStaticFiles (Request, Response, next) { 421 | let FilePath = Request.url // has been normalized before 422 | 423 | for (let Entry in ProtectionRegistry) { 424 | if (ProtectionRegistry[Entry].PathPattern.test(FilePath)) { 425 | authenticateUser(Request, Response, (Request, Response) => { 426 | let authenticatedUser = Request.authenticatedUser 427 | let permittedUsers = ProtectionRegistry[Entry].permittedUsers 428 | switch (true) { 429 | case (permittedUsers == null): 430 | case (permittedUsers.indexOf(authenticatedUser) >= 0): 431 | return next() 432 | default: 433 | Response.set('WWW-Authenticate','Basic realm="' + Request.virtualHost + '"') 434 | return Response.sendStatus(401) 435 | } 436 | }) 437 | return 438 | } 439 | } 440 | 441 | return next() // path does not seem to be protected 442 | } 443 | 444 | //------------------------------------------------------------------------------ 445 | //-- static File Serving -- 446 | //------------------------------------------------------------------------------ 447 | 448 | const sendStaticFile = require('serve-static')( 449 | FileRoot, { 450 | index: ['index.html'], 451 | setHeaders: function (Response, Path /* FileStat */) { 452 | if (Path.endsWith('/manifest.json') || Path.endsWith('.manifest')) { 453 | Response.set('Content-Type', 'text/cache-manifest') 454 | } 455 | } 456 | } 457 | ) 458 | 459 | //------------------------------------------------------------------------------ 460 | //-- Logging -- 461 | //------------------------------------------------------------------------------ 462 | 463 | const morgan = require('morgan') 464 | 465 | //------------------------------------------------------------------------------ 466 | //-- common Server and Service Configuration -- 467 | //------------------------------------------------------------------------------ 468 | 469 | function configure (actualServer,actualService) { 470 | actualService.use(configuredHelmet) 471 | actualService.use(PathNormalizer) // validates paths, maps virtual hosts 472 | actualService.use(cors(validateCORS))//before(!) processing any entry points 473 | 474 | /**** Logging ****/ 475 | 476 | actualService.use(morgan(LogFormat, { 477 | stream:fs.createWriteStream( 478 | path.join(LogRoot,(primaryDomain || 'localhost') + '.log'), 479 | { flags:'a' } 480 | ) 481 | })) 482 | 483 | actualService.use(morgan( 484 | ':remote-addr :remote-user :method :url :status :res[content-length] - :response-time ms' 485 | )) 486 | 487 | /**** require basic authentication for Node-RED editor ****/ 488 | 489 | actualService.use(REDSettings.httpAdminRoot, protectNodeRED) 490 | 491 | /**** embed Node-RED ****/ 492 | 493 | REDSettings.userDir = path.join(ConfigRoot,REDSettings.userDir) 494 | REDSettings.functionGlobalContext = { 495 | ServerPort, RedirectionPort, behindProxy, 496 | primaryDomain, virtualHosts, allowSubdomains, PBKDF2Iterations, 497 | FileRoot, ConfigRoot, LogRoot, 498 | CORSRegistry, UserRegistry, ProtectionRegistry, 499 | functionExternalModules:true 500 | } 501 | 502 | console.log('Node-RED Settings:',REDSettings) 503 | 504 | RED.init(actualServer,REDSettings) 505 | 506 | actualService.use(REDSettings.httpNodeRoot, RED.httpNode) // for "HTTP in" 507 | actualService.use(REDSettings.httpAdminRoot,RED.httpAdmin) // for the Editor 508 | 509 | /**** require basic authentication for protected static files ****/ 510 | 511 | actualService.get('*', protectStaticFiles) 512 | 513 | /**** paths not caught by Node-RED could be static files ****/ 514 | 515 | actualService.get('*',sendStaticFile) 516 | 517 | /**** the following middleware should never be invoked - but who knows? ****/ 518 | 519 | actualService.all('*', (Request, Response) => { 520 | Response.sendStatus(404) 521 | }) 522 | } 523 | 524 | //------------------------------------------------------------------------------ 525 | //-- actual Server -- 526 | //------------------------------------------------------------------------------ 527 | 528 | console.clear() 529 | 530 | const KeyFilePath = path.join(CERTFolder,'privkey.pem') 531 | if (! fs.existsSync(KeyFilePath)) { 532 | console.error('no key file at "' + KeyFilePath + '"') 533 | process.exit(1) 534 | } 535 | 536 | const CERTFilePath = path.join(CERTFolder,'fullchain.pem') 537 | if (! fs.existsSync(CERTFilePath)) { 538 | console.error('no cert file at "' + CERTFilePath + '"') 539 | process.exit(1) 540 | } 541 | 542 | const safeService = express() 543 | const safeServer = https.createServer({ 544 | key: fs.readFileSync(KeyFilePath), 545 | cert: fs.readFileSync(CERTFilePath) 546 | }, safeService) 547 | 548 | if (behindProxy) { safeService.set('trust proxy',behindProxy) } 549 | 550 | configure(safeServer,safeService) 551 | 552 | let ServerInstance = safeServer.listen(ServerPort, function () { 553 | let ServerAddress = ServerInstance.address() 554 | console.log('HTTPS Server:') 555 | console.log('- listening at ' + ServerAddress.address + ':' + ServerAddress.port) 556 | console.log('- serving from ' + FileRoot + ')') 557 | }) 558 | 559 | let RedirectionService 560 | if (RedirectionPort != null) { 561 | RedirectionService = express() 562 | if (behindProxy) { safeService.set('trust proxy',behindProxy) } 563 | 564 | RedirectionService.all('*',function Redirector (Request, Response) { 565 | let virtualHost = Request.headers[':authority'] || Request.headers.host 566 | if (behindProxy && (Request.headers['x-forwarded-host'] != null)) { 567 | virtualHost = Request.headers['x-forwarded-host'] 568 | } 569 | 570 | if (virtualHost == null) { 571 | Response.sendStatus(404) 572 | } else { 573 | virtualHost = virtualHost.replace(/:\d+$/,'') 574 | Response.redirect('https://' + virtualHost + ':' + ServerPort + Request.url) 575 | } 576 | }) 577 | let RedirectionInstance = RedirectionService.listen(RedirectionPort, function () { 578 | let ServerAddress = RedirectionInstance.address() 579 | console.log('HTTP Redirection Server:') 580 | console.log('- listening at ' + ServerAddress.address + ':' + ServerAddress.port) 581 | console.log('- serving from ' + FileRoot + ')') 582 | }) 583 | } 584 | 585 | RED.start() 586 | 587 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-red-within-express # 2 | 3 | This repository contains an HTTP(S) server based on [Node.js](https://nodejs.org/en/) with [Express.js](http://expressjs.com/) including an embedded [Node-RED](https://nodered.org/) instance. 4 | 5 | Its intended purpose is to provide a very easily maintainable server for development and test of web and REST service prototypes. 6 | 7 | > Just a small note: if you like this work and plan to use it, consider "starring" this repository (you will find the "Star" button on the top right of this page), so that I know which of my repositories to take most care of. 8 | 9 | ## Features ## 10 | 11 | The implemented server has the following features: 12 | 13 | * **HTTPS with optional HTTP-to-HTTPS Redirection**
the main server handles HTTPS only as it is becoming increasingly difficult to deliver pure HTTP content to browsers (even locally). If desired, an additional auxiliary HTTP server may be started which redirects incoming requests to its HTTPS counterpart 14 | * **Proxy Support** 15 | * **Support for "self-signed" or "Let's Encrypt" Certificates**
for local tests, it may be sufficient to generate self-signed certificates (instructions can be found below). For public tests, the server also supports certificates generated by ["Let's Encrypt"](https://letsencrypt.org/) 16 | * **Support for "virtual Hosts" and Subdomains**
the server may optionally support "virtual hosts" and serve multiple domains (including subdomains) simultaneously. In this case, each domain will be mapped to an individual file system subtree in order to isolate the domains from each other 17 | * **"www" Subdomains**
if desired, "www" subdomains can be mapped to their original domain (since they usually serve the same content anyway) 18 | * **embedded Node-RED runtime**
incoming requests will first be compared to the entry points given by "HTTP in" nodes - and their flows be executed whenever the URL paths match (if "virtual hosts" are to be respected, all these entry points become domain-specific and their paths must therefore be prefixed by the domain they belong to). Requests not matching any "HTTP in" node entry points will then be used to serve static files from the file system (or generate a 404 response if no matching file could be found) 19 | * **embedded Node-RED editor**
the embedded Node-RED editor is generally protected by "basic HTTP authentication": for that purpose, the server always comes with a "User Registry" which already contains a single user (named "node-red" with the initial password "t0pS3cr3t!") who is allowed to access the Node-RED editor 20 | * **Path-specific static File Protection**
most static files on this server may be available to the public - but perhaps not all of them. For that purpose, this server allows to specify which files should only be available to specific users 21 | * **User Registry with PBKDF2 hashed Passwords and Role Support**
the list of registered users is stored in a JSON file with passwords saved as PBKDF2 hashes with random salt. While the server itself does not contain any user management, such a feature may easily be added as a Node-RED flow - although, in fact, a simple text editor is already sufficient to add new users, change existing ones or remove obsolete users 22 | * **Path-specific CORS**
"Cross-Origin Resource Sharing" may be configured for complete sites as a whole or for specific resource paths with any desired granularity 23 | * **configurable "Content Security Policies"**
the server is secured using [Helmet](https://github.com/helmetjs/helmet) with a configuration option for specific "Content Security Policies" 24 | * **standard-compliant Logging**
access logging is done using [morgan](https://expressjs.com/en/resources/middleware/morgan.html). Logs may be written into a file either in "standard Apache common log format" or any other format 25 | 26 | ## Installation and Use ## 27 | 28 | You may easily install and run this server on your machine. 29 | 30 | Just install [NPM](https://docs.npmjs.com/) according to the instructions for your platform and follow these steps: 31 | 32 | 1. either clone this repository using [git](https://git-scm.com/) or [download a ZIP archive](https://github.com/rozek/node-red-within-express/archive/refs/heads/main.zip) with its contents to your disk and unpack it there 33 | 2. open a shell and navigate to the root directory of this repository 34 | 3. run `npm install` in order to install the server 35 | 36 | ### Preparing the first Start ### 37 | 38 | For a quick start, the server comes preconfigured for two different use cases: 39 | 40 | * ***without* virtual hosts processing**
this variant does not require much preparation and is ideal for initial experiments 41 | * ***with* virtual hosts processing**
this variant requires a bit of preparational work but may be used to test installations serving multiple domains 42 | 43 | #### Preparing a Server *without* virtual Hosts Processing #### 44 | 45 | Using HTTPS to access servers with self-signed certificates cause warnings in most browsers, other tools may even refuse to work. In many browsers, it is sufficient to follow the presented instructions and accept the "malicious" certificate against all warnings (which is safe, since you created the certificate yourself). For other tools, it may be better to add the certificate to the system's list of trusted certificates. 46 | 47 | You will have to accept the certificate for `localhost` when you start your server and navigate to one of its pages for the first time. However, the configured exception will only last for a few minutes and will then have to be repeated. 48 | 49 | > Chromium-based browsers (such as Chrome or Microsoft Edge) offer a special flag which automatically accepts certificates for `localhost`: just enter `chrome://flags/#allow-insecure-localhost` (or `edge://flags/#allow-insecure-localhost`) into the browser's address list, press Enter and activate the flag on the page you will be shown 50 | 51 | #### Preparing a Server *with* virtual Hosts Processing #### 52 | 53 | Using HTTPS to access servers with self-signed certificates cause warnings in most browsers, other tools may even refuse to work. In many browsers, it is sufficient to follow the presented instructions and accept the "malicious" certificate against all warnings (which is safe, since you created the certificate yourself). For other tools, it may be better to add the certificate to the system's list of trusted certificates. 54 | 55 | You will have to accept the certificate for `local-server.org` when you start your server and navigate to one of its pages for the first time. However, the configured exception will only last for a few minutes and will then have to be repeated. 56 | 57 | In order to be able to directly navigate to `localserver.org` and its subdomains, you will have to append the following entries to the file `/etc/hosts` (under Windows, this file is found at `%windir%\system32\drivers\etc\hosts`): 58 | 59 | ``` 60 | 127.0.0.1 local-server.org 61 | 127.0.0.1 www.local-server.org 62 | 127.0.0.1 webapp.local-server.org 63 | ``` 64 | 65 | Any text editor will do the job. 66 | 67 | ### First Experiments ### 68 | 69 | On UNIX-like systems, the available start scripts should be made executable in order to simplify their invocation: 70 | 71 | ``` 72 | chmod +x startServer* 73 | ``` 74 | 75 | Under Windows, the first line of these scripts (the "shebang" line) may have to be deleted instead. 76 | 77 | Now you are ready to start your server: within a terminal window, navigate to the folder containing this repository and either enter 78 | 79 | ``` 80 | ./startServerWithDomains 81 | ``` 82 | 83 | or 84 | 85 | ``` 86 | ./startServerWithoutDomains 87 | ``` 88 | 89 | depending on the scenario you want to test. 90 | 91 | As soon as the server has started up (it reports this process on stdout) you may use a browser of your choice and navigate to `localhost:8443` or `local-server.org:8443`, resp. As a result, the browser should show you a simple web page with a small image and the server should have logged two requests on the console. This experiment proves that static files are delivered properly 92 | 93 | Go further and navigate to `localhost:8443/hello-world` (or `local-server.org:8443/hello-world`, resp.). The browser should now show the text `Hello, World!` - a text that has been generated by Node-RED. 94 | 95 | Next, navigate to `localhost:8443/show-request` (or `local-server.org:8443/show-request`, resp.). The browser should now show the text `(see Node-RED debug console)` which reminds you to open the Node-RED editor and have a look into its debug window. 96 | 97 | Thus, open a new tab in your browser and navigate to `localhost:8443/.Node-RED/Editor` (or `local-server.org:8443/.Node-RED/Editor`, resp.). Instead of showing anything, your browser should first ask you to authenticate yourself: enter `node-red` as your user name and `t0pS3cr3t!` as your password (exactly as shown here). If you mistyped anything, the browser will present the authentication dialog again. Otherwise, the Node-RED editor will be opened and you will see a workspace with the initial set of flows that came with this server. 98 | 99 | Open the debug output, switch back to the previous browser tab and reload the page you requested before (either `localhost:8443/show-request` or `local-server.org:8443/show-request`). Switch back to the Node-RED editor and inspect the debug window: you should now be able to inspect the request your browser sent to the server. 100 | 101 | You are welcome to test that `show-request` with the HTTP methods `PUT`, `POST`, `PATCH` and `DELETE` as well - e.g., using tools like [cURL](https://curl.se/) or [Postman](https://www.postman.com/). 102 | 103 | As usual, all requests should have been logged both on the console and in a file called `localhost.log` (or `local-server.log`, resp.) within subfolder `logs`. 104 | 105 | ## Invocation Parameters ## 106 | 107 | The server in this repo has been implemented as a Node.js script and can be invoked as follows 108 | 109 | ``` 110 | node WebServer.js [options] [ []] 111 | ``` 112 | 113 | with the following arguments: 114 | 115 | * **``**
specifies the root folder (relative to the server's current working directory) of all deliverable static files. By default, this is a subfolder of the current working directory called `public` 116 | * **``**
specifies the folder (relative to the server's current working directory) where configuration files (such as the list of registered users) are found. By default, this is the current working directory itself 117 | * **``**
specifies the folder (relative to the server's current working directory) into which the log file is written. By default, this is a subfolder of the current working directory called `logs` 118 | 119 | The following options are supported: 120 | 121 | * **`--server-port `**
specifies the TCP port at which to listen for incoming HTTPS requests. The default is `8443` 122 | * **`--redirection-port `**
if provided, this option activates HTTP-to-HTTPS redirection and specifies the TCP port at which to listen for incoming HTTP requests 123 | * **`--proxy `**
activates and configures proxy support. Consider the [Express.js documentation](https://expressjs.com/en/guide/behind-proxies.html) for a list and explanation of actually allowed values 124 | * **`--domain `**
specifies the primary domain of this server. It should be the "common name" (CN) of the associated server certificate and also appears in the log file name. If virtual hosts are given as well (even if the list is empty), the primary domain is automatically added to that list 125 | * **`--virtual-hosts `**
activates virtual hosts processing and configures the domains to handle. The given argument may either be an empty string (`""`) or a string containing a comma-separated list of internet domains. All mentioned domains should also be specified as "subject alternative names" (SAN) in the server certificate 126 | * **`--allow-subdomains`**
if specified, all subdomains of the given primary domain and virtual hosts are processed as well. In this case, the server certificate should also contain "subject alternative names" (SAN) with wildcards of the form `*.` 127 | * **`--ignore-www`**
if specified, subdomains of the form `www.` are not treated as a separate subdomain but mapped to their main `` 128 | * **`--cert-folder `**
specifies the folder where to find server certificates. By default, this is a subfolder of the server's current working directory called `certificates` 129 | * **`--pbkdf2-iterations `**
specifies the number of iterations when computing PBKDF2 hashes. Default is 100000 130 | * **`--log-format `**
specifies the format in which log entries are written into a file. Consider the [morgan documentation](https://expressjs.com/en/resources/middleware/morgan.html) for a list and explanation of permitted settings. Default is `common` 131 | 132 | If everything works well, the server reports its start-up and logs all incoming requests on `stdout` 133 | 134 | ### Configuring Domains and Virtual Hosts ### 135 | 136 | On a "production system", usually no special precautions need to be taken to run this server. 137 | 138 | Synthetic tests with virtual hosts on a local machine, however, should be prepared as follows: 139 | 140 | * generate self-signed certificates for all domains under test:
folder `certificates` contains a file `local-server.org.cnf` which can be used for that purpose 141 | * copy this file and name the copy `.cnf` where "<primary-domain>" should be replaced by the name of your primary domain 142 | * create a subfolder with the name of your primary domain 143 | * open `.cnf` in a text editor of your choice 144 | * replace `local-server.org` with your primary domain (both behind `CN =` and `DNS.1 =`) 145 | * append additional domain names as further `DNS.#` entries at your will 146 | * save this file and run the following command
`openssl req -x509 -nodes -newkey rsa:4096 \`
`-keyout /privkey.pem \`
`-out /fullchain.pem \`
`-days 3650 -config .cnf`
(again, after replacing "<primary-domain>" with the name of your primary domain) 147 | * append an entry for each desired domain to `/etc/hosts`. Each entry must have the form
    `127.0.0.1 `
wildcards are not allowed 148 | * modify the script `startServerWithDomain` by replacing `local-server.org` with the name of your primary domain and - if need be - adding a `--virtual-hosts` option with a comma-separted list of additional domain names (subdomains of your primary domain do not have to be mentioned explicitly, the option `--allow-subdomains` already covers them) 149 | 150 | You may now run `startServerWithDomain` and navigate your browser to any of the configured domains (don't forget to specify your server's port number unless it is a standard one) 151 | 152 | ## Embedded Node-RED Instance ## 153 | 154 | The embedded Node-RED instance comes with two sets of flows for initial "smoke tests": one for a server *with* virtual host processing and one *without*. 155 | A GET request to `/hello-world` simply responds with a "Hello, World!" message, GET, PUT, POST, PATCH and DELETE requests to `show-request` dump the contents of any incoming message to the Node-RED debug window. 156 | 157 | ![initial Flow Sets](initialFlows.png) 158 | 159 | Both flow sets are welcome to be removed and replaced with more meaningful ones. 160 | 161 | For your own flows, the following server parameters are copied into the global context: 162 | 163 | * **`ServerPort`**
contains the port number at which this server listens for incoming requests 164 | * **`RedirectionPort`**
is either `undefined` or contains the port number of the redirection server 165 | * **`behindProxy`**
is either `false` or contains the setting given in command option `--proxy` 166 | * **`primaryDomain`**
is either `undefined` or contains the primary domain name for this server 167 | * **`virtualHosts`**
contains a (potentially empty) JavaScript array with the names of all domains handled by this server 168 | * **`allowSubdomains`**
is `true` if subdomains are allowed or `false` otherwise 169 | * **`PBKDF2Iterations`**
contains the number of PBKDF2 iterations calculated when generating password hashes 170 | * **`FileRoot`**
contains the absolute path of the root folder containing all static files which may be delivered by this server 171 | * **`ConfigRoot`**
contains the absolute path of the folder containing any server configuration files 172 | * **`LogRoot`**
contains the absolute path of the folder into which logs are written 173 | * **`CORSRegistry`**
contains the JavaScript array with all CORS rules. The array is copied "by reference" which means that changes in this array immediately affect the server itself (no server restart required) 174 | * **`UserRegistry`**
contains the JavaScript object with all registered users. The object is copied "by reference" which means that changes in this object immediately affect the server itself (no server restart required) 175 | 176 | ## User Registry ## 177 | 178 | The server comes with a file `registeredUsers.json` which contains all "registered users" of this server. 179 | Initially, it contains a single user named "node-red" with password "t0pS3cr3t!" who is needed to access the embedded Node-RED editor. 180 | New users may be added and existing users changed or deleted at will with a simple text editor. 181 | 182 | `registeredUsers.json` contains the JSON serialization of a JavaScript object with the following format: 183 | 184 | * the object's property names are the ids of registered users
user ids have no specific format, they may be user names, email addresses or any other data you are free to choose - with **two important exceptions: user ids must neither contain any slashes ("/") nor any colons (":")** or some authentication or user management mechanisms (like those described in [node-red-authorization-examples](https://github.com/rozek/node-red-authorization-examples) or in the [node-red-user-management-example](https://github.com/rozek/node-red-user-management-example)) may fail 185 | * the object's property values are JavaScript objects with the following properties, at least (additional properties may be added at will): 186 | * **Roles**
is either missing or contains a list of strings with the user's roles. There is no specific format for role names - just the role `node-red` has a special meaning: users with this role are allowed to access the embedded Node-RED editor 187 | * **Salt**
contains a random "salt" value which is used during PBKDF2 password hash calculation 188 | * **Hash**
contains the actual PBKDF2 hash of the user's password 189 | 190 | The server will not start if file `registeredUsers.json` is missing or does not have valid JSON content. 191 | 192 | Users without proper `Salt` and `Hash` values can not authenticate themselves. Those without role `node-red` can not access the embedded Node-RED editor 193 | 194 | > Nota bene: Node-RED *flows* work independent of the embedded editor's accessability! 195 | 196 | ### Generating "Salt" and "Hash" ### 197 | 198 | This repo also contains a small utility called `generateSaltAndHash` which may be used to generate the "Salt" and "Hash" values for entries in the user registry. 199 | 200 | From within the folder containing the repository, it is invoked with 201 | 202 | ``` 203 | ./generateSaltAndHash 204 | ``` 205 | 206 | The script will ask for a password and - as soon as the password has been entered - generate salt and hash values and display them on the console. From there, these values may be copied into the clipboard and added to `registeredUsers.json`. 207 | 208 | By default, `generateSaltAndHash` assumes a PBKDF2 iteration count of 100000. If another count is desired, the utility should be invoked with 209 | 210 | ``` 211 | ./generateSaltAndHash pbkdf2-iterations 212 | ``` 213 | 214 | ## Static File Protection ## 215 | 216 | By default, all static files are considered "public", i.e. available to any visitor without prior authentication. 217 | 218 | If desired, however, it is possible to specify, which files should only be available to specific users (who then have to authenticate themselves before they are allowed to access these protected files). Such rules can be specified in file `protectedFiles.json` which is found in the configured ``. 219 | 220 | This file contains the JSON serialization of a JavaScript object with the following format: 221 | 222 | * the object's property names are regular expression (RegExp) patterns which are compared against any incoming URL path (including the domain name, if virtual hosts are to be processed) 223 | * the object's property values are strings which either contain a single asterisk ("`*`") indicating that any authenticated user may access, or a blank separated list with the names of those users who are allowed to access the matching file. 224 | 225 | The example that comes with this server protects all files and folders whose names start with a dot (".") and (in the case of protected folders) their contents. 226 | 227 | The server will not start if file `protectedFiles.json` is missing or does not have valid JSON content. 228 | 229 | ## CORS Support ## 230 | 231 | "Cross-Origin Resource Sharing" ([CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)) instructs browsers to restrict resource access to specific domains. For this server, CORS behaviour can be specified in file `sharedResources.json` which is found in the configured ``. 232 | 233 | This file contains the JSON serialization of a JavaScript array containing objects with the following format: 234 | 235 | * property `PathPattern` contains a regular expression (RegExp) pattern which is compared against any incoming URL path (including the domain name, if virtual hosts are to be processed) 236 | * property `OriginList` is either `null` (which acts as a placeholder allowing unrestricted resource sharing) or a JavaScript array containing the names (without protocols or port numbers) of all domains which are allowed to request the matching resource (whereas any domain not mentioned in this list is not allowed to access the resource) 237 | 238 | Without any entries, browser settings apply (which are often quite restrictive) 239 | 240 | The server will not start if file `sharedResources.json` is missing or does not have valid JSON content. 241 | 242 | ## Content Security Policies ## 243 | 244 | "Content Security Policies" (CSPs) are a method for web servers to ask browsers for imposing certain restrictions when the delivered page is shown (and contained scripts executed). Carefully crafted, CSPs may reduce the effect of Cross-Site Scripting (XSS) and data injection attacks. Sloppyly chosen, the delivered web page will just not work as intended. 245 | 246 | Consideration of CSPs requires a modern browser (as this function is actually provided by the browser) and should always be tested with an eye on the browser's console (where CSP violations will be reported) 247 | 248 | > European users may also use CSPs as another aid for guaranteeing the GDPR (or DSGVO) compliance of their web sites by permitting web pages to access only servers which are known for their GDPR compliance, and inhibiting access to any other unknown ones. 249 | 250 | For this server, "Content Security Policies" can be entered in a file called `ContentSecurityPolicies.json` which is found in the configured ``. For technical reasons, the given CSPs apply to *all* delivered web pages and can not be specified per page. 251 | 252 | This file contains the JSON serialization of a JavaScript object with the following format: 253 | 254 | * the object's property names are "Content Security Policies" (CSP) "directives"
see [the documentation at MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) for a list and explanation of available directives 255 | * the object's property values are JavaScript arrays containing a list of policies for the given directive (see [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src) for a list and explanation of such policies) 256 | 257 | The server will not start if file `ContentSecurityPolicies.json` is missing or does not have valid JSON content. 258 | 259 | ## Logging ## 260 | 261 | The server uses [morgan](https://expressjs.com/en/resources/middleware/morgan.html) for the formatting of log entries. 262 | 263 | Two logs are written: a simplified one (which allows to monitor server operation) is written to `stdout`, an extended one into a file named after the configured primary domain (or `localhost`) within the configured ``. 264 | 265 | Unless this has been changed, the larger log file follows the "standard Apache common log format" and may be processed using standard log file analysis tools. Other log format may be configured with command option `--log-format ` 266 | 267 | ## License ## 268 | 269 | [MIT License](LICENSE.md) 270 | --------------------------------------------------------------------------------