mastercontroller 1.2.12 → 1.2.13

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/MasterRouter.js CHANGED
@@ -1,4 +1,4 @@
1
- // version 0.0.248
1
+ // version 0.0.249
2
2
 
3
3
  var master = require('./MasterControl');
4
4
  var toolClass = require('./MasterTools');
@@ -7,6 +7,17 @@ var path = require('path');
7
7
  var currentRoute = {};
8
8
  var tools = new toolClass();
9
9
 
10
+ // Enhanced error handling
11
+ const { handleRoutingError, handleControllerError, sendErrorResponse } = require('./MasterBackendErrorHandler');
12
+ const { logger } = require('./MasterErrorLogger');
13
+ const { performanceTracker, errorHandlerMiddleware } = require('./MasterErrorMiddleware');
14
+
15
+ // Security - Input validation and sanitization
16
+ const { validator, detectPathTraversal, detectSQLInjection, detectCommandInjection } = require('./MasterValidator');
17
+ const { escapeHTML } = require('./MasterSanitizer');
18
+
19
+ const isDevelopment = process.env.NODE_ENV !== 'production' && process.env.master === 'development';
20
+
10
21
  var normalizePaths = function(requestPath, routePath, requestParams){
11
22
  var obj = {
12
23
  requestPath : "",
@@ -21,8 +32,14 @@ var tools = new toolClass();
21
32
  routeItem = routePathList[i];
22
33
  if(routeItem){
23
34
  if(routeItem.indexOf(":") > -1){
24
- requestParams[routeItem.replace(":", "")] = requestItem;
25
- routePathList[i] = requestItem;
35
+ const paramName = routeItem.replace(":", "");
36
+ const paramValue = requestItem;
37
+
38
+ // Security: Sanitize route parameter
39
+ const sanitizedValue = sanitizeRouteParam(paramName, paramValue);
40
+
41
+ requestParams[paramName] = sanitizedValue;
42
+ routePathList[i] = sanitizedValue;
26
43
  }
27
44
  }
28
45
  }
@@ -32,10 +49,66 @@ var tools = new toolClass();
32
49
  return obj;
33
50
  }
34
51
 
52
+ /**
53
+ * Sanitize route parameter to prevent injection attacks
54
+ */
55
+ var sanitizeRouteParam = function(paramName, paramValue) {
56
+ if (!paramValue || typeof paramValue !== 'string') {
57
+ return paramValue;
58
+ }
59
+
60
+ // Check for path traversal attempts
61
+ const pathCheck = detectPathTraversal(paramValue);
62
+ if (!pathCheck.safe) {
63
+ logger.warn({
64
+ code: 'MC_SECURITY_PATH_TRAVERSAL',
65
+ message: 'Path traversal attempt detected in route parameter',
66
+ param: paramName,
67
+ value: paramValue
68
+ });
69
+
70
+ // Remove dangerous content
71
+ return paramValue.replace(/\.\./g, '').replace(/\.\//g, '');
72
+ }
73
+
74
+ // Check for SQL injection attempts
75
+ const sqlCheck = detectSQLInjection(paramValue);
76
+ if (!sqlCheck.safe) {
77
+ logger.warn({
78
+ code: 'MC_SECURITY_SQL_INJECTION',
79
+ message: 'SQL injection attempt detected in route parameter',
80
+ param: paramName,
81
+ value: paramValue
82
+ });
83
+
84
+ // Escape to prevent injection
85
+ return escapeHTML(paramValue);
86
+ }
87
+
88
+ // Check for command injection attempts
89
+ const cmdCheck = detectCommandInjection(paramValue);
90
+ if (!cmdCheck.safe) {
91
+ logger.warn({
92
+ code: 'MC_SECURITY_COMMAND_INJECTION',
93
+ message: 'Command injection attempt detected in route parameter',
94
+ param: paramName,
95
+ value: paramValue
96
+ });
97
+
98
+ // Remove dangerous characters
99
+ return paramValue.replace(/[;&|`$()]/g, '');
100
+ }
101
+
102
+ // Basic sanitization for all params
103
+ return escapeHTML(paramValue);
104
+ }
105
+
35
106
  var processRoutes = function(requestObject, emitter, routeObject){
36
107
  var routeList = routeObject.routes;
37
108
  var root = routeObject.root;
38
109
  var isComponent = routeObject.isComponent;
110
+ var currentRouteBeingProcessed = null; // Track current route for better error messages
111
+
39
112
  try{
40
113
  // Ensure routes is an array
41
114
  if(!Array.isArray(routeList)){
@@ -51,56 +124,109 @@ var tools = new toolClass();
51
124
  if(routeList.length > 0){
52
125
  // loop through routes
53
126
  for(var item in routeList){
127
+ // Store current route for error handling
128
+ currentRouteBeingProcessed = {
129
+ path: routeList[item].path,
130
+ toController: routeList[item].toController,
131
+ toAction: routeList[item].toAction,
132
+ type: routeList[item].type
133
+ };
134
+
135
+ try {
136
+ requestObject.toController = routeList[item].toController;
137
+ requestObject.toAction = routeList[item].toAction;
138
+ var pathObj = normalizePaths(requestObject.pathName, routeList[item].path, requestObject.params);
139
+
140
+ // if we find the route that matches the request
141
+ if(pathObj.requestPath === pathObj.routePath && routeList[item].type === requestObject.type){
142
+
143
+ // call Constraint
144
+ if(typeof routeList[item].constraint === "function"){
145
+
146
+ var newObj = {};
147
+ //tools.combineObjects(newObj, master.controllerList);
148
+ newObj.next = function(){
149
+ currentRoute.root = root;
150
+ currentRoute.pathName = requestObject.pathName;
151
+ currentRoute.toAction = requestObject.toAction;
152
+ currentRoute.toController = requestObject.toController;
153
+ currentRoute.response = requestObject.response;
154
+ currentRoute.isComponent = isComponent;
155
+ currentRoute.routeDef = currentRouteBeingProcessed; // Add route definition
156
+ emitter.emit("routeConstraintGood", requestObject);
157
+ };
158
+
159
+ // Wrap constraint execution with error handling
160
+ try {
161
+ routeList[item].constraint.call(newObj, requestObject);
162
+ } catch(constraintError) {
163
+ const routeError = handleRoutingError(
164
+ requestObject.pathName,
165
+ [],
166
+ {
167
+ type: 'CONSTRAINT_ERROR',
168
+ route: currentRouteBeingProcessed,
169
+ error: constraintError
170
+ }
171
+ );
172
+ logger.error({
173
+ code: 'MC_ERR_ROUTE_CONSTRAINT',
174
+ message: `Route constraint failed for ${currentRouteBeingProcessed.path}`,
175
+ route: currentRouteBeingProcessed,
176
+ error: constraintError.message,
177
+ stack: constraintError.stack
178
+ });
179
+ throw constraintError;
180
+ }
181
+
182
+ return true;
183
+ }else{
54
184
 
55
- requestObject.toController = routeList[item].toController;
56
- requestObject.toAction = routeList[item].toAction;
57
- var pathObj = normalizePaths(requestObject.pathName, routeList[item].path, requestObject.params);
58
- // if we find the route that matches the request
59
- if(pathObj.requestPath === pathObj.routePath && routeList[item].type === requestObject.type){
60
-
61
- // call Constraint
62
- if(typeof routeList[item].constraint === "function"){
63
-
64
- var newObj = {};
65
- //tools.combineObjects(newObj, master.controllerList);
66
- newObj.next = function(){
67
185
  currentRoute.root = root;
68
186
  currentRoute.pathName = requestObject.pathName;
69
187
  currentRoute.toAction = requestObject.toAction;
70
188
  currentRoute.toController = requestObject.toController;
71
189
  currentRoute.response = requestObject.response;
72
190
  currentRoute.isComponent = isComponent;
191
+ currentRoute.routeDef = currentRouteBeingProcessed; // Add route definition
73
192
  emitter.emit("routeConstraintGood", requestObject);
74
- };
75
- routeList[item].constraint.call(newObj, requestObject);
76
- return true;
77
- }else{
78
-
79
- currentRoute.root = root;
80
- currentRoute.pathName = requestObject.pathName;
81
- currentRoute.toAction = requestObject.toAction;
82
- currentRoute.toController = requestObject.toController;
83
- currentRoute.response = requestObject.response;
84
- currentRoute.isComponent = isComponent;
85
- emitter.emit("routeConstraintGood", requestObject);
193
+ return true;
194
+ }
195
+
196
+ }
197
+
198
+ if(pathObj.requestPath === pathObj.routePath && "options" ===requestObject.type.toLowerCase()){
199
+ // this means that the request is correct but its an options request means its the browser checking to see if the request is allowed
200
+ requestObject.response.writeHead(200, {'Content-Type': 'application/json'});
201
+ requestObject.response.end(JSON.stringify({"done": "true"}));
86
202
  return true;
87
203
  }
88
-
89
- }
204
+ } catch(routeProcessError) {
205
+ // Log the specific route that failed
206
+ logger.error({
207
+ code: 'MC_ERR_ROUTE_PROCESS',
208
+ message: `Failed to process route: ${currentRouteBeingProcessed.path}`,
209
+ route: currentRouteBeingProcessed,
210
+ requestPath: requestObject.pathName,
211
+ error: routeProcessError.message,
212
+ stack: routeProcessError.stack
213
+ });
90
214
 
91
- if(pathObj.requestPath === pathObj.routePath && "options" ===requestObject.type.toLowerCase()){
92
- // this means that the request is correct but its an options request means its the browser checking to see if the request is allowed
93
- requestObject.response.writeHead(200, {'Content-Type': 'application/json'});
94
- requestObject.response.end(JSON.stringify({"done": "true"}));
95
- return true;
215
+ // Re-throw to be caught by outer try-catch
216
+ throw routeProcessError;
96
217
  }
97
-
218
+
98
219
  };
99
220
  return -1;
100
221
  }
101
222
  }
102
223
  catch(e){
103
- throw new Error("Error processing routes: " + e.stack);
224
+ // Enhanced error message with route context
225
+ const errorDetails = currentRouteBeingProcessed
226
+ ? `\n\nFailing Route:\n Path: ${currentRouteBeingProcessed.path}\n Controller: ${currentRouteBeingProcessed.toController}#${currentRouteBeingProcessed.toAction}\n Method: ${currentRouteBeingProcessed.type.toUpperCase()}\n\nRequest:\n Path: ${requestObject.pathName}\n Method: ${requestObject.type.toUpperCase()}`
227
+ : '';
228
+
229
+ throw new Error(`Error processing routes: ${e.message}${errorDetails}\n\nOriginal Stack:\n${e.stack}`);
104
230
  }
105
231
  };
106
232
 
@@ -257,40 +383,113 @@ class MasterRouter {
257
383
 
258
384
  _call(requestObject){
259
385
 
386
+ // Start performance tracking
387
+ const requestId = `${Date.now()}-${Math.random()}`;
388
+ performanceTracker.start(requestId, requestObject);
389
+
260
390
  tools.combineObjects(master.requestList, requestObject);
261
391
  requestObject = master.requestList;
262
392
  var Control = null;
393
+
263
394
  try{
264
- Control = require(path.join(currentRoute.root, 'app', 'controllers', `${tools.firstLetterlowercase(requestObject.toController)}Controller`));
265
- }catch(e){
395
+ // Try to load controller
266
396
  try{
267
- Control = require(path.join(currentRoute.root, 'app', 'controllers', `${tools.firstLetterUppercase(requestObject.toController)}Controller`));
268
- }catch(e2){
269
- console.log(`Cannot find controller name - ${requestObject.toController}`);
397
+ Control = require(path.join(currentRoute.root, 'app', 'controllers', `${tools.firstLetterlowercase(requestObject.toController)}Controller`));
398
+ }catch(e){
399
+ try{
400
+ Control = require(path.join(currentRoute.root, 'app', 'controllers', `${tools.firstLetterUppercase(requestObject.toController)}Controller`));
401
+ }catch(e2){
402
+ // Controller not found - handle error
403
+ const error = handleControllerError(
404
+ new Error(`Controller not found: ${requestObject.toController}Controller`),
405
+ requestObject.toController,
406
+ requestObject.toAction,
407
+ requestObject.pathName,
408
+ currentRoute.routeDef // Pass route definition
409
+ );
410
+
411
+ sendErrorResponse(requestObject.response, error, requestObject.pathName);
412
+ performanceTracker.end(requestId);
413
+ return;
414
+ }
270
415
  }
271
- }
272
- tools.combineObjectPrototype(Control, master.controllerList);
273
- Control.prototype.__namespace = Control.name;
274
- Control.prototype.__requestObject = requestObject;
275
- Control.prototype.__currentRoute = currentRoute;
276
- Control.prototype.__response = requestObject.response;
277
- Control.prototype.__request = requestObject.request;
278
- var control = new Control(requestObject);
279
- var _callEmit = new EventEmitter();
280
-
281
- _callEmit.on("controller", function(){
282
- control.next = function(){
283
- control.__callAfterAction(control, requestObject);
284
- }
285
- //
286
- control[requestObject.toAction](requestObject);
287
- });
288
-
289
- // check if before function is avaliable and wait for it to return
290
- if(control.__hasBeforeAction(control, requestObject)){
291
- control.__callBeforeAction(control, requestObject, _callEmit);
292
- }else{
293
- _callEmit.emit("controller");
416
+
417
+ tools.combineObjectPrototype(Control, master.controllerList);
418
+ Control.prototype.__namespace = Control.name;
419
+ Control.prototype.__requestObject = requestObject;
420
+ Control.prototype.__currentRoute = currentRoute;
421
+ Control.prototype.__response = requestObject.response;
422
+ Control.prototype.__request = requestObject.request;
423
+ var control = new Control(requestObject);
424
+ var _callEmit = new EventEmitter();
425
+
426
+ _callEmit.on("controller", function(){
427
+ try {
428
+ control.next = function(){
429
+ control.__callAfterAction(control, requestObject);
430
+ }
431
+
432
+ // Check if action exists
433
+ if (typeof control[requestObject.toAction] !== 'function') {
434
+ throw new Error(`Action '${requestObject.toAction}' not found in controller ${requestObject.toController}`);
435
+ }
436
+
437
+ // Wrap action with error handling
438
+ const wrappedAction = errorHandlerMiddleware(
439
+ control[requestObject.toAction],
440
+ requestObject.toController,
441
+ requestObject.toAction
442
+ );
443
+
444
+ // Execute action
445
+ Promise.resolve(wrappedAction.call(control, requestObject))
446
+ .then(() => {
447
+ performanceTracker.end(requestId);
448
+ })
449
+ .catch((error) => {
450
+ const mcError = handleControllerError(
451
+ error,
452
+ requestObject.toController,
453
+ requestObject.toAction,
454
+ requestObject.pathName,
455
+ currentRoute.routeDef // Pass route definition
456
+ );
457
+ sendErrorResponse(requestObject.response, mcError, requestObject.pathName);
458
+ performanceTracker.end(requestId);
459
+ });
460
+
461
+ } catch (error) {
462
+ // Action execution error
463
+ const mcError = handleControllerError(
464
+ error,
465
+ requestObject.toController,
466
+ requestObject.toAction,
467
+ requestObject.pathName,
468
+ currentRoute.routeDef // Pass route definition
469
+ );
470
+ sendErrorResponse(requestObject.response, mcError, requestObject.pathName);
471
+ performanceTracker.end(requestId);
472
+ }
473
+ });
474
+
475
+ // check if before function is avaliable and wait for it to return
476
+ if(control.__hasBeforeAction(control, requestObject)){
477
+ control.__callBeforeAction(control, requestObject, _callEmit);
478
+ }else{
479
+ _callEmit.emit("controller");
480
+ }
481
+
482
+ } catch (error) {
483
+ // General error
484
+ const mcError = handleControllerError(
485
+ error,
486
+ requestObject.toController,
487
+ requestObject.toAction,
488
+ requestObject.pathName,
489
+ currentRoute.routeDef // Pass route definition
490
+ );
491
+ sendErrorResponse(requestObject.response, mcError, requestObject.pathName);
492
+ performanceTracker.end(requestId);
294
493
  }
295
494
 
296
495
  }
@@ -319,8 +518,16 @@ class MasterRouter {
319
518
  }
320
519
 
321
520
  if(routeFound === false){
322
- master.error.log(`Cannot find route for path ${requestObject.pathName}`, "warn");
323
- master.error.callHttpStatus(404, requestObject.response);
521
+ // Enhanced 404 handling
522
+ const allRoutes = [];
523
+ for (const route of routes) {
524
+ if (this._routes[route] && this._routes[route].routes) {
525
+ allRoutes.push(...this._routes[route].routes);
526
+ }
527
+ }
528
+
529
+ const mcError = handleRoutingError(requestObject.pathName, allRoutes);
530
+ sendErrorResponse(requestObject.response, mcError, requestObject.pathName);
324
531
  }
325
532
 
326
533
  }