mastercontroller 1.2.4 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MasterControl.js +146 -5
- package/README.md +58 -0
- package/docs/environment-tls-reference.md +60 -0
- package/docs/server-setup-hostname-binding.md +24 -0
- package/docs/server-setup-http.md +32 -0
- package/docs/server-setup-https-credentials.md +32 -0
- package/docs/server-setup-https-env-tls-sni.md +62 -0
- package/docs/server-setup-nginx-reverse-proxy.md +46 -0
- package/package.json +1 -1
package/MasterControl.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// MasterControl - by Alexander rich
|
|
2
|
-
// version 1.0.
|
|
2
|
+
// version 1.0.246
|
|
3
3
|
|
|
4
4
|
var url = require('url');
|
|
5
5
|
var fileserver = require('fs');
|
|
6
6
|
var http = require('http');
|
|
7
7
|
var https = require('https');
|
|
8
|
+
var tls = require('tls');
|
|
8
9
|
var fs = require('fs');
|
|
9
10
|
var url = require('url');
|
|
10
11
|
var path = require('path');
|
|
@@ -20,6 +21,8 @@ class MasterControl {
|
|
|
20
21
|
_serverProtocol = null
|
|
21
22
|
_scopedList = []
|
|
22
23
|
_loadedFunc = null
|
|
24
|
+
_tlsOptions = null
|
|
25
|
+
_hstsEnabled = false
|
|
23
26
|
|
|
24
27
|
#loadTransientListClasses(name, params){
|
|
25
28
|
Object.defineProperty(this.requestList, name, {
|
|
@@ -169,11 +172,12 @@ class MasterControl {
|
|
|
169
172
|
|
|
170
173
|
if(settings.httpPort || settings.requestTimeout){
|
|
171
174
|
this.server.timeout = settings.requestTimeout;
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
var host = settings.hostname || settings.host || settings.http;
|
|
176
|
+
if(host){
|
|
177
|
+
this.server.listen(settings.httpPort, host);
|
|
174
178
|
}else{
|
|
175
|
-
this.server.listen(settings.httpPort);
|
|
176
|
-
}
|
|
179
|
+
this.server.listen(settings.httpPort);
|
|
180
|
+
}
|
|
177
181
|
}
|
|
178
182
|
else{
|
|
179
183
|
throw "HTTP, HTTPS, HTTPPORT and REQUEST TIMEOUT MISSING";
|
|
@@ -201,7 +205,16 @@ class MasterControl {
|
|
|
201
205
|
}
|
|
202
206
|
if(type === "https"){
|
|
203
207
|
$that.serverProtocol = "https";
|
|
208
|
+
// Initialize TLS from env if no credentials passed
|
|
209
|
+
if(!credentials){
|
|
210
|
+
$that._initializeTlsFromEnv();
|
|
211
|
+
credentials = $that._tlsOptions;
|
|
212
|
+
}
|
|
213
|
+
// Apply secure defaults if missing
|
|
204
214
|
if(credentials){
|
|
215
|
+
if(!credentials.minVersion){ credentials.minVersion = 'TLSv1.2'; }
|
|
216
|
+
if(credentials.honorCipherOrder === undefined){ credentials.honorCipherOrder = true; }
|
|
217
|
+
if(!credentials.ALPNProtocols){ credentials.ALPNProtocols = ['h2', 'http/1.1']; }
|
|
205
218
|
return https.createServer(credentials, async function(req, res) {
|
|
206
219
|
$that.serverRun(req, res);
|
|
207
220
|
});
|
|
@@ -216,6 +229,130 @@ class MasterControl {
|
|
|
216
229
|
}
|
|
217
230
|
}
|
|
218
231
|
|
|
232
|
+
// Creates an HTTP server that 301-redirects to HTTPS counterpart
|
|
233
|
+
startHttpToHttpsRedirect(redirectPort, bindHost){
|
|
234
|
+
var $that = this;
|
|
235
|
+
return http.createServer(function (req, res) {
|
|
236
|
+
try{
|
|
237
|
+
var host = req.headers['host'] || '';
|
|
238
|
+
// Force original host, just change scheme
|
|
239
|
+
var location = 'https://' + host + req.url;
|
|
240
|
+
res.statusCode = 301;
|
|
241
|
+
res.setHeader('Location', location);
|
|
242
|
+
res.end();
|
|
243
|
+
}catch(e){
|
|
244
|
+
res.statusCode = 500;
|
|
245
|
+
res.end();
|
|
246
|
+
}
|
|
247
|
+
}).listen(redirectPort, bindHost);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Load TLS configuration from env and build SNI contexts with live reload
|
|
251
|
+
_initializeTlsFromEnv(){
|
|
252
|
+
try{
|
|
253
|
+
var cfg = this.env;
|
|
254
|
+
if(!cfg || !cfg.server || !cfg.server.tls){
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
var tlsCfg = cfg.server.tls;
|
|
258
|
+
|
|
259
|
+
var defaultCreds = this._buildSecureContextFromPaths(tlsCfg.default);
|
|
260
|
+
var defaultContext = defaultCreds ? tls.createSecureContext(defaultCreds) : null;
|
|
261
|
+
|
|
262
|
+
var sniMap = {};
|
|
263
|
+
if(tlsCfg.sni && typeof tlsCfg.sni === 'object'){
|
|
264
|
+
for (var domain in tlsCfg.sni){
|
|
265
|
+
if (Object.prototype.hasOwnProperty.call(tlsCfg.sni, domain)){
|
|
266
|
+
var domCreds = this._buildSecureContextFromPaths(tlsCfg.sni[domain]);
|
|
267
|
+
if(domCreds){
|
|
268
|
+
sniMap[domain] = tls.createSecureContext(domCreds);
|
|
269
|
+
// watch domain certs for reload
|
|
270
|
+
this._watchTlsFilesAndReload(tlsCfg.sni[domain], function(){
|
|
271
|
+
try{
|
|
272
|
+
var updated = tls.createSecureContext(
|
|
273
|
+
this._buildSecureContextFromPaths(tlsCfg.sni[domain])
|
|
274
|
+
);
|
|
275
|
+
sniMap[domain] = updated;
|
|
276
|
+
}catch(e){
|
|
277
|
+
console.error('Failed to reload TLS context for domain', domain, e);
|
|
278
|
+
}
|
|
279
|
+
}.bind(this));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
var options = defaultCreds ? Object.assign({}, defaultCreds) : {};
|
|
286
|
+
options.SNICallback = function(servername, cb){
|
|
287
|
+
var ctx = sniMap[servername];
|
|
288
|
+
if(!ctx && defaultContext){ ctx = defaultContext; }
|
|
289
|
+
if(cb){ return cb(null, ctx); }
|
|
290
|
+
return ctx;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// Apply top-level TLS defaults/hardening from env if provided
|
|
294
|
+
if(tlsCfg.minVersion){ options.minVersion = tlsCfg.minVersion; }
|
|
295
|
+
if(tlsCfg.honorCipherOrder !== undefined){ options.honorCipherOrder = tlsCfg.honorCipherOrder; }
|
|
296
|
+
if(tlsCfg.ciphers){ options.ciphers = tlsCfg.ciphers; }
|
|
297
|
+
if(tlsCfg.alpnProtocols){ options.ALPNProtocols = tlsCfg.alpnProtocols; }
|
|
298
|
+
|
|
299
|
+
// HSTS
|
|
300
|
+
this._hstsEnabled = !!tlsCfg.hsts;
|
|
301
|
+
this._hstsMaxAge = tlsCfg.hstsMaxAge || 15552000; // 180 days by default
|
|
302
|
+
|
|
303
|
+
// Watch default certs for reload
|
|
304
|
+
if(tlsCfg.default){
|
|
305
|
+
this._watchTlsFilesAndReload(tlsCfg.default, function(){
|
|
306
|
+
try{
|
|
307
|
+
var updatedCreds = this._buildSecureContextFromPaths(tlsCfg.default);
|
|
308
|
+
defaultContext = tls.createSecureContext(updatedCreds);
|
|
309
|
+
// keep key/cert on options for non-SNI connections
|
|
310
|
+
Object.assign(options, updatedCreds);
|
|
311
|
+
}catch(e){
|
|
312
|
+
console.error('Failed to reload default TLS context', e);
|
|
313
|
+
}
|
|
314
|
+
}.bind(this));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
this._tlsOptions = options;
|
|
318
|
+
}catch(e){
|
|
319
|
+
console.error('Failed to initialize TLS from env', e);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
_buildSecureContextFromPaths(desc){
|
|
324
|
+
if(!desc){ return null; }
|
|
325
|
+
var opts = {};
|
|
326
|
+
try{
|
|
327
|
+
if(desc.keyPath){ opts.key = fs.readFileSync(desc.keyPath); }
|
|
328
|
+
if(desc.certPath){ opts.cert = fs.readFileSync(desc.certPath); }
|
|
329
|
+
if(desc.caPath){ opts.ca = fs.readFileSync(desc.caPath); }
|
|
330
|
+
if(desc.pfxPath){ opts.pfx = fs.readFileSync(desc.pfxPath); }
|
|
331
|
+
if(desc.passphrase){ opts.passphrase = desc.passphrase; }
|
|
332
|
+
return opts;
|
|
333
|
+
}catch(e){
|
|
334
|
+
console.error('Failed to read TLS files', e);
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
_watchTlsFilesAndReload(desc, onChange){
|
|
340
|
+
var paths = [];
|
|
341
|
+
if(desc.keyPath){ paths.push(desc.keyPath); }
|
|
342
|
+
if(desc.certPath){ paths.push(desc.certPath); }
|
|
343
|
+
if(desc.caPath){ paths.push(desc.caPath); }
|
|
344
|
+
if(desc.pfxPath){ paths.push(desc.pfxPath); }
|
|
345
|
+
paths.forEach(function(p){
|
|
346
|
+
try{
|
|
347
|
+
fs.watchFile(p, { interval: 5000 }, function(){
|
|
348
|
+
onChange();
|
|
349
|
+
});
|
|
350
|
+
}catch(e){
|
|
351
|
+
console.error('Failed to watch TLS file', p, e);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
219
356
|
async serverRun(req, res){
|
|
220
357
|
var $that = this;
|
|
221
358
|
console.log("path", `${req.method} ${req.url}`);
|
|
@@ -271,6 +408,10 @@ class MasterControl {
|
|
|
271
408
|
if(ext === ""){
|
|
272
409
|
var requestObject = await this.middleware(req, res);
|
|
273
410
|
if(requestObject !== -1){
|
|
411
|
+
// HSTS header if enabled
|
|
412
|
+
if(this.serverProtocol === 'https' && this._hstsEnabled){
|
|
413
|
+
res.setHeader('strict-transport-security', `max-age=${this._hstsMaxAge}; includeSubDomains`);
|
|
414
|
+
}
|
|
274
415
|
var loadedDone = false;
|
|
275
416
|
if (typeof $that._loadedFunc === 'function') {
|
|
276
417
|
loadedDone = $that._loadedFunc(requestObject);
|
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
## MasterController Framework
|
|
2
|
+
|
|
3
|
+
MasterController is a lightweight MVC-style server framework for Node.js with routing, controllers, views, dependency injection, CORS, sessions, sockets, and more.
|
|
4
|
+
|
|
5
|
+
### Install
|
|
6
|
+
```
|
|
7
|
+
npm install mastercontroller
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
### Quickstart
|
|
11
|
+
```js
|
|
12
|
+
// server.js
|
|
13
|
+
const master = require('./MasterControl');
|
|
14
|
+
|
|
15
|
+
master.root = __dirname; // your project root
|
|
16
|
+
master.environmentType = 'development'; // or process.env.NODE_ENV
|
|
17
|
+
|
|
18
|
+
const server = master.setupServer('http'); // or 'https'
|
|
19
|
+
master.start(server);
|
|
20
|
+
master.serverSettings({ httpPort: 3000, hostname: '127.0.0.1', requestTimeout: 60000 });
|
|
21
|
+
|
|
22
|
+
// Or load from config/environments/env.<env>.json
|
|
23
|
+
// master.serverSettings(master.env.server);
|
|
24
|
+
|
|
25
|
+
// Load your routes
|
|
26
|
+
master.startMVC('app');
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Routes
|
|
30
|
+
Create `app/config/routes.js` and define routes with `master.router.start()` API.
|
|
31
|
+
|
|
32
|
+
### Controllers
|
|
33
|
+
Place controllers under `app/controllers/*.js` and export methods matching your routes.
|
|
34
|
+
|
|
35
|
+
### Views and Templates
|
|
36
|
+
Views live under `app/views/<controller>/<action>.html` with a layout at `app/views/layouts/master.html`.
|
|
37
|
+
|
|
38
|
+
### CORS and Preflight
|
|
39
|
+
`MasterCors` configures CORS headers. Preflight `OPTIONS` requests are short-circuited with 204.
|
|
40
|
+
|
|
41
|
+
### HTTPS
|
|
42
|
+
Use `setupServer('https', credentials)` or configure via environment TLS; see docs in `docs/` for multiple setups.
|
|
43
|
+
|
|
44
|
+
### Docs
|
|
45
|
+
- `docs/server-setup-http.md`
|
|
46
|
+
- `docs/server-setup-https-credentials.md`
|
|
47
|
+
- `docs/server-setup-https-env-tls-sni.md`
|
|
48
|
+
- `docs/server-setup-hostname-binding.md`
|
|
49
|
+
- `docs/server-setup-nginx-reverse-proxy.md`
|
|
50
|
+
- `docs/environment-tls-reference.md`
|
|
51
|
+
|
|
52
|
+
### Production tips
|
|
53
|
+
- Prefer a reverse proxy for TLS and serve Node on a high port.
|
|
54
|
+
- If keeping TLS in Node, harden TLS and manage cert rotation.
|
|
55
|
+
|
|
56
|
+
### License
|
|
57
|
+
ISC
|
|
58
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
## Environment TLS/SNI reference
|
|
2
|
+
|
|
3
|
+
Place environment files at `config/environments/env.<environment>.json`.
|
|
4
|
+
|
|
5
|
+
### server section
|
|
6
|
+
```json
|
|
7
|
+
{
|
|
8
|
+
"server": {
|
|
9
|
+
"httpPort": 3000,
|
|
10
|
+
"hostname": "127.0.0.1",
|
|
11
|
+
"requestTimeout": 60000,
|
|
12
|
+
"tls": { /* optional, for HTTPS when using setupServer('https') without credentials */ }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### tls section
|
|
18
|
+
```json
|
|
19
|
+
"tls": {
|
|
20
|
+
"hsts": true, // add HSTS header on HTTPS responses
|
|
21
|
+
"hstsMaxAge": 15552000, // HSTS max-age in seconds (default 180 days)
|
|
22
|
+
"minVersion": "TLSv1.2", // minimum TLS version ('TLSv1.2' or 'TLSv1.3')
|
|
23
|
+
"honorCipherOrder": true, // prefer server cipher order
|
|
24
|
+
"alpnProtocols": ["h2", "http/1.1"], // enable HTTP/2 and HTTP/1.1
|
|
25
|
+
"default": { // fallback certificate if SNI host doesn't match
|
|
26
|
+
"keyPath": "/path/to/key.pem",
|
|
27
|
+
"certPath": "/path/to/cert.pem",
|
|
28
|
+
"caPath": "/path/to/chain.pem",
|
|
29
|
+
"pfxPath": null, // optional if using PFX
|
|
30
|
+
"passphrase": null // optional if key is encrypted
|
|
31
|
+
},
|
|
32
|
+
"sni": { // per-domain certificates
|
|
33
|
+
"example.com": {
|
|
34
|
+
"keyPath": "/path/to/example.key",
|
|
35
|
+
"certPath": "/path/to/example.crt",
|
|
36
|
+
"caPath": "/path/to/chain.pem"
|
|
37
|
+
},
|
|
38
|
+
"api.example.com": {
|
|
39
|
+
"keyPath": "/path/to/api.key",
|
|
40
|
+
"certPath": "/path/to/api.crt",
|
|
41
|
+
"caPath": "/path/to/chain.pem"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Terminology
|
|
48
|
+
- tls: Transport Layer Security. Encrypts traffic between client and server.
|
|
49
|
+
- SNI: Server Name Indication. Lets the server present different certificates based on the requested hostname during TLS handshake.
|
|
50
|
+
- default: The certificate used when no SNI match is found.
|
|
51
|
+
|
|
52
|
+
### Behavior
|
|
53
|
+
- If you call `setupServer('https')` without credentials, MasterControl reads `server.tls` and builds secure contexts (default + SNI) and watches the key/cert files for changes. Updates apply in-memory without restart.
|
|
54
|
+
- If you pass credentials directly to `setupServer('https', credentials)`, those are used instead and env `tls` is ignored.
|
|
55
|
+
|
|
56
|
+
### Tips
|
|
57
|
+
- Keep private keys readable only by the process user.
|
|
58
|
+
- Prefer `TLSv1.2`+ and enable HTTP/2 via ALPN.
|
|
59
|
+
- If binding to 443 without a proxy, consider using a high port (8443) or grant `CAP_NET_BIND_SERVICE` to avoid running as root.
|
|
60
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
## Server setup: Hostname binding
|
|
2
|
+
|
|
3
|
+
Bind the listener to a specific interface using `hostname` (or `host`/`http`).
|
|
4
|
+
|
|
5
|
+
### server.js
|
|
6
|
+
```js
|
|
7
|
+
const master = require('./MasterControl');
|
|
8
|
+
|
|
9
|
+
master.root = __dirname;
|
|
10
|
+
master.environmentType = process.env.NODE_ENV || 'development';
|
|
11
|
+
|
|
12
|
+
const server = master.setupServer('http');
|
|
13
|
+
master.start(server);
|
|
14
|
+
|
|
15
|
+
// Bind to localhost only
|
|
16
|
+
master.serverSettings({ httpPort: 3000, hostname: '127.0.0.1', requestTimeout: 60000 });
|
|
17
|
+
|
|
18
|
+
master.startMVC('app');
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Notes
|
|
22
|
+
- Use `0.0.0.0` to accept connections on all interfaces.
|
|
23
|
+
- In production with a reverse proxy, bind to `127.0.0.1` so only the proxy can reach the app.
|
|
24
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
## Server setup: HTTP
|
|
2
|
+
|
|
3
|
+
This example starts a plain HTTP server. Useful for local development, or when you run behind a reverse proxy that terminates TLS.
|
|
4
|
+
|
|
5
|
+
### server.js (HTTP)
|
|
6
|
+
```js
|
|
7
|
+
const master = require('./MasterControl');
|
|
8
|
+
|
|
9
|
+
// Point master to your project root and environment
|
|
10
|
+
master.root = __dirname;
|
|
11
|
+
master.environmentType = process.env.NODE_ENV || 'development';
|
|
12
|
+
|
|
13
|
+
// Create HTTP server and bind it
|
|
14
|
+
const server = master.setupServer('http');
|
|
15
|
+
master.start(server);
|
|
16
|
+
|
|
17
|
+
// Use either explicit settings or your environment JSON
|
|
18
|
+
// Option A: explicit
|
|
19
|
+
// master.serverSettings({ httpPort: 3000, hostname: '127.0.0.1', requestTimeout: 60000 });
|
|
20
|
+
|
|
21
|
+
// Option B: from env config at config/environments/env.<env>.json
|
|
22
|
+
master.serverSettings(master.env.server);
|
|
23
|
+
|
|
24
|
+
// Load your routes and controllers
|
|
25
|
+
// If your routes are under <root>/app/**/routes.js
|
|
26
|
+
master.startMVC('app');
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Notes
|
|
30
|
+
- `master.serverSettings` now honors `hostname` (or `host`/`http`) if provided; otherwise it listens on all interfaces.
|
|
31
|
+
- For production, prefer running behind a reverse proxy and keep the app on a high port (e.g., 3000).
|
|
32
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
## Server setup: HTTPS with direct credentials
|
|
2
|
+
|
|
3
|
+
Pass key/cert (and optional chain/ca) directly to `setupServer('https', credentials)`.
|
|
4
|
+
|
|
5
|
+
### server.js (HTTPS credentials)
|
|
6
|
+
```js
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const master = require('./MasterControl');
|
|
9
|
+
|
|
10
|
+
master.root = __dirname;
|
|
11
|
+
master.environmentType = process.env.NODE_ENV || 'production';
|
|
12
|
+
|
|
13
|
+
const credentials = {
|
|
14
|
+
key: fs.readFileSync('/etc/ssl/private/site.key'),
|
|
15
|
+
cert: fs.readFileSync('/etc/ssl/certs/site.crt'),
|
|
16
|
+
ca: fs.readFileSync('/etc/ssl/certs/chain.pem'),
|
|
17
|
+
minVersion: 'TLSv1.2',
|
|
18
|
+
honorCipherOrder: true,
|
|
19
|
+
ALPNProtocols: ['h2', 'http/1.1']
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const server = master.setupServer('https', credentials);
|
|
23
|
+
master.start(server);
|
|
24
|
+
master.serverSettings({ httpPort: 8443, hostname: '0.0.0.0', requestTimeout: 60000 });
|
|
25
|
+
master.startMVC('app');
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Notes
|
|
29
|
+
- Use a high port (e.g., 8443) to avoid running as root, or grant `CAP_NET_BIND_SERVICE` if binding to 443.
|
|
30
|
+
- Strong defaults are ensured if you omit them, but explicitly setting them is recommended.
|
|
31
|
+
- For multiple domains, see the TLS/SNI guide.
|
|
32
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
## Server setup: HTTPS via environment TLS (with SNI and live reload)
|
|
2
|
+
|
|
3
|
+
This uses `config/environments/env.<env>.json` to configure TLS, SNI (multi-domain), HSTS, and watches cert files for live reload.
|
|
4
|
+
|
|
5
|
+
### Example env.production.json
|
|
6
|
+
```json
|
|
7
|
+
{
|
|
8
|
+
"server": {
|
|
9
|
+
"httpPort": 8443,
|
|
10
|
+
"hostname": "0.0.0.0",
|
|
11
|
+
"requestTimeout": 60000,
|
|
12
|
+
"tls": {
|
|
13
|
+
"hsts": true,
|
|
14
|
+
"hstsMaxAge": 15552000,
|
|
15
|
+
"minVersion": "TLSv1.2",
|
|
16
|
+
"honorCipherOrder": true,
|
|
17
|
+
"alpnProtocols": ["h2", "http/1.1"],
|
|
18
|
+
"default": {
|
|
19
|
+
"keyPath": "/etc/ssl/private/site.key",
|
|
20
|
+
"certPath": "/etc/ssl/certs/site.crt",
|
|
21
|
+
"caPath": "/etc/ssl/certs/chain.pem"
|
|
22
|
+
},
|
|
23
|
+
"sni": {
|
|
24
|
+
"example.com": {
|
|
25
|
+
"keyPath": "/etc/ssl/private/example.key",
|
|
26
|
+
"certPath": "/etc/ssl/certs/example.crt",
|
|
27
|
+
"caPath": "/etc/ssl/certs/chain.pem"
|
|
28
|
+
},
|
|
29
|
+
"api.example.com": {
|
|
30
|
+
"keyPath": "/etc/ssl/private/api.key",
|
|
31
|
+
"certPath": "/etc/ssl/certs/api.crt",
|
|
32
|
+
"caPath": "/etc/ssl/certs/chain.pem"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### server.js (HTTPS from env)
|
|
41
|
+
```js
|
|
42
|
+
const master = require('./MasterControl');
|
|
43
|
+
|
|
44
|
+
master.root = __dirname;
|
|
45
|
+
master.environmentType = process.env.NODE_ENV || 'production';
|
|
46
|
+
|
|
47
|
+
// No credentials passed; MasterControl will auto-load TLS from env
|
|
48
|
+
const server = master.setupServer('https');
|
|
49
|
+
master.start(server);
|
|
50
|
+
master.serverSettings(master.env.server);
|
|
51
|
+
master.startMVC('app');
|
|
52
|
+
|
|
53
|
+
// Optional: HTTP->HTTPS redirect (listen on 80)
|
|
54
|
+
// master.startHttpToHttpsRedirect(80, '0.0.0.0');
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### How it works
|
|
58
|
+
- `default`: certs used when SNI domain does not match any entry.
|
|
59
|
+
- `sni`: per-domain certificates; the server chooses the right cert via `SNICallback`.
|
|
60
|
+
- Live reload: when any `keyPath`/`certPath`/`caPath` changes, the secure context is rebuilt in-memory (no restart needed).
|
|
61
|
+
- HSTS: when enabled, responses over HTTPS include `strict-transport-security` with the configured max-age.
|
|
62
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
## Server setup: Nginx reverse proxy with HTTP→HTTPS redirect
|
|
2
|
+
|
|
3
|
+
Recommended production pattern: Node app on a high port (HTTP), Nginx on 80/443 handling TLS and redirects.
|
|
4
|
+
|
|
5
|
+
### server.js (app on HTTP localhost:3000)
|
|
6
|
+
```js
|
|
7
|
+
const master = require('./MasterControl');
|
|
8
|
+
|
|
9
|
+
master.root = __dirname;
|
|
10
|
+
master.environmentType = process.env.NODE_ENV || 'production';
|
|
11
|
+
|
|
12
|
+
const server = master.setupServer('http');
|
|
13
|
+
master.start(server);
|
|
14
|
+
master.serverSettings({ httpPort: 3000, hostname: '127.0.0.1', requestTimeout: 60000 });
|
|
15
|
+
master.startMVC('app');
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Nginx config
|
|
19
|
+
```nginx
|
|
20
|
+
server {
|
|
21
|
+
listen 80;
|
|
22
|
+
server_name yourdomain.com;
|
|
23
|
+
return 301 https://$host$request_uri;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
server {
|
|
27
|
+
listen 443 ssl http2;
|
|
28
|
+
server_name yourdomain.com;
|
|
29
|
+
|
|
30
|
+
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
|
|
31
|
+
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
|
|
32
|
+
|
|
33
|
+
location / {
|
|
34
|
+
proxy_pass http://127.0.0.1:3000;
|
|
35
|
+
proxy_set_header Host $host;
|
|
36
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
37
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
38
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Notes
|
|
44
|
+
- Use certbot or another ACME client to manage certificates and renewals automatically.
|
|
45
|
+
- This keeps Node unprivileged (no need to bind to 443) and simplifies TLS.
|
|
46
|
+
|
package/package.json
CHANGED