mastercontroller 1.3.0 → 1.3.1

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.
@@ -4,7 +4,8 @@
4
4
  "Bash(rm:*)",
5
5
  "Bash(mv:*)",
6
6
  "Bash(node -e:*)",
7
- "Bash(find:*)"
7
+ "Bash(find:*)",
8
+ "Bash(node -c:*)"
8
9
  ],
9
10
  "deny": [],
10
11
  "ask": []
package/MasterControl.js CHANGED
@@ -270,6 +270,7 @@ class MasterControl {
270
270
  'MasterError',
271
271
  'MasterCors',
272
272
  'MasterSession',
273
+ 'SessionSecurity',
273
274
  'MasterSocket',
274
275
  'MasterHtml',
275
276
  'MasterTemplate',
@@ -619,6 +620,7 @@ class MasterControl {
619
620
  'MasterRequest': './MasterRequest',
620
621
  'MasterCors': './MasterCors',
621
622
  'MasterSession': './MasterSession',
623
+ 'SessionSecurity': './security/SessionSecurity',
622
624
  'MasterSocket': './MasterSocket',
623
625
  'MasterHtml': './MasterHtml',
624
626
  'MasterTemplate': './MasterTemplate',
package/MasterPipeline.js CHANGED
@@ -44,7 +44,7 @@ class MasterPipeline {
44
44
  /**
45
45
  * Run: Add terminal middleware that ends the pipeline
46
46
  *
47
- * Terminal middleware signature: async (ctx) => { /* send response */ }
47
+ * Terminal middleware signature: async (ctx) => { ... send response ... }
48
48
  * - Does NOT call next()
49
49
  * - Must send response
50
50
  *
package/MasterTools.js CHANGED
@@ -51,21 +51,48 @@ class MasterTools{
51
51
  };
52
52
 
53
53
  encrypt(payload, secret){
54
- let iv = crypto.randomBytes(16).toString('hex').slice(0, 16);
55
- let key = crypto.createHash('sha256').update(String(secret)).digest('base64').substr(0, 32);
56
- crypto.createCipheriv('aes-256-cbc', key, iv);
57
- var cipher = crypto.createCipher(algorithm, key);
58
- var crypted = cipher.update(payload,'utf8','hex');
59
- crypted += cipher.final('hex');
60
- return crypted;
54
+ // Generate random IV (16 bytes for AES)
55
+ const iv = crypto.randomBytes(16);
56
+
57
+ // Create 256-bit key from secret
58
+ const key = crypto.createHash('sha256').update(String(secret)).digest();
59
+
60
+ // Create cipher with AES-256-CBC
61
+ const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
62
+
63
+ // Encrypt payload
64
+ let encrypted = cipher.update(String(payload), 'utf8', 'hex');
65
+ encrypted += cipher.final('hex');
66
+
67
+ // Prepend IV to encrypted data (IV is not secret, needed for decryption)
68
+ return iv.toString('hex') + ':' + encrypted;
61
69
  }
62
-
70
+
63
71
  decrypt(encryption, secret){
64
- var key = crypto.createHash('sha256').update(String(secret)).digest('base64').substr(0, 32);
65
- var decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
66
- var dec = decipher.update(encryption,'hex','utf8');
67
- dec += decipher.final('utf8');
68
- return dec;
72
+ try {
73
+ // Split IV and encrypted data
74
+ const parts = encryption.split(':');
75
+ if (parts.length !== 2) {
76
+ throw new Error('Invalid encrypted data format');
77
+ }
78
+
79
+ const iv = Buffer.from(parts[0], 'hex');
80
+ const encryptedData = parts[1];
81
+
82
+ // Create 256-bit key from secret
83
+ const key = crypto.createHash('sha256').update(String(secret)).digest();
84
+
85
+ // Create decipher
86
+ const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
87
+
88
+ // Decrypt
89
+ let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
90
+ decrypted += decipher.final('utf8');
91
+
92
+ return decrypted;
93
+ } catch (error) {
94
+ throw new Error('Decryption failed: ' + error.message);
95
+ }
69
96
  }
70
97
 
71
98
  generateRandomKey(hash){
package/README.md CHANGED
@@ -790,94 +790,230 @@ master.cors.init({
790
790
 
791
791
  ## Sessions
792
792
 
793
- ### `master.sessions.init(options)`
793
+ MasterController provides **two session systems**:
794
+ - **`master.session`** (NEW) - Secure, Rails/Django-style sessions with automatic regeneration and protection (RECOMMENDED)
795
+ - **`master.sessions`** (LEGACY) - Original cookie-based session API (backward compatibility only)
794
796
 
795
- Initialize sessions (auto-registers with middleware pipeline).
797
+ ### Secure Sessions (NEW - Recommended)
798
+
799
+ #### `master.session.init(options)`
800
+
801
+ Initialize secure sessions with Rails/Django-style `req.session` object (auto-registers with middleware pipeline).
796
802
 
797
803
  ```javascript
798
- master.sessions.init({
799
- secret: 'your-secret-key',
800
- maxAge: 900000, // 15 minutes
801
- httpOnly: true,
802
- secure: true, // HTTPS only
803
- sameSite: true,
804
- path: '/'
804
+ // Environment-specific configuration
805
+ const isProduction = master.environmentType === 'production';
806
+
807
+ master.session.init({
808
+ cookieName: 'mc_session',
809
+ maxAge: isProduction ? 3600000 : 86400000, // Production: 1 hour, Dev: 24 hours
810
+ httpOnly: true, // Prevent JavaScript access (XSS protection)
811
+ secure: isProduction, // HTTPS only in production
812
+ sameSite: isProduction ? 'strict' : 'lax', // CSRF protection
813
+ rolling: true, // Extend session on each request
814
+ regenerateInterval: 900000, // Regenerate session ID every 15 minutes
815
+ useFingerprint: false // Session hijacking detection (opt-in)
805
816
  });
806
817
  ```
807
818
 
808
- ### Session API
819
+ **Security Features:**
820
+ - ✅ 32-byte (256-bit) session IDs (cryptographically secure)
821
+ - ✅ Automatic session regeneration (prevents fixation attacks)
822
+ - ✅ HttpOnly cookies (prevents XSS cookie theft)
823
+ - ✅ Secure flag for HTTPS (prevents MITM attacks)
824
+ - ✅ SameSite CSRF protection
825
+ - ✅ Rolling sessions (extends expiry on activity)
826
+ - ✅ Automatic cleanup of expired sessions
827
+ - ✅ Optional fingerprinting (detects hijacking)
828
+
829
+ #### Using Sessions in Controllers
809
830
 
810
- #### `master.sessions.set(name, data, response, secret, options)`
811
- Create a session.
831
+ Sessions are accessed via `obj.request.session` object:
812
832
 
813
833
  ```javascript
814
834
  class AuthController {
815
835
  login(obj) {
816
836
  const user = authenticateUser(obj.params.formData);
817
837
 
818
- master.sessions.set('user', user, obj.response);
838
+ // Set session data (Rails/Express style)
839
+ obj.request.session.userId = user.id;
840
+ obj.request.session.username = user.name;
841
+ obj.request.session.loggedInAt = Date.now();
819
842
 
820
843
  this.redirect('/dashboard');
821
844
  }
845
+
846
+ logout(obj) {
847
+ // Destroy entire session
848
+ master.session.destroy(obj.request, obj.response);
849
+ this.redirect('/');
850
+ }
822
851
  }
823
852
  ```
824
853
 
825
- #### `master.sessions.get(name, request, secret)`
826
- Retrieve session data.
827
-
828
854
  ```javascript
829
855
  class DashboardController {
830
856
  index(obj) {
831
- const user = master.sessions.get('user', obj.request);
857
+ // Read session data
858
+ const userId = obj.request.session.userId;
832
859
 
833
- if (!user) {
860
+ if (!userId) {
834
861
  this.redirect('/login');
835
862
  return;
836
863
  }
837
864
 
838
- this.render('dashboard', { user });
865
+ this.render('dashboard', { userId });
839
866
  }
840
867
  }
841
868
  ```
842
869
 
843
- #### `master.sessions.delete(name, response)`
844
- Delete a session.
870
+ #### Session Management API
871
+
872
+ **`master.session.destroy(req, res)`** - Destroy session completely
845
873
 
846
874
  ```javascript
847
- class AuthController {
848
- logout(obj) {
849
- master.sessions.delete('user', obj.response);
850
- this.redirect('/');
851
- }
852
- }
875
+ master.session.destroy(obj.request, obj.response);
853
876
  ```
854
877
 
855
- #### `master.sessions.reset()`
856
- Clear all sessions (useful for testing).
878
+ **`master.session.touch(sessionId)`** - Extend session expiry
857
879
 
858
880
  ```javascript
859
- master.sessions.reset();
881
+ master.session.touch(obj.request.sessionId);
882
+ ```
883
+
884
+ **`master.session.getSessionCount()`** - Get active session count (monitoring)
885
+
886
+ ```javascript
887
+ const count = master.session.getSessionCount();
888
+ console.log(`Active sessions: ${count}`);
889
+ ```
890
+
891
+ **`master.session.clearAllSessions()`** - Clear all sessions (testing only)
892
+
893
+ ```javascript
894
+ master.session.clearAllSessions();
895
+ ```
896
+
897
+ #### Environment-Specific Best Practices
898
+
899
+ ```javascript
900
+ // Get recommended settings
901
+ const settings = master.session.getBestPractices('production');
902
+ master.session.init(settings);
903
+ ```
904
+
905
+ **Production Settings:**
906
+ - Secure: true (HTTPS only)
907
+ - SameSite: 'strict' (maximum CSRF protection)
908
+ - MaxAge: 1 hour (short-lived sessions)
909
+ - RegenerateInterval: 15 minutes
910
+
911
+ **Development Settings:**
912
+ - Secure: false (allow HTTP)
913
+ - SameSite: 'lax' (easier testing)
914
+ - MaxAge: 24 hours (convenient for development)
915
+ - RegenerateInterval: 1 hour
916
+
917
+ ---
918
+
919
+ ### Legacy Sessions (Backward Compatibility)
920
+
921
+ **⚠️ DEPRECATED: Use `master.session` (singular) for new projects.**
922
+
923
+ The original `master.sessions` (plural) API is maintained for backward compatibility but lacks modern security features.
924
+
925
+ #### `master.sessions.init(options)`
926
+
927
+ Initialize legacy sessions (auto-registers with middleware pipeline).
928
+
929
+ ```javascript
930
+ master.sessions.init({
931
+ secret: 'your-secret-key',
932
+ maxAge: 900000, // 15 minutes
933
+ httpOnly: true,
934
+ secure: true, // HTTPS only
935
+ sameSite: 'strict', // Must be string: 'strict', 'lax', or 'none'
936
+ path: '/'
937
+ });
938
+ ```
939
+
940
+ #### Legacy Session API
941
+
942
+ **`master.sessions.set(name, data, response, secret, options)`** - Create a session
943
+
944
+ ```javascript
945
+ master.sessions.set('user', userData, obj.response);
860
946
  ```
861
947
 
862
- ### Cookie Methods
948
+ **`master.sessions.get(name, request, secret)`** - Retrieve session data
863
949
 
864
- Direct cookie access:
950
+ ```javascript
951
+ const user = master.sessions.get('user', obj.request);
952
+ ```
953
+
954
+ **`master.sessions.delete(name, response)`** - Delete a session
865
955
 
866
- #### `master.sessions.setCookie(name, value, response, options)`
956
+ ```javascript
957
+ master.sessions.delete('user', obj.response);
958
+ ```
959
+
960
+ **`master.sessions.reset()`** - Clear all sessions
961
+
962
+ ```javascript
963
+ master.sessions.reset();
964
+ ```
965
+
966
+ #### Legacy Cookie Methods
967
+
968
+ **`master.sessions.setCookie(name, value, response, options)`**
867
969
  ```javascript
868
970
  master.sessions.setCookie('theme', 'dark', obj.response);
869
971
  ```
870
972
 
871
- #### `master.sessions.getCookie(name, request, secret)`
973
+ **`master.sessions.getCookie(name, request, secret)`**
872
974
  ```javascript
873
975
  const theme = master.sessions.getCookie('theme', obj.request);
874
976
  ```
875
977
 
876
- #### `master.sessions.deleteCookie(name, response, options)`
978
+ **`master.sessions.deleteCookie(name, response, options)`**
877
979
  ```javascript
878
980
  master.sessions.deleteCookie('theme', obj.response);
879
981
  ```
880
982
 
983
+ #### Migration Guide: Legacy → Secure Sessions
984
+
985
+ **Old (master.sessions):**
986
+ ```javascript
987
+ // Set
988
+ master.sessions.set('user', userData, obj.response);
989
+
990
+ // Get
991
+ const user = master.sessions.get('user', obj.request);
992
+
993
+ // Delete
994
+ master.sessions.delete('user', obj.response);
995
+ ```
996
+
997
+ **New (master.session):**
998
+ ```javascript
999
+ // Set (Rails/Express style)
1000
+ obj.request.session.user = userData;
1001
+
1002
+ // Get
1003
+ const user = obj.request.session.user;
1004
+
1005
+ // Delete
1006
+ master.session.destroy(obj.request, obj.response);
1007
+ ```
1008
+
1009
+ **Benefits of migration:**
1010
+ - ✅ Automatic session regeneration (prevents fixation)
1011
+ - ✅ 32-byte session IDs (stronger than 20-byte)
1012
+ - ✅ Rolling sessions (better UX)
1013
+ - ✅ Automatic cleanup (no memory leaks)
1014
+ - ✅ Rails/Express-style API (more familiar)
1015
+ - ✅ No broken encryption (legacy has crypto bugs)
1016
+
881
1017
  ---
882
1018
 
883
1019
  ## Security
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.3.0"
21
+ "version": "1.3.1"
22
22
  }
@@ -25,8 +25,8 @@ class SessionSecurity {
25
25
  this.domain = options.domain || null;
26
26
  this.path = options.path || '/';
27
27
 
28
- // Session fingerprinting
29
- this.useFingerprint = options.useFingerprint !== false;
28
+ // Session fingerprinting (disabled by default like ASP.NET Core)
29
+ this.useFingerprint = options.useFingerprint === true;
30
30
 
31
31
  // Start cleanup interval
32
32
  this._startCleanup();
@@ -407,6 +407,103 @@ const SESSION_BEST_PRACTICES = {
407
407
  }
408
408
  };
409
409
 
410
+ // MasterController Integration
411
+ const master = require('../MasterControl');
412
+
413
+ // Create MasterController-compatible wrapper
414
+ class MasterSessionSecurity {
415
+ constructor() {
416
+ this._instance = null;
417
+ this._options = {};
418
+ }
419
+
420
+ /**
421
+ * Initialize session security (Rails/Django style)
422
+ * Auto-registers with middleware pipeline
423
+ */
424
+ init(options = {}) {
425
+ this._options = options;
426
+ this._instance = new SessionSecurity(options);
427
+
428
+ // Auto-register with pipeline if available
429
+ if (master.pipeline) {
430
+ master.pipeline.use(this._instance.middleware());
431
+ }
432
+
433
+ return this;
434
+ }
435
+
436
+ /**
437
+ * Get middleware function
438
+ */
439
+ middleware() {
440
+ if (!this._instance) {
441
+ this.init();
442
+ }
443
+ return this._instance.middleware();
444
+ }
445
+
446
+ /**
447
+ * Destroy session
448
+ */
449
+ destroy(req, res) {
450
+ if (!this._instance) {
451
+ throw new Error('SessionSecurity not initialized. Call master.session.init() first.');
452
+ }
453
+ return this._instance.destroySession(req, res);
454
+ }
455
+
456
+ /**
457
+ * Get session by ID
458
+ */
459
+ getSession(sessionId) {
460
+ if (!this._instance) {
461
+ throw new Error('SessionSecurity not initialized. Call master.session.init() first.');
462
+ }
463
+ return this._instance.getSession(sessionId);
464
+ }
465
+
466
+ /**
467
+ * Touch session (extend expiry)
468
+ */
469
+ touch(sessionId) {
470
+ if (!this._instance) {
471
+ throw new Error('SessionSecurity not initialized. Call master.session.init() first.');
472
+ }
473
+ return this._instance.touch(sessionId);
474
+ }
475
+
476
+ /**
477
+ * Get session count (monitoring)
478
+ */
479
+ getSessionCount() {
480
+ if (!this._instance) {
481
+ throw new Error('SessionSecurity not initialized. Call master.session.init() first.');
482
+ }
483
+ return this._instance.getSessionCount();
484
+ }
485
+
486
+ /**
487
+ * Clear all sessions (testing only)
488
+ */
489
+ clearAllSessions() {
490
+ if (!this._instance) {
491
+ throw new Error('SessionSecurity not initialized. Call master.session.init() first.');
492
+ }
493
+ return this._instance.clearAllSessions();
494
+ }
495
+
496
+ /**
497
+ * Get recommended settings for environment
498
+ */
499
+ getBestPractices(env) {
500
+ return SESSION_BEST_PRACTICES[env] || SESSION_BEST_PRACTICES.development;
501
+ }
502
+ }
503
+
504
+ // Auto-register with MasterController
505
+ master.extend("session", MasterSessionSecurity);
506
+
410
507
  module.exports = {
411
508
  SessionSecurity,
412
509
  session,