mastercontroller 1.2.12 → 1.2.14

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/MasterHtml.js CHANGED
@@ -1,4 +1,4 @@
1
- // version 0.0.23
1
+ // version 0.0.25
2
2
 
3
3
  var master = require('./MasterControl');
4
4
  var fs = require('fs');
@@ -7,6 +7,14 @@ var toolClass = require('./MasterTools');
7
7
  var temp = new tempClass();
8
8
  var tools = new toolClass();
9
9
 
10
+ // Enhanced error handling
11
+ const { handleTemplateError } = require('./error/MasterBackendErrorHandler');
12
+ const { safeReadFile, safeFileExists } = require('./error/MasterErrorMiddleware');
13
+ const { logger } = require('./error/MasterErrorLogger');
14
+
15
+ // Security - Sanitization
16
+ const { sanitizeTemplateHTML, sanitizeUserHTML, escapeHTML } = require('./security/MasterSanitizer');
17
+
10
18
  class html {
11
19
 
12
20
  javaScriptSerializer(name, obj){
@@ -17,21 +25,41 @@ class html {
17
25
 
18
26
  // render partial views
19
27
  renderPartial(path, data){
20
-
28
+ try {
29
+ var partialViewUrl = `/app/views/${path}`;
30
+ var fullPath = master.router.currentRoute.root + partialViewUrl;
31
+
32
+ const fileResult = safeReadFile(fs, fullPath);
33
+
34
+ if (!fileResult.success) {
35
+ logger.warn({
36
+ code: 'MC_ERR_VIEW_NOT_FOUND',
37
+ message: `Partial view not found: ${path}`,
38
+ file: fullPath
39
+ });
40
+ return `<!-- Partial view not found: ${path} -->`;
41
+ }
21
42
 
22
- var partialViewUrl = `/app/views/${path}`;
23
- var filepartialView = fs.readFileSync(master.router.currentRoute.root + partialViewUrl, 'utf8');
43
+ var partialView = null;
44
+ if(master.overwrite.isTemplate){
45
+ partialView = master.overwrite.templateRender(data, "renderPartialView");
46
+ }
47
+ else{
48
+ partialView = temp.htmlBuilder(fileResult.content, data);
49
+ }
24
50
 
25
- var partialView = null;
26
- if(master.overwrite.isTemplate){
27
- partialView = master.overwrite.templateRender(data, "renderPartialView");
28
- }
29
- else{
30
- partialView = temp.htmlBuilder(filepartialView, data);
51
+ return partialView;
52
+ } catch (error) {
53
+ const mcError = handleTemplateError(error, path, data);
54
+ logger.error({
55
+ code: mcError.code,
56
+ message: mcError.message,
57
+ file: path,
58
+ originalError: error
59
+ });
60
+ return `<!-- Error rendering partial: ${path} -->`;
31
61
  }
32
62
 
33
- return partialView;
34
-
35
63
  }
36
64
 
37
65
  // render all your link tags styles given the folder location
@@ -447,7 +475,7 @@ class html {
447
475
  return rangeField;
448
476
  }
449
477
 
450
- // allows you to add data object to params
478
+ // allows you to add data object to params
451
479
  addDataToParams(data){
452
480
 
453
481
  //loop through data and add it to new oobjects prototype
@@ -457,7 +485,66 @@ class html {
457
485
  master.view.extend(newObj);
458
486
  }
459
487
  }
460
-
488
+
489
+ // ==================== Security Methods ====================
490
+
491
+ /**
492
+ * Sanitize user-generated HTML content
493
+ * Use this for any HTML that comes from user input
494
+ * @param {string} html - HTML content to sanitize
495
+ * @returns {string} - Sanitized HTML
496
+ */
497
+ sanitizeHTML(html) {
498
+ return sanitizeUserHTML(html);
499
+ }
500
+
501
+ /**
502
+ * Escape HTML special characters
503
+ * Use this to display user input as text (not HTML)
504
+ * @param {string} text - Text to escape
505
+ * @returns {string} - Escaped text safe for display
506
+ */
507
+ escapeHTML(text) {
508
+ return escapeHTML(text);
509
+ }
510
+
511
+ /**
512
+ * Render user content safely
513
+ * Sanitizes HTML and wraps in container
514
+ * @param {string} content - User-generated content
515
+ * @param {string} containerTag - HTML tag to wrap content (default: div)
516
+ * @param {object} attrs - Attributes for container
517
+ * @returns {string} - Safe HTML
518
+ */
519
+ renderUserContent(content, containerTag = 'div', attrs = {}) {
520
+ const sanitized = sanitizeUserHTML(content);
521
+
522
+ let attrStr = '';
523
+ for (const [key, value] of Object.entries(attrs)) {
524
+ attrStr += ` ${key}="${escapeHTML(String(value))}"`;
525
+ }
526
+
527
+ return `<${containerTag}${attrStr}>${sanitized}</${containerTag}>`;
528
+ }
529
+
530
+ /**
531
+ * Create safe text node content
532
+ * @param {string} text - Text content
533
+ * @returns {string} - HTML-escaped text
534
+ */
535
+ textNode(text) {
536
+ return escapeHTML(text);
537
+ }
538
+
539
+ /**
540
+ * Create safe attribute value
541
+ * @param {string} value - Attribute value
542
+ * @returns {string} - Escaped and quoted value
543
+ */
544
+ safeAttr(value) {
545
+ return `"${escapeHTML(String(value))}"`;
546
+ }
547
+
461
548
  }
462
549
 
463
550
  master.extendView("html", html);
package/MasterRouter.js CHANGED
@@ -1,4 +1,4 @@
1
- // version 0.0.248
1
+ // version 0.0.250
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('./error/MasterBackendErrorHandler');
12
+ const { logger } = require('./error/MasterErrorLogger');
13
+ const { performanceTracker, errorHandlerMiddleware } = require('./error/MasterErrorMiddleware');
14
+
15
+ // Security - Input validation and sanitization
16
+ const { validator, detectPathTraversal, detectSQLInjection, detectCommandInjection } = require('./security/MasterValidator');
17
+ const { escapeHTML } = require('./security/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,117 @@ 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
+
139
+ // FIX: Create a clean copy of params for each route test to prevent parameter pollution
140
+ // This prevents parameters from non-matching routes from accumulating in requestObject.params
141
+ var testParams = Object.assign({}, requestObject.params);
142
+ var pathObj = normalizePaths(requestObject.pathName, routeList[item].path, testParams);
143
+
144
+ // if we find the route that matches the request
145
+ if(pathObj.requestPath === pathObj.routePath && routeList[item].type === requestObject.type){
146
+ // Only commit the extracted params if this route actually matches
147
+ requestObject.params = testParams;
148
+
149
+ // call Constraint
150
+ if(typeof routeList[item].constraint === "function"){
151
+
152
+ var newObj = {};
153
+ //tools.combineObjects(newObj, master.controllerList);
154
+ newObj.next = function(){
155
+ currentRoute.root = root;
156
+ currentRoute.pathName = requestObject.pathName;
157
+ currentRoute.toAction = requestObject.toAction;
158
+ currentRoute.toController = requestObject.toController;
159
+ currentRoute.response = requestObject.response;
160
+ currentRoute.isComponent = isComponent;
161
+ currentRoute.routeDef = currentRouteBeingProcessed; // Add route definition
162
+ emitter.emit("routeConstraintGood", requestObject);
163
+ };
164
+
165
+ // Wrap constraint execution with error handling
166
+ try {
167
+ routeList[item].constraint.call(newObj, requestObject);
168
+ } catch(constraintError) {
169
+ const routeError = handleRoutingError(
170
+ requestObject.pathName,
171
+ [],
172
+ {
173
+ type: 'CONSTRAINT_ERROR',
174
+ route: currentRouteBeingProcessed,
175
+ error: constraintError
176
+ }
177
+ );
178
+ logger.error({
179
+ code: 'MC_ERR_ROUTE_CONSTRAINT',
180
+ message: `Route constraint failed for ${currentRouteBeingProcessed.path}`,
181
+ route: currentRouteBeingProcessed,
182
+ error: constraintError.message,
183
+ stack: constraintError.stack
184
+ });
185
+ throw constraintError;
186
+ }
187
+
188
+ return true;
189
+ }else{
54
190
 
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
191
  currentRoute.root = root;
68
192
  currentRoute.pathName = requestObject.pathName;
69
193
  currentRoute.toAction = requestObject.toAction;
70
194
  currentRoute.toController = requestObject.toController;
71
195
  currentRoute.response = requestObject.response;
72
196
  currentRoute.isComponent = isComponent;
197
+ currentRoute.routeDef = currentRouteBeingProcessed; // Add route definition
73
198
  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);
199
+ return true;
200
+ }
201
+
202
+ }
203
+
204
+ if(pathObj.requestPath === pathObj.routePath && "options" ===requestObject.type.toLowerCase()){
205
+ // this means that the request is correct but its an options request means its the browser checking to see if the request is allowed
206
+ // Commit the params for OPTIONS requests too
207
+ requestObject.params = testParams;
208
+ requestObject.response.writeHead(200, {'Content-Type': 'application/json'});
209
+ requestObject.response.end(JSON.stringify({"done": "true"}));
86
210
  return true;
87
211
  }
88
-
89
- }
212
+ } catch(routeProcessError) {
213
+ // Log the specific route that failed
214
+ logger.error({
215
+ code: 'MC_ERR_ROUTE_PROCESS',
216
+ message: `Failed to process route: ${currentRouteBeingProcessed.path}`,
217
+ route: currentRouteBeingProcessed,
218
+ requestPath: requestObject.pathName,
219
+ error: routeProcessError.message,
220
+ stack: routeProcessError.stack
221
+ });
90
222
 
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;
223
+ // Re-throw to be caught by outer try-catch
224
+ throw routeProcessError;
96
225
  }
97
-
226
+
98
227
  };
99
228
  return -1;
100
229
  }
101
230
  }
102
231
  catch(e){
103
- throw new Error("Error processing routes: " + e.stack);
232
+ // Enhanced error message with route context
233
+ const errorDetails = currentRouteBeingProcessed
234
+ ? `\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()}`
235
+ : '';
236
+
237
+ throw new Error(`Error processing routes: ${e.message}${errorDetails}\n\nOriginal Stack:\n${e.stack}`);
104
238
  }
105
239
  };
106
240
 
@@ -257,40 +391,113 @@ class MasterRouter {
257
391
 
258
392
  _call(requestObject){
259
393
 
394
+ // Start performance tracking
395
+ const requestId = `${Date.now()}-${Math.random()}`;
396
+ performanceTracker.start(requestId, requestObject);
397
+
260
398
  tools.combineObjects(master.requestList, requestObject);
261
399
  requestObject = master.requestList;
262
400
  var Control = null;
401
+
263
402
  try{
264
- Control = require(path.join(currentRoute.root, 'app', 'controllers', `${tools.firstLetterlowercase(requestObject.toController)}Controller`));
265
- }catch(e){
403
+ // Try to load controller
266
404
  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}`);
405
+ Control = require(path.join(currentRoute.root, 'app', 'controllers', `${tools.firstLetterlowercase(requestObject.toController)}Controller`));
406
+ }catch(e){
407
+ try{
408
+ Control = require(path.join(currentRoute.root, 'app', 'controllers', `${tools.firstLetterUppercase(requestObject.toController)}Controller`));
409
+ }catch(e2){
410
+ // Controller not found - handle error
411
+ const error = handleControllerError(
412
+ new Error(`Controller not found: ${requestObject.toController}Controller`),
413
+ requestObject.toController,
414
+ requestObject.toAction,
415
+ requestObject.pathName,
416
+ currentRoute.routeDef // Pass route definition
417
+ );
418
+
419
+ sendErrorResponse(requestObject.response, error, requestObject.pathName);
420
+ performanceTracker.end(requestId);
421
+ return;
422
+ }
270
423
  }
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");
424
+
425
+ tools.combineObjectPrototype(Control, master.controllerList);
426
+ Control.prototype.__namespace = Control.name;
427
+ Control.prototype.__requestObject = requestObject;
428
+ Control.prototype.__currentRoute = currentRoute;
429
+ Control.prototype.__response = requestObject.response;
430
+ Control.prototype.__request = requestObject.request;
431
+ var control = new Control(requestObject);
432
+ var _callEmit = new EventEmitter();
433
+
434
+ _callEmit.on("controller", function(){
435
+ try {
436
+ control.next = function(){
437
+ control.__callAfterAction(control, requestObject);
438
+ }
439
+
440
+ // Check if action exists
441
+ if (typeof control[requestObject.toAction] !== 'function') {
442
+ throw new Error(`Action '${requestObject.toAction}' not found in controller ${requestObject.toController}`);
443
+ }
444
+
445
+ // Wrap action with error handling
446
+ const wrappedAction = errorHandlerMiddleware(
447
+ control[requestObject.toAction],
448
+ requestObject.toController,
449
+ requestObject.toAction
450
+ );
451
+
452
+ // Execute action
453
+ Promise.resolve(wrappedAction.call(control, requestObject))
454
+ .then(() => {
455
+ performanceTracker.end(requestId);
456
+ })
457
+ .catch((error) => {
458
+ const mcError = handleControllerError(
459
+ error,
460
+ requestObject.toController,
461
+ requestObject.toAction,
462
+ requestObject.pathName,
463
+ currentRoute.routeDef // Pass route definition
464
+ );
465
+ sendErrorResponse(requestObject.response, mcError, requestObject.pathName);
466
+ performanceTracker.end(requestId);
467
+ });
468
+
469
+ } catch (error) {
470
+ // Action execution error
471
+ const mcError = handleControllerError(
472
+ error,
473
+ requestObject.toController,
474
+ requestObject.toAction,
475
+ requestObject.pathName,
476
+ currentRoute.routeDef // Pass route definition
477
+ );
478
+ sendErrorResponse(requestObject.response, mcError, requestObject.pathName);
479
+ performanceTracker.end(requestId);
480
+ }
481
+ });
482
+
483
+ // check if before function is avaliable and wait for it to return
484
+ if(control.__hasBeforeAction(control, requestObject)){
485
+ control.__callBeforeAction(control, requestObject, _callEmit);
486
+ }else{
487
+ _callEmit.emit("controller");
488
+ }
489
+
490
+ } catch (error) {
491
+ // General error
492
+ const mcError = handleControllerError(
493
+ error,
494
+ requestObject.toController,
495
+ requestObject.toAction,
496
+ requestObject.pathName,
497
+ currentRoute.routeDef // Pass route definition
498
+ );
499
+ sendErrorResponse(requestObject.response, mcError, requestObject.pathName);
500
+ performanceTracker.end(requestId);
294
501
  }
295
502
 
296
503
  }
@@ -319,8 +526,16 @@ class MasterRouter {
319
526
  }
320
527
 
321
528
  if(routeFound === false){
322
- master.error.log(`Cannot find route for path ${requestObject.pathName}`, "warn");
323
- master.error.callHttpStatus(404, requestObject.response);
529
+ // Enhanced 404 handling
530
+ const allRoutes = [];
531
+ for (const route of routes) {
532
+ if (this._routes[route] && this._routes[route].routes) {
533
+ allRoutes.push(...this._routes[route].routes);
534
+ }
535
+ }
536
+
537
+ const mcError = handleRoutingError(requestObject.pathName, allRoutes);
538
+ sendErrorResponse(requestObject.response, mcError, requestObject.pathName);
324
539
  }
325
540
 
326
541
  }