├── 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 | 
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 |
--------------------------------------------------------------------------------