mastercontroller 1.3.1 → 1.3.3
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/.claude/settings.local.json +6 -1
- package/MasterAction.js +158 -27
- package/MasterActionFilters.js +213 -93
- package/MasterControl.js +289 -43
- package/MasterCors.js +1 -2
- package/MasterHtml.js +243 -145
- package/MasterPipeline.js +2 -3
- package/MasterRequest.js +203 -26
- package/MasterRouter.js +1 -2
- package/MasterSocket.js +7 -3
- package/MasterTemp.js +1 -2
- package/MasterTimeout.js +2 -4
- package/MasterTools.js +388 -0
- package/README.md +2288 -369
- package/TemplateOverwrite.js +1 -1
- package/docs/SECURITY-AUDIT-ACTION-SYSTEM.md +1374 -0
- package/docs/SECURITY-AUDIT-HTTPS.md +1056 -0
- package/docs/SECURITY-QUICKSTART.md +375 -0
- package/docs/timeout-and-error-handling.md +8 -6
- package/error/MasterError.js +1 -2
- package/error/MasterErrorRenderer.js +1 -2
- package/package.json +1 -1
- package/security/SecurityEnforcement.js +241 -0
- package/security/SessionSecurity.js +6 -5
- package/test/security/filters.test.js +276 -0
- package/test/security/https.test.js +214 -0
- package/test/security/path-traversal.test.js +222 -0
- package/test/security/xss.test.js +190 -0
- package/MasterSession.js +0 -208
- package/docs/server-setup-hostname-binding.md +0 -24
- package/docs/server-setup-http.md +0 -32
- package/docs/server-setup-https-credentials.md +0 -32
- package/docs/server-setup-https-env-tls-sni.md +0 -62
- package/docs/server-setup-nginx-reverse-proxy.md +0 -46
package/MasterControl.js
CHANGED
|
@@ -64,6 +64,9 @@ class MasterControl {
|
|
|
64
64
|
_loadedFunc = null
|
|
65
65
|
_tlsOptions = null
|
|
66
66
|
_hstsEnabled = false
|
|
67
|
+
_hstsMaxAge = 31536000 // 1 year default
|
|
68
|
+
_hstsIncludeSubDomains = true
|
|
69
|
+
_hstsPreload = false
|
|
67
70
|
|
|
68
71
|
#loadTransientListClasses(name, params){
|
|
69
72
|
Object.defineProperty(this.requestList, name, {
|
|
@@ -228,6 +231,13 @@ class MasterControl {
|
|
|
228
231
|
|
|
229
232
|
// adds all the server settings needed
|
|
230
233
|
serverSettings(settings){
|
|
234
|
+
// Defensive: Check if server exists (may be called before master.start())
|
|
235
|
+
if (!this.server) {
|
|
236
|
+
console.warn('[MasterControl] serverSettings() called before master.start(server). Settings will be applied when server is set.');
|
|
237
|
+
// Store settings to apply later
|
|
238
|
+
this._pendingServerSettings = settings;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
231
241
|
|
|
232
242
|
if(settings.httpPort || settings.requestTimeout){
|
|
233
243
|
this.server.timeout = settings.requestTimeout;
|
|
@@ -244,6 +254,38 @@ class MasterControl {
|
|
|
244
254
|
|
|
245
255
|
}
|
|
246
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Enable HSTS (HTTP Strict Transport Security) for HTTPS
|
|
259
|
+
* Should only be called for production HTTPS servers
|
|
260
|
+
*
|
|
261
|
+
* @param {Object} options - HSTS configuration options
|
|
262
|
+
* @param {Number} options.maxAge - Max age in seconds (default: 31536000 = 1 year)
|
|
263
|
+
* @param {Boolean} options.includeSubDomains - Include subdomains (default: true)
|
|
264
|
+
* @param {Boolean} options.preload - Enable HSTS preload (default: false)
|
|
265
|
+
* @returns {MasterControl} - Returns this for chaining
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* // Basic usage (1 year, includeSubDomains)
|
|
269
|
+
* master.enableHSTS();
|
|
270
|
+
*
|
|
271
|
+
* // Custom configuration
|
|
272
|
+
* master.enableHSTS({
|
|
273
|
+
* maxAge: 15552000, // 180 days
|
|
274
|
+
* includeSubDomains: true,
|
|
275
|
+
* preload: true // Submit to HSTS preload list
|
|
276
|
+
* });
|
|
277
|
+
*/
|
|
278
|
+
enableHSTS(options = {}) {
|
|
279
|
+
this._hstsEnabled = true;
|
|
280
|
+
this._hstsMaxAge = options.maxAge || 31536000; // 1 year default (matches industry standard)
|
|
281
|
+
this._hstsIncludeSubDomains = options.includeSubDomains !== false; // true by default
|
|
282
|
+
this._hstsPreload = options.preload === true; // false by default
|
|
283
|
+
|
|
284
|
+
console.log(`[MasterControl] HSTS enabled: max-age=${this._hstsMaxAge}${this._hstsIncludeSubDomains ? ', includeSubDomains' : ''}${this._hstsPreload ? ', preload' : ''}`);
|
|
285
|
+
|
|
286
|
+
return this; // Chainable
|
|
287
|
+
}
|
|
288
|
+
|
|
247
289
|
useHTTPServer(port, func){
|
|
248
290
|
if (typeof func === 'function') {
|
|
249
291
|
http.createServer(function (req, res) {
|
|
@@ -256,39 +298,83 @@ class MasterControl {
|
|
|
256
298
|
setupServer(type, credentials ){
|
|
257
299
|
try {
|
|
258
300
|
var $that = this;
|
|
259
|
-
|
|
260
|
-
//
|
|
301
|
+
|
|
302
|
+
// AUTO-LOAD internal framework modules
|
|
303
|
+
// These are required for the framework to function and are loaded transparently
|
|
304
|
+
const internalModules = {
|
|
305
|
+
'MasterPipeline': './MasterPipeline',
|
|
306
|
+
'MasterTimeout': './MasterTimeout',
|
|
307
|
+
'MasterErrorRenderer': './error/MasterErrorRenderer',
|
|
308
|
+
'MasterAction': './MasterAction',
|
|
309
|
+
'MasterActionFilters': './MasterActionFilters',
|
|
310
|
+
'MasterRouter': './MasterRouter',
|
|
311
|
+
'MasterRequest': './MasterRequest',
|
|
312
|
+
'MasterError': './error/MasterError',
|
|
313
|
+
'MasterCors': './MasterCors',
|
|
314
|
+
'SessionSecurity': './security/SessionSecurity',
|
|
315
|
+
'MasterSocket': './MasterSocket',
|
|
316
|
+
'MasterHtml': './MasterHtml',
|
|
317
|
+
'MasterTemplate': './MasterTemplate',
|
|
318
|
+
'MasterTools': './MasterTools',
|
|
319
|
+
'TemplateOverwrite': './TemplateOverwrite'
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Explicit module registration (prevents circular dependency issues)
|
|
323
|
+
// This is the Google-style dependency injection pattern
|
|
324
|
+
const moduleRegistry = {
|
|
325
|
+
'pipeline': { path: './MasterPipeline', exportName: 'MasterPipeline' },
|
|
326
|
+
'timeout': { path: './MasterTimeout', exportName: 'MasterTimeout' },
|
|
327
|
+
'errorRenderer': { path: './error/MasterErrorRenderer', exportName: 'MasterErrorRenderer' },
|
|
328
|
+
'error': { path: './error/MasterError', exportName: 'MasterError' },
|
|
329
|
+
'router': { path: './MasterRouter', exportName: 'MasterRouter' },
|
|
330
|
+
'request': { path: './MasterRequest', exportName: 'MasterRequest' },
|
|
331
|
+
'cors': { path: './MasterCors', exportName: 'MasterCors' },
|
|
332
|
+
'socket': { path: './MasterSocket', exportName: 'MasterSocket' },
|
|
333
|
+
'tempdata': { path: './MasterTemp', exportName: 'MasterTemp' },
|
|
334
|
+
'overwrite': { path: './TemplateOverwrite', exportName: 'TemplateOverwrite' },
|
|
335
|
+
'session': { path: './security/SessionSecurity', exportName: 'MasterSessionSecurity' }
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
for (const [name, config] of Object.entries(moduleRegistry)) {
|
|
339
|
+
try {
|
|
340
|
+
const module = require(config.path);
|
|
341
|
+
const ClassConstructor = module[config.exportName] || module;
|
|
342
|
+
|
|
343
|
+
if (ClassConstructor) {
|
|
344
|
+
$that[name] = new ClassConstructor();
|
|
345
|
+
} else {
|
|
346
|
+
console.warn(`[MasterControl] Module ${name} does not export ${config.exportName}`);
|
|
347
|
+
}
|
|
348
|
+
} catch (e) {
|
|
349
|
+
console.error(`[MasterControl] Failed to load ${name}:`, e.message);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Load view and controller extensions (these extend prototypes, not master instance)
|
|
261
354
|
try {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
'MasterActionFilters',
|
|
268
|
-
'MasterRouter',
|
|
269
|
-
'MasterRequest',
|
|
270
|
-
'MasterError',
|
|
271
|
-
'MasterCors',
|
|
272
|
-
'MasterSession',
|
|
273
|
-
'SessionSecurity',
|
|
274
|
-
'MasterSocket',
|
|
275
|
-
'MasterHtml',
|
|
276
|
-
'MasterTemplate',
|
|
277
|
-
'MasterTools',
|
|
278
|
-
'TemplateOverwrite'
|
|
279
|
-
]);
|
|
355
|
+
require('./MasterAction');
|
|
356
|
+
require('./MasterActionFilters');
|
|
357
|
+
require('./MasterHtml');
|
|
358
|
+
require('./MasterTemplate');
|
|
359
|
+
require('./MasterTools');
|
|
280
360
|
} catch (e) {
|
|
281
|
-
console.error('[MasterControl] Failed to load
|
|
361
|
+
console.error('[MasterControl] Failed to load extensions:', e.message);
|
|
282
362
|
}
|
|
283
363
|
|
|
364
|
+
// Initialize global error handlers
|
|
365
|
+
setupGlobalErrorHandlers();
|
|
366
|
+
|
|
284
367
|
// Register core middleware that must run for framework to function
|
|
285
368
|
$that._registerCoreMiddleware();
|
|
286
369
|
|
|
287
370
|
if(type === "http"){
|
|
288
371
|
$that.serverProtocol = "http";
|
|
289
|
-
|
|
372
|
+
const server = http.createServer(async function(req, res) {
|
|
290
373
|
$that.serverRun(req, res);
|
|
291
374
|
});
|
|
375
|
+
// Set server immediately so config can access it
|
|
376
|
+
$that.server = server;
|
|
377
|
+
return server;
|
|
292
378
|
}
|
|
293
379
|
if(type === "https"){
|
|
294
380
|
$that.serverProtocol = "https";
|
|
@@ -297,14 +383,42 @@ class MasterControl {
|
|
|
297
383
|
$that._initializeTlsFromEnv();
|
|
298
384
|
credentials = $that._tlsOptions;
|
|
299
385
|
}
|
|
300
|
-
// Apply secure defaults if missing
|
|
386
|
+
// Apply secure defaults if missing (2026 security standards)
|
|
301
387
|
if(credentials){
|
|
302
|
-
|
|
388
|
+
// Default to TLS 1.3 for security (2026 standard)
|
|
389
|
+
// TLS 1.2 still supported but not default
|
|
390
|
+
if(!credentials.minVersion){
|
|
391
|
+
credentials.minVersion = 'TLSv1.3';
|
|
392
|
+
console.log('[MasterControl] TLS 1.3 enabled by default (recommended for 2026)');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Secure cipher suites (Mozilla Intermediate configuration - 2026)
|
|
396
|
+
// Supports TLS 1.3 and TLS 1.2 for backward compatibility
|
|
397
|
+
if(!credentials.ciphers){
|
|
398
|
+
credentials.ciphers = [
|
|
399
|
+
// TLS 1.3 cipher suites (strongest)
|
|
400
|
+
'TLS_AES_256_GCM_SHA384',
|
|
401
|
+
'TLS_CHACHA20_POLY1305_SHA256',
|
|
402
|
+
'TLS_AES_128_GCM_SHA256',
|
|
403
|
+
// TLS 1.2 cipher suites (backward compatibility)
|
|
404
|
+
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
|
405
|
+
'ECDHE-RSA-AES256-GCM-SHA384',
|
|
406
|
+
'ECDHE-ECDSA-CHACHA20-POLY1305',
|
|
407
|
+
'ECDHE-RSA-CHACHA20-POLY1305',
|
|
408
|
+
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
|
409
|
+
'ECDHE-RSA-AES128-GCM-SHA256'
|
|
410
|
+
].join(':');
|
|
411
|
+
console.log('[MasterControl] Secure cipher suites configured (Mozilla Intermediate)');
|
|
412
|
+
}
|
|
413
|
+
|
|
303
414
|
if(credentials.honorCipherOrder === undefined){ credentials.honorCipherOrder = true; }
|
|
304
415
|
if(!credentials.ALPNProtocols){ credentials.ALPNProtocols = ['h2', 'http/1.1']; }
|
|
305
|
-
|
|
416
|
+
const server = https.createServer(credentials, async function(req, res) {
|
|
306
417
|
$that.serverRun(req, res);
|
|
307
418
|
});
|
|
419
|
+
// Set server immediately so config can access it
|
|
420
|
+
$that.server = server;
|
|
421
|
+
return server;
|
|
308
422
|
}else{
|
|
309
423
|
throw "Credentials needed to setup https"
|
|
310
424
|
}
|
|
@@ -316,18 +430,69 @@ class MasterControl {
|
|
|
316
430
|
}
|
|
317
431
|
}
|
|
318
432
|
|
|
319
|
-
|
|
320
|
-
|
|
433
|
+
/**
|
|
434
|
+
* Creates an HTTP server that 301-redirects to HTTPS counterpart
|
|
435
|
+
* SECURITY: Validates host header to prevent open redirect attacks
|
|
436
|
+
*
|
|
437
|
+
* @param {Number} redirectPort - Port to listen on (usually 80)
|
|
438
|
+
* @param {String} bindHost - Host to bind to (e.g., '0.0.0.0')
|
|
439
|
+
* @param {Array<String>} allowedHosts - Whitelist of allowed hostnames (REQUIRED for security)
|
|
440
|
+
* @returns {http.Server} - HTTP server instance
|
|
441
|
+
*
|
|
442
|
+
* @example
|
|
443
|
+
* // Production usage (MUST specify allowed hosts)
|
|
444
|
+
* const redirectServer = master.startHttpToHttpsRedirect(80, '0.0.0.0', [
|
|
445
|
+
* 'example.com',
|
|
446
|
+
* 'www.example.com',
|
|
447
|
+
* 'api.example.com'
|
|
448
|
+
* ]);
|
|
449
|
+
*
|
|
450
|
+
* @security CRITICAL: Always provide allowedHosts in production to prevent open redirect attacks
|
|
451
|
+
*/
|
|
452
|
+
startHttpToHttpsRedirect(redirectPort, bindHost, allowedHosts = []){
|
|
321
453
|
var $that = this;
|
|
454
|
+
|
|
455
|
+
// Security warning if no hosts specified
|
|
456
|
+
if (allowedHosts.length === 0) {
|
|
457
|
+
console.warn('[MasterControl] ⚠️ SECURITY WARNING: startHttpToHttpsRedirect() called without allowedHosts.');
|
|
458
|
+
console.warn('[MasterControl] This is vulnerable to open redirect attacks. Specify allowed hosts:');
|
|
459
|
+
console.warn('[MasterControl] master.startHttpToHttpsRedirect(80, "0.0.0.0", ["example.com", "www.example.com"])');
|
|
460
|
+
}
|
|
461
|
+
|
|
322
462
|
return http.createServer(function (req, res) {
|
|
323
463
|
try{
|
|
324
464
|
var host = req.headers['host'] || '';
|
|
325
|
-
|
|
465
|
+
var hostname = host.split(':')[0]; // Remove port number
|
|
466
|
+
|
|
467
|
+
// CRITICAL SECURITY: Validate host header to prevent open redirect attacks
|
|
468
|
+
if (allowedHosts.length > 0) {
|
|
469
|
+
if (!allowedHosts.includes(hostname)) {
|
|
470
|
+
logger.warn({
|
|
471
|
+
code: 'MC_SECURITY_INVALID_HOST',
|
|
472
|
+
message: 'HTTP redirect blocked: invalid host header',
|
|
473
|
+
host: hostname,
|
|
474
|
+
ip: req.connection.remoteAddress
|
|
475
|
+
});
|
|
476
|
+
res.statusCode = 400;
|
|
477
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
478
|
+
res.end('Bad Request: Invalid host header');
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Redirect to HTTPS with validated host
|
|
326
484
|
var location = 'https://' + host + req.url;
|
|
327
485
|
res.statusCode = 301;
|
|
328
486
|
res.setHeader('Location', location);
|
|
487
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
329
488
|
res.end();
|
|
330
489
|
}catch(e){
|
|
490
|
+
logger.error({
|
|
491
|
+
code: 'MC_ERR_REDIRECT',
|
|
492
|
+
message: 'HTTP to HTTPS redirect failed',
|
|
493
|
+
error: e.message,
|
|
494
|
+
stack: e.stack
|
|
495
|
+
});
|
|
331
496
|
res.statusCode = 500;
|
|
332
497
|
res.end();
|
|
333
498
|
}
|
|
@@ -447,30 +612,90 @@ class MasterControl {
|
|
|
447
612
|
_registerCoreMiddleware(){
|
|
448
613
|
var $that = this;
|
|
449
614
|
|
|
450
|
-
// 1. Static File Serving
|
|
615
|
+
// 1. Static File Serving (with path traversal protection)
|
|
451
616
|
$that.pipeline.use(async (ctx, next) => {
|
|
452
617
|
if (ctx.isStatic) {
|
|
453
|
-
//
|
|
454
|
-
let
|
|
618
|
+
// SECURITY: Prevent path traversal attacks
|
|
619
|
+
let requestedPath = ctx.request.url;
|
|
620
|
+
|
|
621
|
+
// Normalize the path and resolve it
|
|
622
|
+
const publicRoot = path.resolve($that.root || '.');
|
|
623
|
+
const safePath = path.join(publicRoot, requestedPath);
|
|
624
|
+
const resolvedPath = path.resolve(safePath);
|
|
625
|
+
|
|
626
|
+
// CRITICAL: Ensure resolved path is within public root (prevents ../ attacks)
|
|
627
|
+
if (!resolvedPath.startsWith(publicRoot)) {
|
|
628
|
+
logger.warn({
|
|
629
|
+
code: 'MC_SECURITY_PATH_TRAVERSAL',
|
|
630
|
+
message: 'Path traversal attack blocked',
|
|
631
|
+
requestedPath: requestedPath,
|
|
632
|
+
resolvedPath: resolvedPath,
|
|
633
|
+
ip: ctx.request.connection.remoteAddress
|
|
634
|
+
});
|
|
635
|
+
ctx.response.statusCode = 403;
|
|
636
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
637
|
+
ctx.response.end('Forbidden');
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// SECURITY: Block dotfiles (.env, .git, .htaccess, etc.)
|
|
642
|
+
const filename = path.basename(resolvedPath);
|
|
643
|
+
if (filename.startsWith('.')) {
|
|
644
|
+
logger.warn({
|
|
645
|
+
code: 'MC_SECURITY_DOTFILE_BLOCKED',
|
|
646
|
+
message: 'Dotfile access blocked',
|
|
647
|
+
filename: filename,
|
|
648
|
+
ip: ctx.request.connection.remoteAddress
|
|
649
|
+
});
|
|
650
|
+
ctx.response.statusCode = 403;
|
|
651
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
652
|
+
ctx.response.end('Forbidden');
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
455
655
|
|
|
456
|
-
|
|
656
|
+
// Check if file exists
|
|
657
|
+
fs.exists(resolvedPath, function (exist) {
|
|
457
658
|
if (!exist) {
|
|
458
659
|
ctx.response.statusCode = 404;
|
|
459
|
-
ctx.response.
|
|
660
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
661
|
+
ctx.response.end('Not Found');
|
|
460
662
|
return;
|
|
461
663
|
}
|
|
462
664
|
|
|
463
|
-
|
|
464
|
-
|
|
665
|
+
// Get file stats
|
|
666
|
+
let finalPath = resolvedPath;
|
|
667
|
+
const stats = fs.statSync(resolvedPath);
|
|
668
|
+
|
|
669
|
+
// If directory, try to serve index.html
|
|
670
|
+
if (stats.isDirectory()) {
|
|
671
|
+
finalPath = path.join(resolvedPath, 'index.html');
|
|
672
|
+
|
|
673
|
+
// Check if index.html exists
|
|
674
|
+
if (!fs.existsSync(finalPath)) {
|
|
675
|
+
ctx.response.statusCode = 403;
|
|
676
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
677
|
+
ctx.response.end('Forbidden');
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
465
680
|
}
|
|
466
681
|
|
|
467
|
-
|
|
682
|
+
// Read and serve the file
|
|
683
|
+
fs.readFile(finalPath, function(err, data) {
|
|
468
684
|
if (err) {
|
|
685
|
+
logger.error({
|
|
686
|
+
code: 'MC_ERR_FILE_READ',
|
|
687
|
+
message: 'Error reading static file',
|
|
688
|
+
path: finalPath,
|
|
689
|
+
error: err.message
|
|
690
|
+
});
|
|
469
691
|
ctx.response.statusCode = 500;
|
|
470
|
-
ctx.response.
|
|
692
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
693
|
+
ctx.response.end('Internal Server Error');
|
|
471
694
|
} else {
|
|
472
|
-
const
|
|
473
|
-
|
|
695
|
+
const ext = path.extname(finalPath);
|
|
696
|
+
const mimeType = $that.router.findMimeType(ext);
|
|
697
|
+
ctx.response.setHeader('Content-Type', mimeType || 'application/octet-stream');
|
|
698
|
+
ctx.response.setHeader('X-Content-Type-Options', 'nosniff');
|
|
474
699
|
ctx.response.end(data);
|
|
475
700
|
}
|
|
476
701
|
});
|
|
@@ -489,7 +714,9 @@ class MasterControl {
|
|
|
489
714
|
// 3. Request Body Parsing (always needed)
|
|
490
715
|
$that.pipeline.use(async (ctx, next) => {
|
|
491
716
|
// Parse body using MasterRequest
|
|
492
|
-
|
|
717
|
+
// Pass entire context for backward compatibility (v1.3.x)
|
|
718
|
+
// getRequestParam() will extract request and requrl from context
|
|
719
|
+
const params = await $that.request.getRequestParam(ctx, ctx.response);
|
|
493
720
|
|
|
494
721
|
// Merge parsed params into context
|
|
495
722
|
if (params && params.query) {
|
|
@@ -514,7 +741,15 @@ class MasterControl {
|
|
|
514
741
|
// 4. HSTS Header (if enabled for HTTPS)
|
|
515
742
|
$that.pipeline.use(async (ctx, next) => {
|
|
516
743
|
if ($that.serverProtocol === 'https' && $that._hstsEnabled) {
|
|
517
|
-
|
|
744
|
+
// Use configured HSTS values (not hardcoded)
|
|
745
|
+
let hstsValue = `max-age=${$that._hstsMaxAge}`;
|
|
746
|
+
if ($that._hstsIncludeSubDomains) {
|
|
747
|
+
hstsValue += '; includeSubDomains';
|
|
748
|
+
}
|
|
749
|
+
if ($that._hstsPreload) {
|
|
750
|
+
hstsValue += '; preload';
|
|
751
|
+
}
|
|
752
|
+
ctx.response.setHeader('Strict-Transport-Security', hstsValue);
|
|
518
753
|
}
|
|
519
754
|
await next();
|
|
520
755
|
});
|
|
@@ -585,6 +820,13 @@ class MasterControl {
|
|
|
585
820
|
|
|
586
821
|
start(server){
|
|
587
822
|
this.server = server;
|
|
823
|
+
|
|
824
|
+
// Apply any pending server settings that were called before start()
|
|
825
|
+
if (this._pendingServerSettings) {
|
|
826
|
+
console.log('[MasterControl] Applying pending server settings');
|
|
827
|
+
this.serverSettings(this._pendingServerSettings);
|
|
828
|
+
this._pendingServerSettings = null;
|
|
829
|
+
}
|
|
588
830
|
}
|
|
589
831
|
|
|
590
832
|
startMVC(foldername){
|
|
@@ -619,7 +861,6 @@ class MasterControl {
|
|
|
619
861
|
'MasterRouter': './MasterRouter',
|
|
620
862
|
'MasterRequest': './MasterRequest',
|
|
621
863
|
'MasterCors': './MasterCors',
|
|
622
|
-
'MasterSession': './MasterSession',
|
|
623
864
|
'SessionSecurity': './security/SessionSecurity',
|
|
624
865
|
'MasterSocket': './MasterSocket',
|
|
625
866
|
'MasterHtml': './MasterHtml',
|
|
@@ -631,7 +872,12 @@ class MasterControl {
|
|
|
631
872
|
for(var i = 0; i < requiredList.length; i++){
|
|
632
873
|
const moduleName = requiredList[i];
|
|
633
874
|
const modulePath = modulePathMap[moduleName] || './' + moduleName;
|
|
634
|
-
require(modulePath);
|
|
875
|
+
const module = require(modulePath);
|
|
876
|
+
|
|
877
|
+
// Special handling for SessionSecurity to avoid circular dependency
|
|
878
|
+
if (moduleName === 'SessionSecurity' && module.MasterSessionSecurity) {
|
|
879
|
+
this.session = new module.MasterSessionSecurity();
|
|
880
|
+
}
|
|
635
881
|
}
|
|
636
882
|
}
|
|
637
883
|
}
|
package/MasterCors.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// version 0.0.3 - robust origin handling (all envs), creds-safe reflection, function origins, extended Vary
|
|
2
|
-
var master = require('./MasterControl');
|
|
3
2
|
|
|
4
3
|
// todo - res.setHeader('Access-Control-Request-Method', '*');
|
|
5
4
|
class MasterCors{
|
|
@@ -198,4 +197,4 @@ class MasterCors{
|
|
|
198
197
|
}
|
|
199
198
|
}
|
|
200
199
|
|
|
201
|
-
|
|
200
|
+
module.exports = { MasterCors };
|