mastercontroller 1.2.3 → 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 CHANGED
@@ -1,10 +1,11 @@
1
1
  // MasterControl - by Alexander rich
2
- // version 1.0.245
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
- if(settings.http){
173
- this.server.listen(settings.httpPort, settings.http);
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/MasterRouter.js CHANGED
@@ -1,4 +1,4 @@
1
- // version 0.0.247
1
+ // version 0.0.248
2
2
 
3
3
  var master = require('./MasterControl');
4
4
  var toolClass = require('./MasterTools');
@@ -87,7 +87,7 @@ var tools = new toolClass();
87
87
  return -1;
88
88
  }
89
89
  else{
90
- master.error.log(`route list is not an array`, "Error");
90
+ master.error.log(`route list is not an array`, "error");
91
91
  return -1;
92
92
  }
93
93
  }
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
@@ -18,5 +18,5 @@
18
18
  "scripts": {
19
19
  "test": "echo \"Error: no test specified\" && exit 1"
20
20
  },
21
- "version": "1.2.3"
21
+ "version": "1.2.5"
22
22
  }