mastercontroller 1.3.0 → 1.3.2
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 +4 -1
- package/MasterAction.js +137 -23
- package/MasterActionFilters.js +197 -92
- package/MasterControl.js +265 -44
- package/MasterHtml.js +226 -143
- package/MasterPipeline.js +1 -1
- package/MasterRequest.js +202 -24
- package/MasterSocket.js +6 -1
- package/MasterTools.js +428 -13
- package/README.md +2364 -309
- package/SECURITY-FIXES-v1.3.2.md +614 -0
- 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/package.json +1 -1
- package/security/SecurityEnforcement.js +241 -0
- package/security/SessionSecurity.js +100 -2
- 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,38 +298,56 @@ class MasterControl {
|
|
|
256
298
|
setupServer(type, credentials ){
|
|
257
299
|
try {
|
|
258
300
|
var $that = this;
|
|
259
|
-
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
+
for (const moduleName in internalModules) {
|
|
323
|
+
try {
|
|
324
|
+
const modulePath = internalModules[moduleName];
|
|
325
|
+
const module = require(modulePath);
|
|
326
|
+
|
|
327
|
+
// Special handling for SessionSecurity to avoid circular dependency
|
|
328
|
+
if (moduleName === 'SessionSecurity' && module.MasterSessionSecurity) {
|
|
329
|
+
$that.session = new module.MasterSessionSecurity();
|
|
330
|
+
}
|
|
331
|
+
// Most modules auto-register via master.extend() at module load time
|
|
332
|
+
} catch (e) {
|
|
333
|
+
console.error(`[MasterControl] Failed to load ${moduleName}:`, e && e.message);
|
|
334
|
+
}
|
|
281
335
|
}
|
|
282
336
|
|
|
337
|
+
// Initialize global error handlers
|
|
338
|
+
setupGlobalErrorHandlers();
|
|
339
|
+
|
|
283
340
|
// Register core middleware that must run for framework to function
|
|
284
341
|
$that._registerCoreMiddleware();
|
|
285
342
|
|
|
286
343
|
if(type === "http"){
|
|
287
344
|
$that.serverProtocol = "http";
|
|
288
|
-
|
|
345
|
+
const server = http.createServer(async function(req, res) {
|
|
289
346
|
$that.serverRun(req, res);
|
|
290
347
|
});
|
|
348
|
+
// Set server immediately so config can access it
|
|
349
|
+
$that.server = server;
|
|
350
|
+
return server;
|
|
291
351
|
}
|
|
292
352
|
if(type === "https"){
|
|
293
353
|
$that.serverProtocol = "https";
|
|
@@ -296,14 +356,42 @@ class MasterControl {
|
|
|
296
356
|
$that._initializeTlsFromEnv();
|
|
297
357
|
credentials = $that._tlsOptions;
|
|
298
358
|
}
|
|
299
|
-
// Apply secure defaults if missing
|
|
359
|
+
// Apply secure defaults if missing (2026 security standards)
|
|
300
360
|
if(credentials){
|
|
301
|
-
|
|
361
|
+
// Default to TLS 1.3 for security (2026 standard)
|
|
362
|
+
// TLS 1.2 still supported but not default
|
|
363
|
+
if(!credentials.minVersion){
|
|
364
|
+
credentials.minVersion = 'TLSv1.3';
|
|
365
|
+
console.log('[MasterControl] TLS 1.3 enabled by default (recommended for 2026)');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Secure cipher suites (Mozilla Intermediate configuration - 2026)
|
|
369
|
+
// Supports TLS 1.3 and TLS 1.2 for backward compatibility
|
|
370
|
+
if(!credentials.ciphers){
|
|
371
|
+
credentials.ciphers = [
|
|
372
|
+
// TLS 1.3 cipher suites (strongest)
|
|
373
|
+
'TLS_AES_256_GCM_SHA384',
|
|
374
|
+
'TLS_CHACHA20_POLY1305_SHA256',
|
|
375
|
+
'TLS_AES_128_GCM_SHA256',
|
|
376
|
+
// TLS 1.2 cipher suites (backward compatibility)
|
|
377
|
+
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
|
378
|
+
'ECDHE-RSA-AES256-GCM-SHA384',
|
|
379
|
+
'ECDHE-ECDSA-CHACHA20-POLY1305',
|
|
380
|
+
'ECDHE-RSA-CHACHA20-POLY1305',
|
|
381
|
+
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
|
382
|
+
'ECDHE-RSA-AES128-GCM-SHA256'
|
|
383
|
+
].join(':');
|
|
384
|
+
console.log('[MasterControl] Secure cipher suites configured (Mozilla Intermediate)');
|
|
385
|
+
}
|
|
386
|
+
|
|
302
387
|
if(credentials.honorCipherOrder === undefined){ credentials.honorCipherOrder = true; }
|
|
303
388
|
if(!credentials.ALPNProtocols){ credentials.ALPNProtocols = ['h2', 'http/1.1']; }
|
|
304
|
-
|
|
389
|
+
const server = https.createServer(credentials, async function(req, res) {
|
|
305
390
|
$that.serverRun(req, res);
|
|
306
391
|
});
|
|
392
|
+
// Set server immediately so config can access it
|
|
393
|
+
$that.server = server;
|
|
394
|
+
return server;
|
|
307
395
|
}else{
|
|
308
396
|
throw "Credentials needed to setup https"
|
|
309
397
|
}
|
|
@@ -315,18 +403,69 @@ class MasterControl {
|
|
|
315
403
|
}
|
|
316
404
|
}
|
|
317
405
|
|
|
318
|
-
|
|
319
|
-
|
|
406
|
+
/**
|
|
407
|
+
* Creates an HTTP server that 301-redirects to HTTPS counterpart
|
|
408
|
+
* SECURITY: Validates host header to prevent open redirect attacks
|
|
409
|
+
*
|
|
410
|
+
* @param {Number} redirectPort - Port to listen on (usually 80)
|
|
411
|
+
* @param {String} bindHost - Host to bind to (e.g., '0.0.0.0')
|
|
412
|
+
* @param {Array<String>} allowedHosts - Whitelist of allowed hostnames (REQUIRED for security)
|
|
413
|
+
* @returns {http.Server} - HTTP server instance
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* // Production usage (MUST specify allowed hosts)
|
|
417
|
+
* const redirectServer = master.startHttpToHttpsRedirect(80, '0.0.0.0', [
|
|
418
|
+
* 'example.com',
|
|
419
|
+
* 'www.example.com',
|
|
420
|
+
* 'api.example.com'
|
|
421
|
+
* ]);
|
|
422
|
+
*
|
|
423
|
+
* @security CRITICAL: Always provide allowedHosts in production to prevent open redirect attacks
|
|
424
|
+
*/
|
|
425
|
+
startHttpToHttpsRedirect(redirectPort, bindHost, allowedHosts = []){
|
|
320
426
|
var $that = this;
|
|
427
|
+
|
|
428
|
+
// Security warning if no hosts specified
|
|
429
|
+
if (allowedHosts.length === 0) {
|
|
430
|
+
console.warn('[MasterControl] ⚠️ SECURITY WARNING: startHttpToHttpsRedirect() called without allowedHosts.');
|
|
431
|
+
console.warn('[MasterControl] This is vulnerable to open redirect attacks. Specify allowed hosts:');
|
|
432
|
+
console.warn('[MasterControl] master.startHttpToHttpsRedirect(80, "0.0.0.0", ["example.com", "www.example.com"])');
|
|
433
|
+
}
|
|
434
|
+
|
|
321
435
|
return http.createServer(function (req, res) {
|
|
322
436
|
try{
|
|
323
437
|
var host = req.headers['host'] || '';
|
|
324
|
-
|
|
438
|
+
var hostname = host.split(':')[0]; // Remove port number
|
|
439
|
+
|
|
440
|
+
// CRITICAL SECURITY: Validate host header to prevent open redirect attacks
|
|
441
|
+
if (allowedHosts.length > 0) {
|
|
442
|
+
if (!allowedHosts.includes(hostname)) {
|
|
443
|
+
logger.warn({
|
|
444
|
+
code: 'MC_SECURITY_INVALID_HOST',
|
|
445
|
+
message: 'HTTP redirect blocked: invalid host header',
|
|
446
|
+
host: hostname,
|
|
447
|
+
ip: req.connection.remoteAddress
|
|
448
|
+
});
|
|
449
|
+
res.statusCode = 400;
|
|
450
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
451
|
+
res.end('Bad Request: Invalid host header');
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Redirect to HTTPS with validated host
|
|
325
457
|
var location = 'https://' + host + req.url;
|
|
326
458
|
res.statusCode = 301;
|
|
327
459
|
res.setHeader('Location', location);
|
|
460
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
328
461
|
res.end();
|
|
329
462
|
}catch(e){
|
|
463
|
+
logger.error({
|
|
464
|
+
code: 'MC_ERR_REDIRECT',
|
|
465
|
+
message: 'HTTP to HTTPS redirect failed',
|
|
466
|
+
error: e.message,
|
|
467
|
+
stack: e.stack
|
|
468
|
+
});
|
|
330
469
|
res.statusCode = 500;
|
|
331
470
|
res.end();
|
|
332
471
|
}
|
|
@@ -446,30 +585,90 @@ class MasterControl {
|
|
|
446
585
|
_registerCoreMiddleware(){
|
|
447
586
|
var $that = this;
|
|
448
587
|
|
|
449
|
-
// 1. Static File Serving
|
|
588
|
+
// 1. Static File Serving (with path traversal protection)
|
|
450
589
|
$that.pipeline.use(async (ctx, next) => {
|
|
451
590
|
if (ctx.isStatic) {
|
|
452
|
-
//
|
|
453
|
-
let
|
|
591
|
+
// SECURITY: Prevent path traversal attacks
|
|
592
|
+
let requestedPath = ctx.request.url;
|
|
593
|
+
|
|
594
|
+
// Normalize the path and resolve it
|
|
595
|
+
const publicRoot = path.resolve($that.root || '.');
|
|
596
|
+
const safePath = path.join(publicRoot, requestedPath);
|
|
597
|
+
const resolvedPath = path.resolve(safePath);
|
|
598
|
+
|
|
599
|
+
// CRITICAL: Ensure resolved path is within public root (prevents ../ attacks)
|
|
600
|
+
if (!resolvedPath.startsWith(publicRoot)) {
|
|
601
|
+
logger.warn({
|
|
602
|
+
code: 'MC_SECURITY_PATH_TRAVERSAL',
|
|
603
|
+
message: 'Path traversal attack blocked',
|
|
604
|
+
requestedPath: requestedPath,
|
|
605
|
+
resolvedPath: resolvedPath,
|
|
606
|
+
ip: ctx.request.connection.remoteAddress
|
|
607
|
+
});
|
|
608
|
+
ctx.response.statusCode = 403;
|
|
609
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
610
|
+
ctx.response.end('Forbidden');
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
454
613
|
|
|
455
|
-
|
|
614
|
+
// SECURITY: Block dotfiles (.env, .git, .htaccess, etc.)
|
|
615
|
+
const filename = path.basename(resolvedPath);
|
|
616
|
+
if (filename.startsWith('.')) {
|
|
617
|
+
logger.warn({
|
|
618
|
+
code: 'MC_SECURITY_DOTFILE_BLOCKED',
|
|
619
|
+
message: 'Dotfile access blocked',
|
|
620
|
+
filename: filename,
|
|
621
|
+
ip: ctx.request.connection.remoteAddress
|
|
622
|
+
});
|
|
623
|
+
ctx.response.statusCode = 403;
|
|
624
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
625
|
+
ctx.response.end('Forbidden');
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Check if file exists
|
|
630
|
+
fs.exists(resolvedPath, function (exist) {
|
|
456
631
|
if (!exist) {
|
|
457
632
|
ctx.response.statusCode = 404;
|
|
458
|
-
ctx.response.
|
|
633
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
634
|
+
ctx.response.end('Not Found');
|
|
459
635
|
return;
|
|
460
636
|
}
|
|
461
637
|
|
|
462
|
-
|
|
463
|
-
|
|
638
|
+
// Get file stats
|
|
639
|
+
let finalPath = resolvedPath;
|
|
640
|
+
const stats = fs.statSync(resolvedPath);
|
|
641
|
+
|
|
642
|
+
// If directory, try to serve index.html
|
|
643
|
+
if (stats.isDirectory()) {
|
|
644
|
+
finalPath = path.join(resolvedPath, 'index.html');
|
|
645
|
+
|
|
646
|
+
// Check if index.html exists
|
|
647
|
+
if (!fs.existsSync(finalPath)) {
|
|
648
|
+
ctx.response.statusCode = 403;
|
|
649
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
650
|
+
ctx.response.end('Forbidden');
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
464
653
|
}
|
|
465
654
|
|
|
466
|
-
|
|
655
|
+
// Read and serve the file
|
|
656
|
+
fs.readFile(finalPath, function(err, data) {
|
|
467
657
|
if (err) {
|
|
658
|
+
logger.error({
|
|
659
|
+
code: 'MC_ERR_FILE_READ',
|
|
660
|
+
message: 'Error reading static file',
|
|
661
|
+
path: finalPath,
|
|
662
|
+
error: err.message
|
|
663
|
+
});
|
|
468
664
|
ctx.response.statusCode = 500;
|
|
469
|
-
ctx.response.
|
|
665
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
666
|
+
ctx.response.end('Internal Server Error');
|
|
470
667
|
} else {
|
|
471
|
-
const
|
|
472
|
-
|
|
668
|
+
const ext = path.extname(finalPath);
|
|
669
|
+
const mimeType = $that.router.findMimeType(ext);
|
|
670
|
+
ctx.response.setHeader('Content-Type', mimeType || 'application/octet-stream');
|
|
671
|
+
ctx.response.setHeader('X-Content-Type-Options', 'nosniff');
|
|
473
672
|
ctx.response.end(data);
|
|
474
673
|
}
|
|
475
674
|
});
|
|
@@ -488,7 +687,9 @@ class MasterControl {
|
|
|
488
687
|
// 3. Request Body Parsing (always needed)
|
|
489
688
|
$that.pipeline.use(async (ctx, next) => {
|
|
490
689
|
// Parse body using MasterRequest
|
|
491
|
-
|
|
690
|
+
// Pass entire context for backward compatibility (v1.3.x)
|
|
691
|
+
// getRequestParam() will extract request and requrl from context
|
|
692
|
+
const params = await $that.request.getRequestParam(ctx, ctx.response);
|
|
492
693
|
|
|
493
694
|
// Merge parsed params into context
|
|
494
695
|
if (params && params.query) {
|
|
@@ -513,7 +714,15 @@ class MasterControl {
|
|
|
513
714
|
// 4. HSTS Header (if enabled for HTTPS)
|
|
514
715
|
$that.pipeline.use(async (ctx, next) => {
|
|
515
716
|
if ($that.serverProtocol === 'https' && $that._hstsEnabled) {
|
|
516
|
-
|
|
717
|
+
// Use configured HSTS values (not hardcoded)
|
|
718
|
+
let hstsValue = `max-age=${$that._hstsMaxAge}`;
|
|
719
|
+
if ($that._hstsIncludeSubDomains) {
|
|
720
|
+
hstsValue += '; includeSubDomains';
|
|
721
|
+
}
|
|
722
|
+
if ($that._hstsPreload) {
|
|
723
|
+
hstsValue += '; preload';
|
|
724
|
+
}
|
|
725
|
+
ctx.response.setHeader('Strict-Transport-Security', hstsValue);
|
|
517
726
|
}
|
|
518
727
|
await next();
|
|
519
728
|
});
|
|
@@ -584,6 +793,13 @@ class MasterControl {
|
|
|
584
793
|
|
|
585
794
|
start(server){
|
|
586
795
|
this.server = server;
|
|
796
|
+
|
|
797
|
+
// Apply any pending server settings that were called before start()
|
|
798
|
+
if (this._pendingServerSettings) {
|
|
799
|
+
console.log('[MasterControl] Applying pending server settings');
|
|
800
|
+
this.serverSettings(this._pendingServerSettings);
|
|
801
|
+
this._pendingServerSettings = null;
|
|
802
|
+
}
|
|
587
803
|
}
|
|
588
804
|
|
|
589
805
|
startMVC(foldername){
|
|
@@ -618,7 +834,7 @@ class MasterControl {
|
|
|
618
834
|
'MasterRouter': './MasterRouter',
|
|
619
835
|
'MasterRequest': './MasterRequest',
|
|
620
836
|
'MasterCors': './MasterCors',
|
|
621
|
-
'
|
|
837
|
+
'SessionSecurity': './security/SessionSecurity',
|
|
622
838
|
'MasterSocket': './MasterSocket',
|
|
623
839
|
'MasterHtml': './MasterHtml',
|
|
624
840
|
'MasterTemplate': './MasterTemplate',
|
|
@@ -629,7 +845,12 @@ class MasterControl {
|
|
|
629
845
|
for(var i = 0; i < requiredList.length; i++){
|
|
630
846
|
const moduleName = requiredList[i];
|
|
631
847
|
const modulePath = modulePathMap[moduleName] || './' + moduleName;
|
|
632
|
-
require(modulePath);
|
|
848
|
+
const module = require(modulePath);
|
|
849
|
+
|
|
850
|
+
// Special handling for SessionSecurity to avoid circular dependency
|
|
851
|
+
if (moduleName === 'SessionSecurity' && module.MasterSessionSecurity) {
|
|
852
|
+
this.session = new module.MasterSessionSecurity();
|
|
853
|
+
}
|
|
633
854
|
}
|
|
634
855
|
}
|
|
635
856
|
}
|