mastercontroller 1.3.14 → 1.3.16
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/MasterAction.js +302 -62
- package/MasterActionFilters.js +556 -82
- package/MasterControl.js +77 -38
- package/MasterCors.js +61 -19
- package/MasterPipeline.js +29 -6
- package/MasterRequest.js +579 -102
- package/MasterRouter.js +458 -75
- package/MasterSocket.js +380 -15
- package/MasterTemp.js +292 -10
- package/MasterTimeout.js +420 -64
- package/MasterTools.js +478 -77
- package/README.md +505 -0
- package/package.json +1 -1
- package/.claude/settings.local.json +0 -29
- package/.github/workflows/ci.yml +0 -317
- package/PERFORMANCE_SECURITY_AUDIT.md +0 -677
- package/SENIOR_ENGINEER_AUDIT.md +0 -2477
- package/VERIFICATION_CHECKLIST.md +0 -726
- package/log/mastercontroller.log +0 -2
- package/test-json-empty-body.js +0 -76
- package/test-raw-body-preservation.js +0 -128
- package/test-v1.3.4-fixes.js +0 -129
package/MasterControl.js
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
// MasterControl - by Alexander rich
|
|
2
2
|
// version 1.0.252
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
const url = require('url');
|
|
5
|
+
const fileserver = require('fs');
|
|
6
|
+
const http = require('http');
|
|
7
|
+
const https = require('https');
|
|
8
|
+
const tls = require('tls');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const globSearch = require("glob");
|
|
12
|
+
const crypto = require('crypto'); // CRITICAL FIX: For ETag generation
|
|
13
|
+
|
|
14
|
+
// HTTP Status Code Constants
|
|
15
|
+
const HTTP_STATUS = {
|
|
16
|
+
OK: 200,
|
|
17
|
+
MOVED_PERMANENTLY: 301,
|
|
18
|
+
FOUND: 302,
|
|
19
|
+
NOT_MODIFIED: 304,
|
|
20
|
+
BAD_REQUEST: 400,
|
|
21
|
+
FORBIDDEN: 403,
|
|
22
|
+
NOT_FOUND: 404,
|
|
23
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
24
|
+
INTERNAL_ERROR: 500
|
|
25
|
+
};
|
|
14
26
|
|
|
15
27
|
// Enhanced error handling - setup global handlers
|
|
16
28
|
const { setupGlobalErrorHandlers } = require('./error/MasterErrorMiddleware');
|
|
@@ -176,7 +188,10 @@ class MasterControl {
|
|
|
176
188
|
return false;
|
|
177
189
|
};
|
|
178
190
|
|
|
179
|
-
|
|
191
|
+
logger.info({
|
|
192
|
+
code: 'MC_INFO_PROTOTYPE_PROTECTION',
|
|
193
|
+
message: 'Prototype pollution protection initialized'
|
|
194
|
+
});
|
|
180
195
|
}
|
|
181
196
|
|
|
182
197
|
// extends class methods to be used inside of the view class using the THIS keyword
|
|
@@ -291,7 +306,7 @@ class MasterControl {
|
|
|
291
306
|
|
|
292
307
|
// Enhanced: Support both relative (to master.root) and absolute paths
|
|
293
308
|
// If folderLocation is absolute, use it directly; otherwise join with master.root
|
|
294
|
-
|
|
309
|
+
let rootFolderLocation;
|
|
295
310
|
if (path.isAbsolute(folderLocation)) {
|
|
296
311
|
// Absolute path provided - use it directly
|
|
297
312
|
rootFolderLocation = path.join(folderLocation, innerFolder);
|
|
@@ -301,16 +316,20 @@ class MasterControl {
|
|
|
301
316
|
}
|
|
302
317
|
|
|
303
318
|
// Structure is always: {rootFolderLocation}/config/initializers/config.js
|
|
304
|
-
|
|
319
|
+
const configPath =path.join(rootFolderLocation, 'config', 'initializers', 'config.js');
|
|
305
320
|
if(fs.existsSync(configPath)){
|
|
306
321
|
require(configPath);
|
|
307
322
|
}else{
|
|
308
|
-
|
|
323
|
+
logger.error({
|
|
324
|
+
code: 'MC_ERR_CONFIG_NOT_FOUND',
|
|
325
|
+
message: 'Cannot find config file',
|
|
326
|
+
path: configPath
|
|
327
|
+
});
|
|
309
328
|
}
|
|
310
329
|
|
|
311
330
|
// Structure is always: {rootFolderLocation}/config/routes.js
|
|
312
|
-
|
|
313
|
-
|
|
331
|
+
const routePath =path.join(rootFolderLocation, 'config', 'routes.js');
|
|
332
|
+
const routeObject ={
|
|
314
333
|
isComponent : true,
|
|
315
334
|
root : rootFolderLocation
|
|
316
335
|
}
|
|
@@ -318,7 +337,11 @@ class MasterControl {
|
|
|
318
337
|
if(fs.existsSync(routePath)){
|
|
319
338
|
require(routePath);
|
|
320
339
|
}else{
|
|
321
|
-
|
|
340
|
+
logger.error({
|
|
341
|
+
code: 'MC_ERR_ROUTES_NOT_FOUND',
|
|
342
|
+
message: 'Cannot find routes file',
|
|
343
|
+
path: routePath
|
|
344
|
+
});
|
|
322
345
|
}
|
|
323
346
|
}
|
|
324
347
|
|
|
@@ -335,7 +358,7 @@ class MasterControl {
|
|
|
335
358
|
|
|
336
359
|
if(settings.httpPort || settings.requestTimeout){
|
|
337
360
|
this.server.timeout = settings.requestTimeout;
|
|
338
|
-
|
|
361
|
+
const host =settings.hostname || settings.host || settings.http;
|
|
339
362
|
if(host){
|
|
340
363
|
this.server.listen(settings.httpPort, host);
|
|
341
364
|
}else{
|
|
@@ -391,7 +414,7 @@ class MasterControl {
|
|
|
391
414
|
// sets up https or http server protocals
|
|
392
415
|
setupServer(type, credentials ){
|
|
393
416
|
try {
|
|
394
|
-
|
|
417
|
+
const $that = this;
|
|
395
418
|
|
|
396
419
|
// SECURITY: Initialize prototype pollution protection
|
|
397
420
|
this._initPrototypePollutionProtection();
|
|
@@ -547,7 +570,7 @@ class MasterControl {
|
|
|
547
570
|
* @security CRITICAL: Always provide allowedHosts in production to prevent open redirect attacks
|
|
548
571
|
*/
|
|
549
572
|
startHttpToHttpsRedirect(redirectPort, bindHost, allowedHosts = []){
|
|
550
|
-
|
|
573
|
+
const $that = this;
|
|
551
574
|
|
|
552
575
|
// Security warning if no hosts specified
|
|
553
576
|
if (allowedHosts.length === 0) {
|
|
@@ -558,8 +581,8 @@ class MasterControl {
|
|
|
558
581
|
|
|
559
582
|
return http.createServer(function (req, res) {
|
|
560
583
|
try{
|
|
561
|
-
|
|
562
|
-
|
|
584
|
+
const host = req.headers['host'] || '';
|
|
585
|
+
const hostname = host.split(':')[0]; // Remove port number
|
|
563
586
|
|
|
564
587
|
// CRITICAL SECURITY: Validate host header to prevent open redirect attacks
|
|
565
588
|
if (allowedHosts.length > 0) {
|
|
@@ -707,7 +730,7 @@ class MasterControl {
|
|
|
707
730
|
* This includes: static files, body parsing, scoped services, routing, error handling
|
|
708
731
|
*/
|
|
709
732
|
_registerCoreMiddleware(){
|
|
710
|
-
|
|
733
|
+
const $that = this;
|
|
711
734
|
|
|
712
735
|
// 1. Static File Serving (with path traversal protection)
|
|
713
736
|
$that.pipeline.use(async (ctx, next) => {
|
|
@@ -750,17 +773,17 @@ class MasterControl {
|
|
|
750
773
|
return;
|
|
751
774
|
}
|
|
752
775
|
|
|
753
|
-
// Check if file exists
|
|
754
|
-
fs.
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
}
|
|
776
|
+
// Check if file exists (use synchronous check for better performance in middleware)
|
|
777
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
778
|
+
ctx.response.statusCode = 404;
|
|
779
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
780
|
+
ctx.response.end('Not Found');
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
761
783
|
|
|
762
|
-
|
|
763
|
-
|
|
784
|
+
// Get file stats
|
|
785
|
+
let finalPath = resolvedPath;
|
|
786
|
+
try {
|
|
764
787
|
const stats = fs.statSync(resolvedPath);
|
|
765
788
|
|
|
766
789
|
// If directory, try to serve index.html
|
|
@@ -877,7 +900,19 @@ class MasterControl {
|
|
|
877
900
|
}
|
|
878
901
|
});
|
|
879
902
|
}
|
|
880
|
-
})
|
|
903
|
+
} catch (error) {
|
|
904
|
+
// Handle file stat errors
|
|
905
|
+
logger.error({
|
|
906
|
+
code: 'MC_ERR_FILE_STAT',
|
|
907
|
+
message: 'Error accessing static file',
|
|
908
|
+
path: resolvedPath,
|
|
909
|
+
error: error.message
|
|
910
|
+
});
|
|
911
|
+
ctx.response.statusCode = 500;
|
|
912
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
913
|
+
ctx.response.end('Internal Server Error');
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
881
916
|
|
|
882
917
|
return; // Terminal - don't call next()
|
|
883
918
|
}
|
|
@@ -968,7 +1003,7 @@ class MasterControl {
|
|
|
968
1003
|
}
|
|
969
1004
|
|
|
970
1005
|
async serverRun(req, res){
|
|
971
|
-
|
|
1006
|
+
const $that = this;
|
|
972
1007
|
console.log("path", `${req.method} ${req.url}`);
|
|
973
1008
|
|
|
974
1009
|
// Create request context for middleware pipeline
|
|
@@ -1016,7 +1051,7 @@ class MasterControl {
|
|
|
1016
1051
|
var rootFolderLocation = path.join(this.root, foldername);
|
|
1017
1052
|
|
|
1018
1053
|
// Structure is always: {rootFolderLocation}/routes.js
|
|
1019
|
-
|
|
1054
|
+
const routePath =path.join(rootFolderLocation, 'routes.js');
|
|
1020
1055
|
var route = {
|
|
1021
1056
|
isComponent : false,
|
|
1022
1057
|
root : `${this.root}`
|
|
@@ -1025,14 +1060,18 @@ class MasterControl {
|
|
|
1025
1060
|
if(fs.existsSync(routePath)){
|
|
1026
1061
|
require(routePath);
|
|
1027
1062
|
}else{
|
|
1028
|
-
|
|
1063
|
+
logger.error({
|
|
1064
|
+
code: 'MC_ERR_ROUTES_NOT_FOUND',
|
|
1065
|
+
message: 'Cannot find routes file',
|
|
1066
|
+
path: routePath
|
|
1067
|
+
});
|
|
1029
1068
|
}
|
|
1030
1069
|
}
|
|
1031
1070
|
|
|
1032
1071
|
|
|
1033
1072
|
// builds and calls all the required tools to have master running completely
|
|
1034
1073
|
addInternalTools(requiredList){
|
|
1035
|
-
if(requiredList
|
|
1074
|
+
if(Array.isArray(requiredList)){
|
|
1036
1075
|
// Map module names to their new organized paths
|
|
1037
1076
|
const modulePathMap = {
|
|
1038
1077
|
'MasterPipeline': './MasterPipeline',
|
package/MasterCors.js
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
// version 0.0.3 - robust origin handling (all envs), creds-safe reflection, function origins, extended Vary
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const { logger } = require('./error/MasterErrorLogger');
|
|
4
|
+
|
|
5
|
+
// HTTP Status Code Constants
|
|
6
|
+
const HTTP_STATUS = {
|
|
7
|
+
NO_CONTENT: 204,
|
|
8
|
+
BAD_REQUEST: 400
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// CORS Header Name Constants
|
|
12
|
+
const CORS_HEADERS = {
|
|
13
|
+
ALLOW_ORIGIN: 'Access-Control-Allow-Origin',
|
|
14
|
+
ALLOW_METHODS: 'Access-Control-Allow-Methods',
|
|
15
|
+
ALLOW_HEADERS: 'Access-Control-Allow-Headers',
|
|
16
|
+
ALLOW_CREDENTIALS: 'Access-Control-Allow-Credentials',
|
|
17
|
+
MAX_AGE: 'Access-Control-Max-Age',
|
|
18
|
+
EXPOSE_HEADERS: 'Access-Control-Expose-Headers',
|
|
19
|
+
REQUEST_HEADERS: 'Access-Control-Request-Headers',
|
|
20
|
+
REQUEST_METHOD: 'Access-Control-Request-Method',
|
|
21
|
+
VARY: 'Vary'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// todo - res.setHeader('Access-Control-Request-Method', '*');
|
|
4
25
|
class MasterCors{
|
|
5
26
|
|
|
6
27
|
// Lazy-load master to avoid circular dependency (Google-style lazy initialization)
|
|
@@ -16,7 +37,10 @@ class MasterCors{
|
|
|
16
37
|
this.options = options;
|
|
17
38
|
}
|
|
18
39
|
else{
|
|
19
|
-
|
|
40
|
+
logger.warn({
|
|
41
|
+
code: 'MC_CORS_OPTIONS_MISSING',
|
|
42
|
+
message: 'CORS options missing'
|
|
43
|
+
});
|
|
20
44
|
}
|
|
21
45
|
|
|
22
46
|
// Auto-register with pipeline if available
|
|
@@ -32,7 +56,15 @@ class MasterCors{
|
|
|
32
56
|
this.response = params.response;
|
|
33
57
|
this.request = params.request;
|
|
34
58
|
// Always signal that response may vary by Origin and requested headers/method
|
|
35
|
-
try {
|
|
59
|
+
try {
|
|
60
|
+
this.response.setHeader('Vary', 'Origin, Access-Control-Request-Headers, Access-Control-Request-Method');
|
|
61
|
+
} catch(error) {
|
|
62
|
+
logger.warn({
|
|
63
|
+
code: 'MC_CORS_VARY_HEADER_FAILED',
|
|
64
|
+
message: 'Failed to set Vary header',
|
|
65
|
+
error: error.message
|
|
66
|
+
});
|
|
67
|
+
}
|
|
36
68
|
this.configureOrigin();
|
|
37
69
|
this.configureMethods()
|
|
38
70
|
this.configureAllowedHeaders();
|
|
@@ -41,7 +73,10 @@ class MasterCors{
|
|
|
41
73
|
this.configureMaxAge();
|
|
42
74
|
}
|
|
43
75
|
else{
|
|
44
|
-
|
|
76
|
+
logger.warn({
|
|
77
|
+
code: 'MC_CORS_PARAMS_MISSING',
|
|
78
|
+
message: 'CORS response and request params missing'
|
|
79
|
+
});
|
|
45
80
|
}
|
|
46
81
|
}
|
|
47
82
|
|
|
@@ -63,7 +98,7 @@ class MasterCors{
|
|
|
63
98
|
|
|
64
99
|
if(this.options.origin === true){
|
|
65
100
|
// If credentials are enabled, reflect request origin per spec
|
|
66
|
-
|
|
101
|
+
const requestOrigin =this.request.headers.origin;
|
|
67
102
|
if (this.options.credentials === true && requestOrigin) {
|
|
68
103
|
this.setHeader('access-control-allow-origin', requestOrigin);
|
|
69
104
|
} else {
|
|
@@ -76,9 +111,9 @@ class MasterCors{
|
|
|
76
111
|
this.removeHeader('access-control-allow-origin');
|
|
77
112
|
}
|
|
78
113
|
|
|
79
|
-
if(this.options.origin
|
|
114
|
+
if(Array.isArray(this.options.origin)){
|
|
80
115
|
// Get the origin from the incoming request
|
|
81
|
-
|
|
116
|
+
const requestOrigin =this.request.headers.origin;
|
|
82
117
|
|
|
83
118
|
// Check if the request origin is in our allowed list
|
|
84
119
|
if(requestOrigin && this.options.origin.includes(requestOrigin)){
|
|
@@ -90,15 +125,22 @@ class MasterCors{
|
|
|
90
125
|
// Function predicate support: (origin, req) => boolean|string
|
|
91
126
|
if (typeof this.options.origin === 'function'){
|
|
92
127
|
try {
|
|
93
|
-
|
|
94
|
-
|
|
128
|
+
const requestOrigin = this.request.headers.origin;
|
|
129
|
+
const res = this.options.origin(requestOrigin, this.request);
|
|
95
130
|
if (res === true && requestOrigin){
|
|
96
131
|
this.setHeader('access-control-allow-origin', requestOrigin);
|
|
97
132
|
}
|
|
98
133
|
else if (typeof res === 'string' && res){
|
|
99
134
|
this.setHeader('access-control-allow-origin', res);
|
|
100
135
|
}
|
|
101
|
-
} catch(
|
|
136
|
+
} catch(error) {
|
|
137
|
+
logger.error({
|
|
138
|
+
code: 'MC_CORS_ORIGIN_FUNCTION_ERROR',
|
|
139
|
+
message: 'Error in origin function predicate',
|
|
140
|
+
error: error.message,
|
|
141
|
+
stack: error.stack
|
|
142
|
+
});
|
|
143
|
+
}
|
|
102
144
|
}
|
|
103
145
|
|
|
104
146
|
}
|
|
@@ -106,16 +148,16 @@ class MasterCors{
|
|
|
106
148
|
|
|
107
149
|
configureMethods(){
|
|
108
150
|
if(this.options.methods){
|
|
109
|
-
if(this.options.methods
|
|
110
|
-
|
|
151
|
+
if(Array.isArray(this.options.methods)){
|
|
152
|
+
const elements =this.options.methods.join(", ");
|
|
111
153
|
this.setHeader('access-control-allow-methods', elements);
|
|
112
154
|
}
|
|
113
155
|
}
|
|
114
156
|
}
|
|
115
157
|
|
|
116
158
|
configureAllowedHeaders(){
|
|
117
|
-
|
|
118
|
-
|
|
159
|
+
const requestheader =this.request.headers["access-control-request-headers"];
|
|
160
|
+
const $that =this;
|
|
119
161
|
if(this.options.allowedHeaders){
|
|
120
162
|
|
|
121
163
|
if($that.options.allowedHeaders === true){
|
|
@@ -135,8 +177,8 @@ class MasterCors{
|
|
|
135
177
|
this.setHeader("access-control-allow-headers", $that.options.allowedHeaders);
|
|
136
178
|
}
|
|
137
179
|
|
|
138
|
-
if($that.options.allowedHeaders
|
|
139
|
-
|
|
180
|
+
if(Array.isArray($that.options.allowedHeaders)){
|
|
181
|
+
const elements =$that.options.allowedHeaders.join(", ");
|
|
140
182
|
$that.request.headers['access-control-allow-headers'] = elements;
|
|
141
183
|
this.setHeader("access-control-allow-headers", elements);
|
|
142
184
|
}
|
|
@@ -158,8 +200,8 @@ class MasterCors{
|
|
|
158
200
|
this.setHeader('access-control-expose-headers', this.options.exposeHeaders);
|
|
159
201
|
}
|
|
160
202
|
|
|
161
|
-
if(this.options.exposeHeaders
|
|
162
|
-
|
|
203
|
+
if(Array.isArray(this.options.exposeHeaders)){
|
|
204
|
+
const elements =this.options.exposeHeaders.join(", ");
|
|
163
205
|
this.setHeader('access-control-expose-headers', elements);
|
|
164
206
|
}
|
|
165
207
|
|
|
@@ -187,7 +229,7 @@ class MasterCors{
|
|
|
187
229
|
* Handles both preflight OPTIONS requests and regular requests
|
|
188
230
|
*/
|
|
189
231
|
middleware() {
|
|
190
|
-
|
|
232
|
+
const $that =this;
|
|
191
233
|
|
|
192
234
|
return async (ctx, next) => {
|
|
193
235
|
// Handle preflight OPTIONS request
|
package/MasterPipeline.js
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
const { logger } = require('./error/MasterErrorLogger');
|
|
5
5
|
|
|
6
|
+
// HTTP Status Code Constants
|
|
7
|
+
const HTTP_STATUS = {
|
|
8
|
+
INTERNAL_ERROR: 500
|
|
9
|
+
};
|
|
10
|
+
|
|
6
11
|
class MasterPipeline {
|
|
7
12
|
constructor() {
|
|
8
13
|
this.middleware = [];
|
|
@@ -214,7 +219,7 @@ class MasterPipeline {
|
|
|
214
219
|
});
|
|
215
220
|
|
|
216
221
|
if (!context.response.headersSent) {
|
|
217
|
-
context.response.statusCode =
|
|
222
|
+
context.response.statusCode = HTTP_STATUS.INTERNAL_ERROR;
|
|
218
223
|
context.response.end('Internal Server Error');
|
|
219
224
|
}
|
|
220
225
|
return;
|
|
@@ -285,7 +290,11 @@ class MasterPipeline {
|
|
|
285
290
|
folders.forEach(folder => {
|
|
286
291
|
const dir = path.join(this._master.root, folder);
|
|
287
292
|
if (!fs.existsSync(dir)) {
|
|
288
|
-
|
|
293
|
+
logger.warn({
|
|
294
|
+
code: 'MC_MIDDLEWARE_FOLDER_NOT_FOUND',
|
|
295
|
+
message: 'Middleware folder not found',
|
|
296
|
+
folder: folder
|
|
297
|
+
});
|
|
289
298
|
return;
|
|
290
299
|
}
|
|
291
300
|
|
|
@@ -305,16 +314,30 @@ class MasterPipeline {
|
|
|
305
314
|
}
|
|
306
315
|
// Pattern 2: module.exports = { register: (master) => {} }
|
|
307
316
|
else if (middleware.register && typeof middleware.register === 'function') {
|
|
308
|
-
middleware.register(
|
|
317
|
+
middleware.register(this._master);
|
|
309
318
|
}
|
|
310
319
|
else {
|
|
311
|
-
|
|
320
|
+
logger.warn({
|
|
321
|
+
code: 'MC_MIDDLEWARE_INVALID_EXPORT',
|
|
322
|
+
message: 'Invalid middleware export',
|
|
323
|
+
file: `${folder}/${file}`
|
|
324
|
+
});
|
|
312
325
|
return;
|
|
313
326
|
}
|
|
314
327
|
|
|
315
|
-
|
|
328
|
+
logger.info({
|
|
329
|
+
code: 'MC_MIDDLEWARE_LOADED',
|
|
330
|
+
message: 'Middleware loaded',
|
|
331
|
+
file: `${folder}/${file}`
|
|
332
|
+
});
|
|
316
333
|
} catch (err) {
|
|
317
|
-
|
|
334
|
+
logger.error({
|
|
335
|
+
code: 'MC_MIDDLEWARE_LOAD_FAILED',
|
|
336
|
+
message: 'Failed to load middleware',
|
|
337
|
+
file: `${folder}/${file}`,
|
|
338
|
+
error: err.message,
|
|
339
|
+
stack: err.stack
|
|
340
|
+
});
|
|
318
341
|
}
|
|
319
342
|
});
|
|
320
343
|
});
|