mastercontroller 1.2.4 → 1.2.6
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 +167 -17
- 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, {
|
|
@@ -148,19 +151,25 @@ class MasterControl {
|
|
|
148
151
|
|
|
149
152
|
component(folderLocation, innerFolder){
|
|
150
153
|
|
|
151
|
-
var rootFolderLocation =
|
|
152
|
-
var
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
var rootFolderLocation = path.join(this.root, folderLocation, innerFolder);
|
|
155
|
+
var files = globSearch.sync("**/*config.js", { cwd: rootFolderLocation, absolute: true });
|
|
156
|
+
if(files && files.length > 0){
|
|
157
|
+
require(files[0]);
|
|
158
|
+
}else{
|
|
159
|
+
master.error.log(`Cannot find config file under ${rootFolderLocation}`, "error");
|
|
160
|
+
}
|
|
161
|
+
var routeFiles = globSearch.sync("**/*routes.js", { cwd: rootFolderLocation, absolute: true });
|
|
162
|
+
var route = routeFiles && routeFiles.length > 0 ? routeFiles[0] : null;
|
|
158
163
|
var routeObject = {
|
|
159
164
|
isComponent : true,
|
|
160
165
|
root : rootFolderLocation
|
|
161
166
|
}
|
|
162
167
|
this.router.setup(routeObject);
|
|
163
|
-
|
|
168
|
+
if(route){
|
|
169
|
+
require(route);
|
|
170
|
+
}else{
|
|
171
|
+
master.error.log(`Cannot find routes file under ${rootFolderLocation}`, "error");
|
|
172
|
+
}
|
|
164
173
|
}
|
|
165
174
|
|
|
166
175
|
|
|
@@ -169,11 +178,12 @@ class MasterControl {
|
|
|
169
178
|
|
|
170
179
|
if(settings.httpPort || settings.requestTimeout){
|
|
171
180
|
this.server.timeout = settings.requestTimeout;
|
|
172
|
-
|
|
173
|
-
|
|
181
|
+
var host = settings.hostname || settings.host || settings.http;
|
|
182
|
+
if(host){
|
|
183
|
+
this.server.listen(settings.httpPort, host);
|
|
174
184
|
}else{
|
|
175
|
-
this.server.listen(settings.httpPort);
|
|
176
|
-
}
|
|
185
|
+
this.server.listen(settings.httpPort);
|
|
186
|
+
}
|
|
177
187
|
}
|
|
178
188
|
else{
|
|
179
189
|
throw "HTTP, HTTPS, HTTPPORT and REQUEST TIMEOUT MISSING";
|
|
@@ -201,7 +211,16 @@ class MasterControl {
|
|
|
201
211
|
}
|
|
202
212
|
if(type === "https"){
|
|
203
213
|
$that.serverProtocol = "https";
|
|
214
|
+
// Initialize TLS from env if no credentials passed
|
|
215
|
+
if(!credentials){
|
|
216
|
+
$that._initializeTlsFromEnv();
|
|
217
|
+
credentials = $that._tlsOptions;
|
|
218
|
+
}
|
|
219
|
+
// Apply secure defaults if missing
|
|
204
220
|
if(credentials){
|
|
221
|
+
if(!credentials.minVersion){ credentials.minVersion = 'TLSv1.2'; }
|
|
222
|
+
if(credentials.honorCipherOrder === undefined){ credentials.honorCipherOrder = true; }
|
|
223
|
+
if(!credentials.ALPNProtocols){ credentials.ALPNProtocols = ['h2', 'http/1.1']; }
|
|
205
224
|
return https.createServer(credentials, async function(req, res) {
|
|
206
225
|
$that.serverRun(req, res);
|
|
207
226
|
});
|
|
@@ -216,6 +235,130 @@ class MasterControl {
|
|
|
216
235
|
}
|
|
217
236
|
}
|
|
218
237
|
|
|
238
|
+
// Creates an HTTP server that 301-redirects to HTTPS counterpart
|
|
239
|
+
startHttpToHttpsRedirect(redirectPort, bindHost){
|
|
240
|
+
var $that = this;
|
|
241
|
+
return http.createServer(function (req, res) {
|
|
242
|
+
try{
|
|
243
|
+
var host = req.headers['host'] || '';
|
|
244
|
+
// Force original host, just change scheme
|
|
245
|
+
var location = 'https://' + host + req.url;
|
|
246
|
+
res.statusCode = 301;
|
|
247
|
+
res.setHeader('Location', location);
|
|
248
|
+
res.end();
|
|
249
|
+
}catch(e){
|
|
250
|
+
res.statusCode = 500;
|
|
251
|
+
res.end();
|
|
252
|
+
}
|
|
253
|
+
}).listen(redirectPort, bindHost);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Load TLS configuration from env and build SNI contexts with live reload
|
|
257
|
+
_initializeTlsFromEnv(){
|
|
258
|
+
try{
|
|
259
|
+
var cfg = this.env;
|
|
260
|
+
if(!cfg || !cfg.server || !cfg.server.tls){
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
var tlsCfg = cfg.server.tls;
|
|
264
|
+
|
|
265
|
+
var defaultCreds = this._buildSecureContextFromPaths(tlsCfg.default);
|
|
266
|
+
var defaultContext = defaultCreds ? tls.createSecureContext(defaultCreds) : null;
|
|
267
|
+
|
|
268
|
+
var sniMap = {};
|
|
269
|
+
if(tlsCfg.sni && typeof tlsCfg.sni === 'object'){
|
|
270
|
+
for (var domain in tlsCfg.sni){
|
|
271
|
+
if (Object.prototype.hasOwnProperty.call(tlsCfg.sni, domain)){
|
|
272
|
+
var domCreds = this._buildSecureContextFromPaths(tlsCfg.sni[domain]);
|
|
273
|
+
if(domCreds){
|
|
274
|
+
sniMap[domain] = tls.createSecureContext(domCreds);
|
|
275
|
+
// watch domain certs for reload
|
|
276
|
+
this._watchTlsFilesAndReload(tlsCfg.sni[domain], function(){
|
|
277
|
+
try{
|
|
278
|
+
var updated = tls.createSecureContext(
|
|
279
|
+
this._buildSecureContextFromPaths(tlsCfg.sni[domain])
|
|
280
|
+
);
|
|
281
|
+
sniMap[domain] = updated;
|
|
282
|
+
}catch(e){
|
|
283
|
+
console.error('Failed to reload TLS context for domain', domain, e);
|
|
284
|
+
}
|
|
285
|
+
}.bind(this));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
var options = defaultCreds ? Object.assign({}, defaultCreds) : {};
|
|
292
|
+
options.SNICallback = function(servername, cb){
|
|
293
|
+
var ctx = sniMap[servername];
|
|
294
|
+
if(!ctx && defaultContext){ ctx = defaultContext; }
|
|
295
|
+
if(cb){ return cb(null, ctx); }
|
|
296
|
+
return ctx;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// Apply top-level TLS defaults/hardening from env if provided
|
|
300
|
+
if(tlsCfg.minVersion){ options.minVersion = tlsCfg.minVersion; }
|
|
301
|
+
if(tlsCfg.honorCipherOrder !== undefined){ options.honorCipherOrder = tlsCfg.honorCipherOrder; }
|
|
302
|
+
if(tlsCfg.ciphers){ options.ciphers = tlsCfg.ciphers; }
|
|
303
|
+
if(tlsCfg.alpnProtocols){ options.ALPNProtocols = tlsCfg.alpnProtocols; }
|
|
304
|
+
|
|
305
|
+
// HSTS
|
|
306
|
+
this._hstsEnabled = !!tlsCfg.hsts;
|
|
307
|
+
this._hstsMaxAge = tlsCfg.hstsMaxAge || 15552000; // 180 days by default
|
|
308
|
+
|
|
309
|
+
// Watch default certs for reload
|
|
310
|
+
if(tlsCfg.default){
|
|
311
|
+
this._watchTlsFilesAndReload(tlsCfg.default, function(){
|
|
312
|
+
try{
|
|
313
|
+
var updatedCreds = this._buildSecureContextFromPaths(tlsCfg.default);
|
|
314
|
+
defaultContext = tls.createSecureContext(updatedCreds);
|
|
315
|
+
// keep key/cert on options for non-SNI connections
|
|
316
|
+
Object.assign(options, updatedCreds);
|
|
317
|
+
}catch(e){
|
|
318
|
+
console.error('Failed to reload default TLS context', e);
|
|
319
|
+
}
|
|
320
|
+
}.bind(this));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
this._tlsOptions = options;
|
|
324
|
+
}catch(e){
|
|
325
|
+
console.error('Failed to initialize TLS from env', e);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
_buildSecureContextFromPaths(desc){
|
|
330
|
+
if(!desc){ return null; }
|
|
331
|
+
var opts = {};
|
|
332
|
+
try{
|
|
333
|
+
if(desc.keyPath){ opts.key = fs.readFileSync(desc.keyPath); }
|
|
334
|
+
if(desc.certPath){ opts.cert = fs.readFileSync(desc.certPath); }
|
|
335
|
+
if(desc.caPath){ opts.ca = fs.readFileSync(desc.caPath); }
|
|
336
|
+
if(desc.pfxPath){ opts.pfx = fs.readFileSync(desc.pfxPath); }
|
|
337
|
+
if(desc.passphrase){ opts.passphrase = desc.passphrase; }
|
|
338
|
+
return opts;
|
|
339
|
+
}catch(e){
|
|
340
|
+
console.error('Failed to read TLS files', e);
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
_watchTlsFilesAndReload(desc, onChange){
|
|
346
|
+
var paths = [];
|
|
347
|
+
if(desc.keyPath){ paths.push(desc.keyPath); }
|
|
348
|
+
if(desc.certPath){ paths.push(desc.certPath); }
|
|
349
|
+
if(desc.caPath){ paths.push(desc.caPath); }
|
|
350
|
+
if(desc.pfxPath){ paths.push(desc.pfxPath); }
|
|
351
|
+
paths.forEach(function(p){
|
|
352
|
+
try{
|
|
353
|
+
fs.watchFile(p, { interval: 5000 }, function(){
|
|
354
|
+
onChange();
|
|
355
|
+
});
|
|
356
|
+
}catch(e){
|
|
357
|
+
console.error('Failed to watch TLS file', p, e);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
219
362
|
async serverRun(req, res){
|
|
220
363
|
var $that = this;
|
|
221
364
|
console.log("path", `${req.method} ${req.url}`);
|
|
@@ -271,6 +414,10 @@ class MasterControl {
|
|
|
271
414
|
if(ext === ""){
|
|
272
415
|
var requestObject = await this.middleware(req, res);
|
|
273
416
|
if(requestObject !== -1){
|
|
417
|
+
// HSTS header if enabled
|
|
418
|
+
if(this.serverProtocol === 'https' && this._hstsEnabled){
|
|
419
|
+
res.setHeader('strict-transport-security', `max-age=${this._hstsMaxAge}; includeSubDomains`);
|
|
420
|
+
}
|
|
274
421
|
var loadedDone = false;
|
|
275
422
|
if (typeof $that._loadedFunc === 'function') {
|
|
276
423
|
loadedDone = $that._loadedFunc(requestObject);
|
|
@@ -323,15 +470,18 @@ class MasterControl {
|
|
|
323
470
|
}
|
|
324
471
|
|
|
325
472
|
startMVC(foldername){
|
|
326
|
-
var rootFolderLocation =
|
|
327
|
-
var
|
|
328
|
-
var files = globSearch.sync(search, rootFolderLocation);
|
|
473
|
+
var rootFolderLocation = path.join(this.root, foldername);
|
|
474
|
+
var files = globSearch.sync("**/*routes.js", { cwd: rootFolderLocation, absolute: true });
|
|
329
475
|
var route = {
|
|
330
476
|
isComponent : false,
|
|
331
477
|
root : `${this.root}`
|
|
332
478
|
}
|
|
333
479
|
this.router.setup(route);
|
|
334
|
-
|
|
480
|
+
if(files && files.length > 0){
|
|
481
|
+
require(files[0]);
|
|
482
|
+
}else{
|
|
483
|
+
master.error.log(`Cannot find routes file under ${rootFolderLocation}`, "error");
|
|
484
|
+
}
|
|
335
485
|
}
|
|
336
486
|
|
|
337
487
|
|
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