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/.claude/settings.local.json +12 -0
- package/MasterAction.js +297 -73
- package/MasterControl.js +112 -19
- package/MasterHtml.js +101 -14
- package/MasterRouter.js +281 -66
- package/MasterTemplate.js +96 -3
- package/README.md +0 -44
- package/error/ErrorBoundary.js +353 -0
- package/error/HydrationMismatch.js +265 -0
- package/error/MasterBackendErrorHandler.js +769 -0
- package/{MasterError.js → error/MasterError.js} +2 -2
- package/error/MasterErrorHandler.js +487 -0
- package/error/MasterErrorLogger.js +360 -0
- package/error/MasterErrorMiddleware.js +407 -0
- package/error/SSRErrorHandler.js +273 -0
- package/monitoring/MasterCache.js +400 -0
- package/monitoring/MasterMemoryMonitor.js +188 -0
- package/monitoring/MasterProfiler.js +409 -0
- package/monitoring/PerformanceMonitor.js +233 -0
- package/package.json +3 -3
- package/security/CSPConfig.js +319 -0
- package/security/EventHandlerValidator.js +464 -0
- package/security/MasterSanitizer.js +429 -0
- package/security/MasterValidator.js +546 -0
- package/security/SecurityMiddleware.js +486 -0
- package/security/SessionSecurity.js +416 -0
- package/ssr/hydration-client.js +93 -0
- package/ssr/runtime-ssr.cjs +553 -0
- package/ssr/ssr-shims.js +73 -0
- package/examples/FileServingExample.js +0 -88
package/MasterAction.js
CHANGED
|
@@ -1,19 +1,47 @@
|
|
|
1
1
|
|
|
2
|
-
// version 0.0.
|
|
2
|
+
// version 0.0.23
|
|
3
3
|
|
|
4
4
|
var master = require('./MasterControl');
|
|
5
5
|
var fileserver = require('fs');
|
|
6
6
|
var toolClass = require('./MasterTools');
|
|
7
7
|
var tempClass = require('./MasterTemplate');
|
|
8
|
+
// Templating helpers
|
|
8
9
|
var temp = new tempClass();
|
|
9
10
|
var tools = new toolClass();
|
|
10
11
|
|
|
12
|
+
// Node utils
|
|
13
|
+
var path = require('path');
|
|
14
|
+
|
|
15
|
+
// Vanilla Web Components SSR runtime (LinkeDOM) - executes connectedCallback() and upgrades
|
|
16
|
+
const compileWebComponentsHTML = require('./ssr/runtime-ssr.cjs');
|
|
17
|
+
|
|
18
|
+
// Enhanced error handling
|
|
19
|
+
const { handleTemplateError, sendErrorResponse } = require('./error/MasterBackendErrorHandler');
|
|
20
|
+
const { safeReadFile } = require('./error/MasterErrorMiddleware');
|
|
21
|
+
const { logger } = require('./error/MasterErrorLogger');
|
|
22
|
+
|
|
23
|
+
// Security - CSRF, validation, sanitization
|
|
24
|
+
const { generateCSRFToken, validateCSRFToken } = require('./security/SecurityMiddleware');
|
|
25
|
+
const { validator, validateRequestBody, sanitizeObject } = require('./security/MasterValidator');
|
|
26
|
+
const { sanitizeUserHTML, escapeHTML } = require('./security/MasterSanitizer');
|
|
27
|
+
|
|
11
28
|
class MasterAction{
|
|
12
29
|
|
|
13
30
|
getView(location, data){
|
|
14
31
|
var actionUrl = master.root + location;
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
const fileResult = safeReadFile(fileserver, actionUrl);
|
|
33
|
+
|
|
34
|
+
if (!fileResult.success) {
|
|
35
|
+
const error = handleTemplateError(fileResult.error.originalError, actionUrl, data);
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
return temp.htmlBuilder(fileResult.content, data);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
const mcError = handleTemplateError(error, actionUrl, data);
|
|
43
|
+
throw mcError;
|
|
44
|
+
}
|
|
17
45
|
}
|
|
18
46
|
|
|
19
47
|
|
|
@@ -110,6 +138,16 @@ class MasterAction{
|
|
|
110
138
|
this.params = tools.combineObjects(data, this.params);
|
|
111
139
|
var func = master.viewList;
|
|
112
140
|
this.params = tools.combineObjects(this.params, func);
|
|
141
|
+
// Prefer page.js module if present (no legacy .html file)
|
|
142
|
+
try {
|
|
143
|
+
const controller = this.__currentRoute.toController;
|
|
144
|
+
const action = this.__currentRoute.toAction;
|
|
145
|
+
const pageModuleAbs = path.join(master.root, 'app/views', controller, action, 'page.js');
|
|
146
|
+
if (fileserver.existsSync(pageModuleAbs)) {
|
|
147
|
+
if (this._renderPageModule(controller, action, data)) { return; }
|
|
148
|
+
}
|
|
149
|
+
} catch (_) {}
|
|
150
|
+
|
|
113
151
|
var actionUrl = (location === undefined) ? this.__currentRoute.root + "/app/views/" + this.__currentRoute.toController + "/" + this.__currentRoute.toAction + ".html" : master.root + location;
|
|
114
152
|
var actionView = fileserver.readFileSync(actionUrl, 'utf8');
|
|
115
153
|
if(master.overwrite.isTemplate){
|
|
@@ -119,8 +157,21 @@ class MasterAction{
|
|
|
119
157
|
masterView = temp.htmlBuilder(actionView, data);
|
|
120
158
|
}
|
|
121
159
|
if (!this.__requestObject.response._headerSent) {
|
|
122
|
-
|
|
123
|
-
|
|
160
|
+
const send = (htmlOut) => {
|
|
161
|
+
try {
|
|
162
|
+
this.__requestObject.response.writeHead(200, {'Content-Type': 'text/html'});
|
|
163
|
+
this.__requestObject.response.end(htmlOut);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
// Fallback in case of double send
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
try {
|
|
169
|
+
Promise.resolve(compileWebComponentsHTML(masterView))
|
|
170
|
+
.then(send)
|
|
171
|
+
.catch(() => send(masterView));
|
|
172
|
+
} catch (_) {
|
|
173
|
+
send(masterView);
|
|
174
|
+
}
|
|
124
175
|
}
|
|
125
176
|
}
|
|
126
177
|
|
|
@@ -133,6 +184,18 @@ class MasterAction{
|
|
|
133
184
|
}
|
|
134
185
|
}
|
|
135
186
|
|
|
187
|
+
returnReact(data, location){
|
|
188
|
+
|
|
189
|
+
var masterView = null;
|
|
190
|
+
data = data === undefined ? {} : data;
|
|
191
|
+
this.params = this.params === undefined ? {} : this.params;
|
|
192
|
+
this.params = tools.combineObjects(data, this.params);
|
|
193
|
+
var func = master.viewList;
|
|
194
|
+
this.params = tools.combineObjects(this.params, func);
|
|
195
|
+
var html = master.reactView.compile(this.__currentRoute.toController, this.__currentRoute.toAction, this.__currentRoute.root);
|
|
196
|
+
|
|
197
|
+
}
|
|
198
|
+
|
|
136
199
|
returnView(data, location){
|
|
137
200
|
|
|
138
201
|
var masterView = null;
|
|
@@ -141,6 +204,16 @@ class MasterAction{
|
|
|
141
204
|
this.params = tools.combineObjects(data, this.params);
|
|
142
205
|
var func = master.viewList;
|
|
143
206
|
this.params = tools.combineObjects(this.params, func);
|
|
207
|
+
// Prefer page.js module if present (no legacy .html file)
|
|
208
|
+
try {
|
|
209
|
+
const controller = this.__currentRoute.toController;
|
|
210
|
+
const action = this.__currentRoute.toAction;
|
|
211
|
+
const pageModuleAbs = path.join(master.root, 'app/views', controller, action, 'page.js');
|
|
212
|
+
if (fileserver.existsSync(pageModuleAbs)) {
|
|
213
|
+
if (this._renderPageModule(controller, action, data)) { return; }
|
|
214
|
+
}
|
|
215
|
+
} catch (_) {}
|
|
216
|
+
|
|
144
217
|
var viewUrl = (location === undefined || location === "" || location === null) ? this.__currentRoute.root + "/app/views/" + this.__currentRoute.toController + "/" + this.__currentRoute.toAction + ".html" : master.root + location;
|
|
145
218
|
var viewFile = fileserver.readFileSync(viewUrl,'utf8');
|
|
146
219
|
var masterFile = fileserver.readFileSync(this.__currentRoute.root + "/app/views/layouts/master.html", 'utf8');
|
|
@@ -154,8 +227,21 @@ class MasterAction{
|
|
|
154
227
|
}
|
|
155
228
|
|
|
156
229
|
if (!this.__response._headerSent) {
|
|
157
|
-
|
|
158
|
-
|
|
230
|
+
const send = (htmlOut) => {
|
|
231
|
+
try {
|
|
232
|
+
this.__response.writeHead(200, {'Content-Type': 'text/html'});
|
|
233
|
+
this.__response.end(htmlOut);
|
|
234
|
+
} catch (e) {
|
|
235
|
+
// Fallback in case of double send
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
try {
|
|
239
|
+
Promise.resolve(compileWebComponentsHTML(masterView))
|
|
240
|
+
.then(send)
|
|
241
|
+
.catch(() => send(masterView));
|
|
242
|
+
} catch (_) {
|
|
243
|
+
send(masterView);
|
|
244
|
+
}
|
|
159
245
|
}
|
|
160
246
|
|
|
161
247
|
}
|
|
@@ -180,7 +266,7 @@ class MasterAction{
|
|
|
180
266
|
return true;
|
|
181
267
|
}
|
|
182
268
|
|
|
183
|
-
|
|
269
|
+
// Enhanced returnJson that checks readiness first
|
|
184
270
|
safeReturnJson(data){
|
|
185
271
|
if (this.waitUntilReady()) {
|
|
186
272
|
this.returnJson(data);
|
|
@@ -190,75 +276,213 @@ class MasterAction{
|
|
|
190
276
|
return false;
|
|
191
277
|
}
|
|
192
278
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
options = options || {};
|
|
201
|
-
|
|
202
|
-
// Default options
|
|
203
|
-
var disposition = options.disposition || 'attachment';
|
|
204
|
-
var filename = options.filename || filePath.split('/').pop();
|
|
205
|
-
var contentType = options.contentType;
|
|
206
|
-
|
|
207
|
-
// Auto-detect content type if not provided
|
|
208
|
-
if (!contentType) {
|
|
209
|
-
var ext = filePath.split('.').pop().toLowerCase();
|
|
210
|
-
var mimeTypes = {
|
|
211
|
-
'pdf': 'application/pdf',
|
|
212
|
-
'jpg': 'image/jpeg',
|
|
213
|
-
'jpeg': 'image/jpeg',
|
|
214
|
-
'png': 'image/png',
|
|
215
|
-
'gif': 'image/gif',
|
|
216
|
-
'svg': 'image/svg+xml',
|
|
217
|
-
'zip': 'application/zip',
|
|
218
|
-
'csv': 'text/csv',
|
|
219
|
-
'txt': 'text/plain',
|
|
220
|
-
'xml': 'application/xml',
|
|
221
|
-
'json': 'application/json',
|
|
222
|
-
'mp4': 'video/mp4',
|
|
223
|
-
'mp3': 'audio/mpeg',
|
|
224
|
-
'wav': 'audio/wav',
|
|
225
|
-
'doc': 'application/msword',
|
|
226
|
-
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
227
|
-
'xls': 'application/vnd.ms-excel',
|
|
228
|
-
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
229
|
-
'ppt': 'application/vnd.ms-powerpoint',
|
|
230
|
-
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
|
231
|
-
};
|
|
232
|
-
contentType = mimeTypes[ext] || 'application/octet-stream';
|
|
233
|
-
}
|
|
279
|
+
// Render using a page.js Web Component module when present
|
|
280
|
+
_renderPageModule(controller, action, data) {
|
|
281
|
+
try {
|
|
282
|
+
const pageModuleAbs = path.join(master.root, 'app/views', controller, action, 'page.js');
|
|
283
|
+
const layoutModuleAbs = path.join(master.root, 'app/views', 'layouts', 'master.js');
|
|
284
|
+
const stylesPath = '/app/assets/stylesheets/output.css';
|
|
285
|
+
const pageTag = `home-${action}-page`;
|
|
234
286
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
287
|
+
const htmlDoc =
|
|
288
|
+
`<!DOCTYPE html>
|
|
289
|
+
<html lang="en">
|
|
290
|
+
<head>
|
|
291
|
+
<meta charset="utf-8"/>
|
|
292
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
293
|
+
<title>${controller}/${action}</title>
|
|
294
|
+
<link rel="stylesheet" href="${stylesPath}"/>
|
|
295
|
+
</head>
|
|
296
|
+
<body class="geist-variable antialiased">
|
|
297
|
+
<root-layout>
|
|
298
|
+
<${pageTag}></${pageTag}>
|
|
299
|
+
</root-layout>
|
|
300
|
+
<script type="module" src="/app/views/layouts/master.js"></script>
|
|
301
|
+
<script type="module" src="/app/views/${controller}/${action}/page.js"></script>
|
|
302
|
+
</body>
|
|
303
|
+
</html>`;
|
|
245
304
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
305
|
+
const send = (htmlOut) => {
|
|
306
|
+
try {
|
|
307
|
+
const res = this.__response || (this.__requestObject && this.__requestObject.response);
|
|
308
|
+
if (res && !res._headerSent) {
|
|
309
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
310
|
+
res.end(htmlOut);
|
|
311
|
+
}
|
|
312
|
+
} catch (_) {}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
Promise
|
|
316
|
+
.resolve(require('./ssr/runtime-ssr.cjs')(htmlDoc, [layoutModuleAbs, pageModuleAbs]))
|
|
317
|
+
.then(send)
|
|
318
|
+
.catch(() => send(htmlDoc));
|
|
319
|
+
} catch (e) {
|
|
320
|
+
// Fallback to legacy view if something goes wrong
|
|
321
|
+
console.warn('[SSR] _renderPageModule failed:', e && e.message);
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Delegate to standard Enhance-based SSR only
|
|
328
|
+
returnWebComponent(data) {
|
|
329
|
+
this.returnView(data);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ==================== Security Methods ====================
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Generate CSRF token for forms
|
|
336
|
+
* Usage: const token = this.generateCSRFToken();
|
|
337
|
+
*/
|
|
338
|
+
generateCSRFToken() {
|
|
339
|
+
const sessionId = this.__requestObject && this.__requestObject.session ? this.__requestObject.session.id : null;
|
|
340
|
+
return generateCSRFToken(sessionId);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Validate CSRF token from request
|
|
345
|
+
* Usage: if (!this.validateCSRF()) { return this.returnError(403, 'Invalid CSRF token'); }
|
|
346
|
+
*/
|
|
347
|
+
validateCSRF(token = null) {
|
|
348
|
+
// Get token from parameter, header, or body
|
|
349
|
+
const csrfToken = token ||
|
|
350
|
+
this.__requestObject.headers['x-csrf-token'] ||
|
|
351
|
+
(this.__requestObject.body && this.__requestObject.body._csrf) ||
|
|
352
|
+
this.params._csrf;
|
|
353
|
+
|
|
354
|
+
if (!csrfToken) {
|
|
355
|
+
logger.warn({
|
|
356
|
+
code: 'MC_SECURITY_CSRF_MISSING',
|
|
357
|
+
message: 'CSRF token missing in request',
|
|
358
|
+
path: this.__requestObject.pathName
|
|
359
|
+
});
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const validation = validateCSRFToken(csrfToken);
|
|
260
364
|
|
|
365
|
+
if (!validation.valid) {
|
|
366
|
+
logger.warn({
|
|
367
|
+
code: 'MC_SECURITY_CSRF_INVALID',
|
|
368
|
+
message: 'CSRF token validation failed',
|
|
369
|
+
path: this.__requestObject.pathName,
|
|
370
|
+
reason: validation.reason
|
|
371
|
+
});
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Validate request body against schema
|
|
380
|
+
* Usage: const result = this.validateRequest({ email: { type: 'email' }, age: { type: 'integer', min: 18 } });
|
|
381
|
+
*/
|
|
382
|
+
validateRequest(schema = {}) {
|
|
383
|
+
const body = this.__requestObject.body || this.params || {};
|
|
384
|
+
const result = validateRequestBody(body, schema);
|
|
385
|
+
|
|
386
|
+
if (!result.valid) {
|
|
387
|
+
logger.warn({
|
|
388
|
+
code: 'MC_VALIDATION_REQUEST_FAILED',
|
|
389
|
+
message: 'Request validation failed',
|
|
390
|
+
path: this.__requestObject.pathName,
|
|
391
|
+
errors: result.errors
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return result;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Sanitize user input (HTML)
|
|
400
|
+
* Usage: const clean = this.sanitizeInput(userInput);
|
|
401
|
+
*/
|
|
402
|
+
sanitizeInput(input) {
|
|
403
|
+
if (typeof input === 'string') {
|
|
404
|
+
return sanitizeUserHTML(input);
|
|
405
|
+
} else if (typeof input === 'object' && input !== null) {
|
|
406
|
+
return sanitizeObject(input);
|
|
407
|
+
}
|
|
408
|
+
return input;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Escape HTML for display
|
|
413
|
+
* Usage: const safe = this.escapeHTML(userContent);
|
|
414
|
+
*/
|
|
415
|
+
escapeHTML(text) {
|
|
416
|
+
return escapeHTML(text);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Validate single field
|
|
421
|
+
* Usage: const result = this.validate(email, { type: 'email' });
|
|
422
|
+
*/
|
|
423
|
+
validate(value, rules = {}) {
|
|
424
|
+
switch (rules.type) {
|
|
425
|
+
case 'string':
|
|
426
|
+
return validator.validateString(value, rules);
|
|
427
|
+
case 'integer':
|
|
428
|
+
return validator.validateInteger(value, rules);
|
|
429
|
+
case 'email':
|
|
430
|
+
return validator.validateEmail(value, rules);
|
|
431
|
+
case 'url':
|
|
432
|
+
return validator.validateURL(value, rules);
|
|
433
|
+
case 'uuid':
|
|
434
|
+
return validator.validateUUID(value, rules);
|
|
435
|
+
default:
|
|
436
|
+
return { valid: true, value };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Check if request is secure (HTTPS)
|
|
442
|
+
*/
|
|
443
|
+
isSecure() {
|
|
444
|
+
const req = this.__requestObject.request || this.__requestObject;
|
|
445
|
+
return req.connection.encrypted || req.headers['x-forwarded-proto'] === 'https';
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Require HTTPS for this action
|
|
450
|
+
* Usage: if (!this.requireHTTPS()) return;
|
|
451
|
+
*/
|
|
452
|
+
requireHTTPS() {
|
|
453
|
+
if (!this.isSecure()) {
|
|
454
|
+
logger.warn({
|
|
455
|
+
code: 'MC_SECURITY_HTTPS_REQUIRED',
|
|
456
|
+
message: 'HTTPS required but request is HTTP',
|
|
457
|
+
path: this.__requestObject.pathName
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
const httpsUrl = `https://${this.__requestObject.request.headers.host}${this.__requestObject.pathName}`;
|
|
461
|
+
this.redirectTo(httpsUrl);
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Return error response with proper status
|
|
469
|
+
* Usage: this.returnError(400, 'Invalid input');
|
|
470
|
+
*/
|
|
471
|
+
returnError(statusCode, message, details = {}) {
|
|
472
|
+
const res = this.__response || (this.__requestObject && this.__requestObject.response);
|
|
473
|
+
|
|
474
|
+
if (res && !res._headerSent) {
|
|
475
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
476
|
+
res.end(JSON.stringify({
|
|
477
|
+
error: true,
|
|
478
|
+
statusCode,
|
|
479
|
+
message,
|
|
480
|
+
...details
|
|
481
|
+
}));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
261
484
|
|
|
262
485
|
}
|
|
263
486
|
|
|
487
|
+
|
|
264
488
|
master.extendController(MasterAction);
|
package/MasterControl.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// MasterControl - by Alexander rich
|
|
2
|
-
// version 1.0.
|
|
2
|
+
// version 1.0.251
|
|
3
3
|
|
|
4
4
|
var url = require('url');
|
|
5
5
|
var fileserver = require('fs');
|
|
@@ -11,6 +11,47 @@ var url = require('url');
|
|
|
11
11
|
var path = require('path');
|
|
12
12
|
var globSearch = require("glob");
|
|
13
13
|
|
|
14
|
+
// Enhanced error handling - setup global handlers
|
|
15
|
+
const { setupGlobalErrorHandlers } = require('./error/MasterErrorMiddleware');
|
|
16
|
+
const { logger } = require('./error/MasterErrorLogger');
|
|
17
|
+
|
|
18
|
+
// Security - Initialize security features
|
|
19
|
+
const { security, securityHeaders } = require('./security/SecurityMiddleware');
|
|
20
|
+
const { csp } = require('./security/CSPConfig');
|
|
21
|
+
const { session } = require('./security/SessionSecurity');
|
|
22
|
+
|
|
23
|
+
// Initialize global error handling
|
|
24
|
+
setupGlobalErrorHandlers();
|
|
25
|
+
|
|
26
|
+
// Log framework start
|
|
27
|
+
logger.info({
|
|
28
|
+
code: 'MC_INFO_FRAMEWORK_START',
|
|
29
|
+
message: 'MasterController framework initializing',
|
|
30
|
+
context: {
|
|
31
|
+
version: '1.0.247',
|
|
32
|
+
nodeVersion: process.version,
|
|
33
|
+
platform: process.platform,
|
|
34
|
+
env: process.env.NODE_ENV || 'development'
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Log security status
|
|
39
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
40
|
+
logger.info({
|
|
41
|
+
code: 'MC_INFO_SECURITY_INITIALIZED',
|
|
42
|
+
message: 'Security features initialized',
|
|
43
|
+
context: {
|
|
44
|
+
environment: isProduction ? 'production' : 'development',
|
|
45
|
+
features: {
|
|
46
|
+
securityHeaders: true,
|
|
47
|
+
csp: csp.enabled,
|
|
48
|
+
csrf: security.csrfEnabled,
|
|
49
|
+
rateLimit: security.rateLimitEnabled,
|
|
50
|
+
sessionSecurity: true
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
14
55
|
|
|
15
56
|
class MasterControl {
|
|
16
57
|
controllerList = {}
|
|
@@ -152,23 +193,26 @@ class MasterControl {
|
|
|
152
193
|
component(folderLocation, innerFolder){
|
|
153
194
|
|
|
154
195
|
var rootFolderLocation = path.join(this.root, folderLocation, innerFolder);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
196
|
+
|
|
197
|
+
// Structure is always: {rootFolderLocation}/config/initializers/config.js
|
|
198
|
+
var configPath = path.join(rootFolderLocation, 'config', 'initializers', 'config.js');
|
|
199
|
+
if(fs.existsSync(configPath)){
|
|
200
|
+
require(configPath);
|
|
158
201
|
}else{
|
|
159
|
-
this.error.log(`Cannot find config file
|
|
202
|
+
this.error.log(`Cannot find config file at ${configPath}`, "error");
|
|
160
203
|
}
|
|
161
|
-
|
|
162
|
-
|
|
204
|
+
|
|
205
|
+
// Structure is always: {rootFolderLocation}/config/routes.js
|
|
206
|
+
var routePath = path.join(rootFolderLocation, 'config', 'routes.js');
|
|
163
207
|
var routeObject = {
|
|
164
208
|
isComponent : true,
|
|
165
209
|
root : rootFolderLocation
|
|
166
210
|
}
|
|
167
211
|
this.router.setup(routeObject);
|
|
168
|
-
if(
|
|
169
|
-
require(
|
|
212
|
+
if(fs.existsSync(routePath)){
|
|
213
|
+
require(routePath);
|
|
170
214
|
}else{
|
|
171
|
-
this.error.log(`Cannot find routes file
|
|
215
|
+
this.error.log(`Cannot find routes file at ${routePath}`, "error");
|
|
172
216
|
}
|
|
173
217
|
}
|
|
174
218
|
|
|
@@ -203,6 +247,26 @@ class MasterControl {
|
|
|
203
247
|
setupServer(type, credentials ){
|
|
204
248
|
try {
|
|
205
249
|
var $that = this;
|
|
250
|
+
// Auto-load internal master tools so services (request, error, router, etc.) are available
|
|
251
|
+
// before user config initializes them.
|
|
252
|
+
try {
|
|
253
|
+
$that.addInternalTools([
|
|
254
|
+
'MasterAction',
|
|
255
|
+
'MasterActionFilters',
|
|
256
|
+
'MasterRouter',
|
|
257
|
+
'MasterRequest',
|
|
258
|
+
'MasterError',
|
|
259
|
+
'MasterCors',
|
|
260
|
+
'MasterSession',
|
|
261
|
+
'MasterSocket',
|
|
262
|
+
'MasterHtml',
|
|
263
|
+
'MasterTemplate',
|
|
264
|
+
'MasterTools',
|
|
265
|
+
'TemplateOverwrite'
|
|
266
|
+
]);
|
|
267
|
+
} catch (e) {
|
|
268
|
+
console.error('[MasterControl] Failed to load internal tools:', e && e.message);
|
|
269
|
+
}
|
|
206
270
|
if(type === "http"){
|
|
207
271
|
$that.serverProtocol = "http";
|
|
208
272
|
return http.createServer(async function(req, res) {
|
|
@@ -399,16 +463,25 @@ class MasterControl {
|
|
|
399
463
|
return;
|
|
400
464
|
}
|
|
401
465
|
|
|
466
|
+
// Apply CORS headers to ALL non-OPTIONS requests
|
|
467
|
+
try {
|
|
468
|
+
if (this.cors && typeof this.cors.load === 'function') {
|
|
469
|
+
this.cors.load({ request: req, response: res });
|
|
470
|
+
}
|
|
471
|
+
} catch (e) {
|
|
472
|
+
console.warn('CORS load failed for non-OPTIONS request:', e.message);
|
|
473
|
+
}
|
|
474
|
+
|
|
402
475
|
// parse URL
|
|
403
476
|
const parsedUrl = url.parse(req.url);
|
|
404
477
|
// extract URL path
|
|
405
478
|
let pathname = `.${parsedUrl.pathname}`;
|
|
406
|
-
|
|
479
|
+
|
|
407
480
|
// based on the URL path, extract the file extension. e.g. .js, .doc, ...
|
|
408
481
|
const ext = path.parse(pathname).ext;
|
|
409
|
-
|
|
482
|
+
|
|
410
483
|
// handle simple preflight configuration - might need a complex approch for all scenarios
|
|
411
|
-
|
|
484
|
+
|
|
412
485
|
|
|
413
486
|
// if extension exist then its a file.
|
|
414
487
|
if(ext === ""){
|
|
@@ -471,16 +544,18 @@ class MasterControl {
|
|
|
471
544
|
|
|
472
545
|
startMVC(foldername){
|
|
473
546
|
var rootFolderLocation = path.join(this.root, foldername);
|
|
474
|
-
|
|
547
|
+
|
|
548
|
+
// Structure is always: {rootFolderLocation}/routes.js
|
|
549
|
+
var routePath = path.join(rootFolderLocation, 'routes.js');
|
|
475
550
|
var route = {
|
|
476
|
-
isComponent : false,
|
|
551
|
+
isComponent : false,
|
|
477
552
|
root : `${this.root}`
|
|
478
553
|
}
|
|
479
554
|
this.router.setup(route);
|
|
480
|
-
if(
|
|
481
|
-
require(
|
|
555
|
+
if(fs.existsSync(routePath)){
|
|
556
|
+
require(routePath);
|
|
482
557
|
}else{
|
|
483
|
-
|
|
558
|
+
this.error.log(`Cannot find routes file at ${routePath}`, "error");
|
|
484
559
|
}
|
|
485
560
|
}
|
|
486
561
|
|
|
@@ -488,8 +563,26 @@ class MasterControl {
|
|
|
488
563
|
// builds and calls all the required tools to have master running completely
|
|
489
564
|
addInternalTools(requiredList){
|
|
490
565
|
if(requiredList.constructor === Array){
|
|
566
|
+
// Map module names to their new organized paths
|
|
567
|
+
const modulePathMap = {
|
|
568
|
+
'MasterError': './error/MasterError',
|
|
569
|
+
'MasterAction': './MasterAction',
|
|
570
|
+
'MasterActionFilters': './MasterActionFilters',
|
|
571
|
+
'MasterRouter': './MasterRouter',
|
|
572
|
+
'MasterRequest': './MasterRequest',
|
|
573
|
+
'MasterCors': './MasterCors',
|
|
574
|
+
'MasterSession': './MasterSession',
|
|
575
|
+
'MasterSocket': './MasterSocket',
|
|
576
|
+
'MasterHtml': './MasterHtml',
|
|
577
|
+
'MasterTemplate': './MasterTemplate',
|
|
578
|
+
'MasterTools': './MasterTools',
|
|
579
|
+
'TemplateOverwrite': './TemplateOverwrite'
|
|
580
|
+
};
|
|
581
|
+
|
|
491
582
|
for(var i = 0; i < requiredList.length; i++){
|
|
492
|
-
|
|
583
|
+
const moduleName = requiredList[i];
|
|
584
|
+
const modulePath = modulePathMap[moduleName] || './' + moduleName;
|
|
585
|
+
require(modulePath);
|
|
493
586
|
}
|
|
494
587
|
}
|
|
495
588
|
}
|