jerkjs 2.5.8 → 2.6.1
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/CHANGELOG.md +162 -99
- package/README.md +113 -190
- package/RESULTADOS_WAF.md +63 -0
- package/doc-2.5/MANUAL_MODULOS_ADMIN.md +287 -0
- package/doc-2.5/QUEUE_CLI_MODULE_MANUAL.md +289 -0
- package/doc-2.5/QUEUE_SYSTEM_MANUAL.md +320 -0
- package/doc-2.5/ROUTE_CACHE_MODULE_MANUAL.md +205 -0
- package/doc-2.5/WAF_MODULE_MANUAL.md +229 -0
- package/index.js +12 -3
- package/jerk-admin-client/README.md +69 -0
- package/jerk-admin-client/package.json +23 -0
- package/jerk-admin-client.js +257 -0
- package/lib/admin/AdminExtension.js +74 -19
- package/lib/admin/modules/ControllerGeneratorModule.js +414 -0
- package/lib/admin/modules/QueueManagementModule.js +265 -0
- package/lib/admin/modules/RouteCacheModule.js +227 -0
- package/lib/admin/modules/RouteManagerModule.js +468 -0
- package/lib/admin/modules/STATS_MODULE_README.md +15 -0
- package/lib/admin/modules/ViewCacheStatsModule.js +92 -0
- package/lib/admin/modules/WAFModule.js +737 -0
- package/lib/core/server.js +72 -69
- package/lib/middleware/firewall.js +112 -17
- package/lib/mvc/viewEngine.js +69 -10
- package/lib/queue/GlobalQueueStorage.js +38 -0
- package/lib/queue/QueueSystem.js +451 -0
- package/lib/queue/admin_example.js +114 -0
- package/lib/queue/example.js +268 -0
- package/lib/queue/integration.js +109 -0
- package/lib/utils/globalViewCacheInfo.js +16 -0
- package/lib/utils/globalWAFStats.js +54 -0
- package/package.json +2 -2
- package/test-colors.js +46 -0
- package/test-help-alias.js +31 -0
- package/ESTADISTICAS_RENDIMIENTO.md +0 -106
- package/debug_hook.js +0 -11
- package/docs/CACHE_SYSTEM_MAP.md +0 -206
package/lib/core/server.js
CHANGED
|
@@ -18,6 +18,7 @@ const { globalStats } = require('../utils/globalStats');
|
|
|
18
18
|
|
|
19
19
|
// Carga anticipada de módulos comunes para evitar cargas repetidas
|
|
20
20
|
let hooksInstance = null;
|
|
21
|
+
let errorHandlerInstance = null;
|
|
21
22
|
const getHooks = () => {
|
|
22
23
|
if (!hooksInstance) {
|
|
23
24
|
hooksInstance = require('../../index.js').hooks;
|
|
@@ -25,6 +26,13 @@ const getHooks = () => {
|
|
|
25
26
|
return hooksInstance;
|
|
26
27
|
};
|
|
27
28
|
|
|
29
|
+
const getErrorHandler = () => {
|
|
30
|
+
if (!errorHandlerInstance) {
|
|
31
|
+
errorHandlerInstance = require('../utils/errorHandler');
|
|
32
|
+
}
|
|
33
|
+
return errorHandlerInstance;
|
|
34
|
+
};
|
|
35
|
+
|
|
28
36
|
class APIServer {
|
|
29
37
|
/**
|
|
30
38
|
* Constructor del servidor
|
|
@@ -114,7 +122,8 @@ class APIServer {
|
|
|
114
122
|
return async (req, res) => {
|
|
115
123
|
try {
|
|
116
124
|
// Disparar hook antes de procesar archivo estático
|
|
117
|
-
|
|
125
|
+
// Usar la instancia de hooks del servidor para evitar llamadas repetidas a getHooks()
|
|
126
|
+
const hooks = this.hooks;
|
|
118
127
|
if (hooks) {
|
|
119
128
|
const hookResult = hooks.applyFilters('pre_static_file_serve', {
|
|
120
129
|
req,
|
|
@@ -150,8 +159,8 @@ class APIServer {
|
|
|
150
159
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
151
160
|
res.end(JSON.stringify({ error: 'Acceso denegado' }));
|
|
152
161
|
|
|
153
|
-
if (hooks) {
|
|
154
|
-
hooks.doAction('static_file_access_denied', req, res, normalizedPath);
|
|
162
|
+
if (this.hooks) {
|
|
163
|
+
this.hooks.doAction('static_file_access_denied', req, res, normalizedPath);
|
|
155
164
|
}
|
|
156
165
|
return;
|
|
157
166
|
}
|
|
@@ -177,8 +186,8 @@ class APIServer {
|
|
|
177
186
|
try {
|
|
178
187
|
await fs.promises.access(indexPath);
|
|
179
188
|
// Disparar hook antes de servir archivo índice
|
|
180
|
-
if (hooks) {
|
|
181
|
-
hooks.doAction('serving_index_file', indexPath, req, res);
|
|
189
|
+
if (this.hooks) {
|
|
190
|
+
this.hooks.doAction('serving_index_file', indexPath, req, res);
|
|
182
191
|
}
|
|
183
192
|
|
|
184
193
|
const fileContent = await fs.promises.readFile(indexPath);
|
|
@@ -194,8 +203,8 @@ class APIServer {
|
|
|
194
203
|
res.end(fileContent);
|
|
195
204
|
|
|
196
205
|
// Disparar hook después de servir archivo
|
|
197
|
-
if (hooks) {
|
|
198
|
-
hooks.doAction('static_file_served', indexPath, req, res);
|
|
206
|
+
if (this.hooks) {
|
|
207
|
+
this.hooks.doAction('static_file_served', indexPath, req, res);
|
|
199
208
|
}
|
|
200
209
|
return;
|
|
201
210
|
} catch (e) {
|
|
@@ -207,8 +216,8 @@ class APIServer {
|
|
|
207
216
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
208
217
|
res.end(JSON.stringify({ error: 'Directorio no encontrado', path: dirPath }));
|
|
209
218
|
|
|
210
|
-
if (hooks) {
|
|
211
|
-
hooks.doAction('static_directory_no_index', dirPath, req, res);
|
|
219
|
+
if (this.hooks) {
|
|
220
|
+
this.hooks.doAction('static_directory_no_index', dirPath, req, res);
|
|
212
221
|
}
|
|
213
222
|
return;
|
|
214
223
|
} else {
|
|
@@ -216,8 +225,8 @@ class APIServer {
|
|
|
216
225
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
217
226
|
res.end(JSON.stringify({ error: 'Archivo no encontrado', path: physicalPath }));
|
|
218
227
|
|
|
219
|
-
if (hooks) {
|
|
220
|
-
hooks.doAction('static_file_not_found', physicalPath, req, res);
|
|
228
|
+
if (this.hooks) {
|
|
229
|
+
this.hooks.doAction('static_file_not_found', physicalPath, req, res);
|
|
221
230
|
}
|
|
222
231
|
return;
|
|
223
232
|
}
|
|
@@ -226,8 +235,8 @@ class APIServer {
|
|
|
226
235
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
227
236
|
res.end(JSON.stringify({ error: 'Archivo no encontrado', path: physicalPath }));
|
|
228
237
|
|
|
229
|
-
if (hooks) {
|
|
230
|
-
hooks.doAction('static_file_not_found', physicalPath, req, res);
|
|
238
|
+
if (this.hooks) {
|
|
239
|
+
this.hooks.doAction('static_file_not_found', physicalPath, req, res);
|
|
231
240
|
}
|
|
232
241
|
return;
|
|
233
242
|
}
|
|
@@ -242,8 +251,8 @@ class APIServer {
|
|
|
242
251
|
try {
|
|
243
252
|
await fs.promises.access(indexPath);
|
|
244
253
|
// Disparar hook antes de servir archivo índice
|
|
245
|
-
if (hooks) {
|
|
246
|
-
hooks.doAction('serving_index_file', indexPath, req, res);
|
|
254
|
+
if (this.hooks) {
|
|
255
|
+
this.hooks.doAction('serving_index_file', indexPath, req, res);
|
|
247
256
|
}
|
|
248
257
|
|
|
249
258
|
const fileContent = await fs.promises.readFile(indexPath);
|
|
@@ -259,8 +268,8 @@ class APIServer {
|
|
|
259
268
|
res.end(fileContent);
|
|
260
269
|
|
|
261
270
|
// Disparar hook después de servir archivo
|
|
262
|
-
if (hooks) {
|
|
263
|
-
hooks.doAction('static_file_served', indexPath, req, res);
|
|
271
|
+
if (this.hooks) {
|
|
272
|
+
this.hooks.doAction('static_file_served', indexPath, req, res);
|
|
264
273
|
}
|
|
265
274
|
return;
|
|
266
275
|
} catch (e) {
|
|
@@ -272,15 +281,15 @@ class APIServer {
|
|
|
272
281
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
273
282
|
res.end(JSON.stringify({ error: 'Directorio no encontrado' }));
|
|
274
283
|
|
|
275
|
-
if (hooks) {
|
|
276
|
-
hooks.doAction('static_directory_no_index', dirPath, req, res);
|
|
284
|
+
if (this.hooks) {
|
|
285
|
+
this.hooks.doAction('static_directory_no_index', dirPath, req, res);
|
|
277
286
|
}
|
|
278
287
|
return;
|
|
279
288
|
}
|
|
280
289
|
|
|
281
290
|
// Disparar hook antes de leer archivo
|
|
282
|
-
if (hooks) {
|
|
283
|
-
hooks.doAction('before_reading_static_file', physicalPath, req, res);
|
|
291
|
+
if (this.hooks) {
|
|
292
|
+
this.hooks.doAction('before_reading_static_file', physicalPath, req, res);
|
|
284
293
|
}
|
|
285
294
|
|
|
286
295
|
// Leer y servir el archivo
|
|
@@ -300,34 +309,31 @@ class APIServer {
|
|
|
300
309
|
res.end(fileContent);
|
|
301
310
|
|
|
302
311
|
// Disparar hook después de servir archivo
|
|
303
|
-
if (hooks) {
|
|
304
|
-
hooks.doAction('static_file_served', physicalPath, req, res);
|
|
312
|
+
if (this.hooks) {
|
|
313
|
+
this.hooks.doAction('static_file_served', physicalPath, req, res);
|
|
305
314
|
}
|
|
306
315
|
} catch (error) {
|
|
307
316
|
if (error.code === 'ENOENT') {
|
|
308
317
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
309
318
|
res.end(JSON.stringify({ error: 'Archivo no encontrado', path: error.path }));
|
|
310
319
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
hooks.doAction('static_file_not_found', error.path, req, res);
|
|
320
|
+
if (this.hooks) {
|
|
321
|
+
this.hooks.doAction('static_file_not_found', error.path, req, res);
|
|
314
322
|
}
|
|
315
323
|
} else if (error.code === 'EACCES') {
|
|
316
324
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
317
325
|
res.end(JSON.stringify({ error: 'Acceso denegado', path: error.path }));
|
|
318
326
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
hooks.doAction('static_file_access_denied', error.path, req, res);
|
|
327
|
+
if (this.hooks) {
|
|
328
|
+
this.hooks.doAction('static_file_access_denied', error.path, req, res);
|
|
322
329
|
}
|
|
323
330
|
} else {
|
|
324
331
|
// Usar el ErrorHandler para mostrar el stacktrace en color amarillo
|
|
325
|
-
const { ErrorHandler } =
|
|
332
|
+
const { ErrorHandler } = getErrorHandler();
|
|
326
333
|
ErrorHandler.handle(error, req, res, this.logger);
|
|
327
334
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
hooks.doAction('static_file_error', error, req, res);
|
|
335
|
+
if (this.hooks) {
|
|
336
|
+
this.hooks.doAction('static_file_error', error, req, res);
|
|
331
337
|
}
|
|
332
338
|
}
|
|
333
339
|
}
|
|
@@ -413,7 +419,6 @@ class APIServer {
|
|
|
413
419
|
|
|
414
420
|
// Obtener el handler final según las configuraciones
|
|
415
421
|
let finalHandler = routeConfig.handler;
|
|
416
|
-
const path = require('path');
|
|
417
422
|
|
|
418
423
|
// Si se especifica un controlador, cargarlo y obtener el handler
|
|
419
424
|
if (routeConfig.controller && routeConfig.handlerName) {
|
|
@@ -445,7 +450,6 @@ class APIServer {
|
|
|
445
450
|
if (routeConfig.auth === 'session') {
|
|
446
451
|
// Verificar si el servidor tiene sessionManager
|
|
447
452
|
if (this.sessionManager) {
|
|
448
|
-
const { sessionAuth } = require('../middleware/session');
|
|
449
453
|
const authMiddleware = sessionAuth(this.sessionManager, routeConfig.authOptions || {});
|
|
450
454
|
|
|
451
455
|
// Crear un nuevo handler que ejecute la autenticación primero
|
|
@@ -473,7 +477,7 @@ class APIServer {
|
|
|
473
477
|
}
|
|
474
478
|
} catch (error) {
|
|
475
479
|
// Usar el ErrorHandler para mostrar el stacktrace en color amarillo
|
|
476
|
-
const { ErrorHandler } =
|
|
480
|
+
const { ErrorHandler } = getErrorHandler();
|
|
477
481
|
if (!res.headersSent) {
|
|
478
482
|
ErrorHandler.handle(error, req, res, this.logger);
|
|
479
483
|
}
|
|
@@ -530,7 +534,7 @@ class APIServer {
|
|
|
530
534
|
}
|
|
531
535
|
} catch (error) {
|
|
532
536
|
// Usar el ErrorHandler para mostrar el stacktrace en color amarillo
|
|
533
|
-
const { ErrorHandler } =
|
|
537
|
+
const { ErrorHandler } = getErrorHandler();
|
|
534
538
|
if (!res.headersSent) {
|
|
535
539
|
ErrorHandler.handle(error, req, res, this.logger);
|
|
536
540
|
}
|
|
@@ -603,9 +607,8 @@ class APIServer {
|
|
|
603
607
|
*/
|
|
604
608
|
start() {
|
|
605
609
|
// Disparar hook antes de iniciar el servidor
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
hooks.doAction('pre_server_start', this);
|
|
610
|
+
if (this.hooks) {
|
|
611
|
+
this.hooks.doAction('pre_server_start', this);
|
|
609
612
|
}
|
|
610
613
|
|
|
611
614
|
if (this.https && Object.keys(this.httpsOptions).length > 0) {
|
|
@@ -645,11 +648,11 @@ class APIServer {
|
|
|
645
648
|
this.logger.logMemoryUsage();
|
|
646
649
|
|
|
647
650
|
// Disparar hook después de iniciar el servidor
|
|
648
|
-
if (hooks) {
|
|
649
|
-
hooks.doAction('post_server_start', this);
|
|
650
|
-
|
|
651
|
+
if (this.hooks) {
|
|
652
|
+
this.hooks.doAction('post_server_start', this);
|
|
653
|
+
|
|
651
654
|
// Disparar hook para inicializar extensiones después de que el servidor esté completamente iniciado
|
|
652
|
-
hooks.doAction('admin_extensions_initialize', this);
|
|
655
|
+
this.hooks.doAction('admin_extensions_initialize', this);
|
|
653
656
|
}
|
|
654
657
|
});
|
|
655
658
|
|
|
@@ -695,9 +698,8 @@ class APIServer {
|
|
|
695
698
|
const { pathname, query } = parsedUrl;
|
|
696
699
|
|
|
697
700
|
// Disparar hook antes de procesar la solicitud
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
hooks.doAction('request_received', req, res);
|
|
701
|
+
if (this.hooks) {
|
|
702
|
+
this.hooks.doAction('request_received', req, res);
|
|
701
703
|
}
|
|
702
704
|
|
|
703
705
|
// Agregar propiedades útiles a la solicitud
|
|
@@ -758,16 +760,16 @@ class APIServer {
|
|
|
758
760
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
759
761
|
res.end(renderedHtml);
|
|
760
762
|
|
|
761
|
-
if (hooks) {
|
|
762
|
-
hooks.doAction('view_rendered', viewName, data, req, res);
|
|
763
|
+
if (this.hooks) {
|
|
764
|
+
this.hooks.doAction('view_rendered', viewName, data, req, res);
|
|
763
765
|
}
|
|
764
766
|
} catch (error) {
|
|
765
767
|
// Usar el ErrorHandler para mostrar el stacktrace en color amarillo
|
|
766
768
|
const { ErrorHandler } = require('../utils/errorHandler');
|
|
767
769
|
ErrorHandler.handle(error, req, res, this.logger);
|
|
768
770
|
|
|
769
|
-
if (hooks) {
|
|
770
|
-
hooks.doAction('view_render_error', viewName, data, req, res, error);
|
|
771
|
+
if (this.hooks) {
|
|
772
|
+
this.hooks.doAction('view_render_error', viewName, data, req, res, error);
|
|
771
773
|
}
|
|
772
774
|
}
|
|
773
775
|
};
|
|
@@ -810,8 +812,8 @@ class APIServer {
|
|
|
810
812
|
}
|
|
811
813
|
|
|
812
814
|
if (res.finished) {
|
|
813
|
-
if (hooks) {
|
|
814
|
-
hooks.doAction('middleware_response_finished', req, res);
|
|
815
|
+
if (this.hooks) {
|
|
816
|
+
this.hooks.doAction('middleware_response_finished', req, res);
|
|
815
817
|
}
|
|
816
818
|
return; // Si el middleware respondió, salir
|
|
817
819
|
}
|
|
@@ -825,8 +827,8 @@ class APIServer {
|
|
|
825
827
|
// Agregar parámetros a la solicitud
|
|
826
828
|
req.params = matchedRoute.params;
|
|
827
829
|
|
|
828
|
-
if (hooks) {
|
|
829
|
-
hooks.doAction('route_matched', matchedRoute, req, res);
|
|
830
|
+
if (this.hooks) {
|
|
831
|
+
this.hooks.doAction('route_matched', matchedRoute, req, res);
|
|
830
832
|
}
|
|
831
833
|
|
|
832
834
|
// Ejecutar handler de la ruta
|
|
@@ -840,8 +842,8 @@ class APIServer {
|
|
|
840
842
|
res.writeHead(204);
|
|
841
843
|
res.end();
|
|
842
844
|
|
|
843
|
-
if (hooks) {
|
|
844
|
-
hooks.doAction('options_response_sent', req, res);
|
|
845
|
+
if (this.hooks) {
|
|
846
|
+
this.hooks.doAction('options_response_sent', req, res);
|
|
845
847
|
}
|
|
846
848
|
}
|
|
847
849
|
}
|
|
@@ -853,8 +855,8 @@ class APIServer {
|
|
|
853
855
|
// Agregar parámetros a la solicitud
|
|
854
856
|
req.params = matchedRoute.params;
|
|
855
857
|
|
|
856
|
-
if (hooks) {
|
|
857
|
-
hooks.doAction('route_matched', matchedRoute, req, res);
|
|
858
|
+
if (this.hooks) {
|
|
859
|
+
this.hooks.doAction('route_matched', matchedRoute, req, res);
|
|
858
860
|
}
|
|
859
861
|
|
|
860
862
|
// Ejecutar middlewares (excepto para rutas estáticas)
|
|
@@ -885,8 +887,8 @@ class APIServer {
|
|
|
885
887
|
}
|
|
886
888
|
|
|
887
889
|
if (res.finished) {
|
|
888
|
-
if (hooks) {
|
|
889
|
-
hooks.doAction('middleware_response_finished', req, res);
|
|
890
|
+
if (this.hooks) {
|
|
891
|
+
this.hooks.doAction('middleware_response_finished', req, res);
|
|
890
892
|
}
|
|
891
893
|
return; // Si el middleware respondió, salir
|
|
892
894
|
}
|
|
@@ -948,27 +950,28 @@ class APIServer {
|
|
|
948
950
|
}
|
|
949
951
|
globalStats.endpointHits.set(endpointKey, globalStats.endpointHits.get(endpointKey) + 1);
|
|
950
952
|
|
|
951
|
-
if (hooks) {
|
|
952
|
-
hooks.doAction('route_handler_executed', matchedRoute, req, res);
|
|
953
|
+
if (this.hooks) {
|
|
954
|
+
this.hooks.doAction('route_handler_executed', matchedRoute, req, res);
|
|
953
955
|
}
|
|
954
956
|
} else {
|
|
955
957
|
// Ruta no encontrada
|
|
956
|
-
if (hooks) {
|
|
957
|
-
hooks.doAction('route_not_found', pathname, req, res);
|
|
958
|
+
if (this.hooks) {
|
|
959
|
+
this.hooks.doAction('route_not_found', pathname, req, res);
|
|
958
960
|
}
|
|
959
961
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
960
962
|
res.end(JSON.stringify({ error: 'Ruta no encontrada', path: pathname }));
|
|
961
963
|
}
|
|
962
964
|
}
|
|
963
965
|
|
|
964
|
-
if (hooks) {
|
|
965
|
-
hooks.doAction('request_processed', req, res);
|
|
966
|
+
if (this.hooks) {
|
|
967
|
+
this.hooks.doAction('request_processed', req, res);
|
|
966
968
|
}
|
|
967
969
|
} catch (error) {
|
|
968
|
-
if (hooks) {
|
|
969
|
-
hooks.doAction('request_error', req, res, error);
|
|
970
|
+
if (this.hooks) {
|
|
971
|
+
this.hooks.doAction('request_error', req, res, error);
|
|
970
972
|
}
|
|
971
973
|
|
|
974
|
+
const { ErrorHandler } = getErrorHandler();
|
|
972
975
|
ErrorHandler.handle(error, req, res, this.logger);
|
|
973
976
|
}
|
|
974
977
|
});
|
|
@@ -170,26 +170,31 @@ class Firewall {
|
|
|
170
170
|
// Path traversal
|
|
171
171
|
const pathTraversal = /\.\.\//g;
|
|
172
172
|
|
|
173
|
-
// Verificar patrones en URL
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
173
|
+
// Verificar patrones en URL solo si hay parámetros (evitar falsos positivos)
|
|
174
|
+
if (path.includes('?')) {
|
|
175
|
+
for (const pattern of sqlPatterns) {
|
|
176
|
+
if (pattern.test(path)) {
|
|
177
|
+
return {
|
|
178
|
+
matched: true,
|
|
179
|
+
rule: 'sql_injection',
|
|
180
|
+
action: 'block',
|
|
181
|
+
reason: 'Patrón de SQL Injection detectado'
|
|
182
|
+
};
|
|
183
|
+
}
|
|
182
184
|
}
|
|
183
185
|
}
|
|
184
186
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
187
|
+
// Verificar patrones XSS solo si hay parámetros (evitar falsos positivos)
|
|
188
|
+
if (path.includes('?')) {
|
|
189
|
+
for (const pattern of xssPatterns) {
|
|
190
|
+
if (pattern.test(path)) {
|
|
191
|
+
return {
|
|
192
|
+
matched: true,
|
|
193
|
+
rule: 'xss_attack',
|
|
194
|
+
action: 'block',
|
|
195
|
+
reason: 'Patrón de XSS detectado'
|
|
196
|
+
};
|
|
197
|
+
}
|
|
193
198
|
}
|
|
194
199
|
}
|
|
195
200
|
|
|
@@ -202,6 +207,34 @@ class Firewall {
|
|
|
202
207
|
};
|
|
203
208
|
}
|
|
204
209
|
|
|
210
|
+
// Verificar reglas personalizadas de headers X-
|
|
211
|
+
const { globalWAFStats } = require('../utils/globalWAFStats');
|
|
212
|
+
for (const [ruleId, rule] of globalWAFStats.xHeaderRules) {
|
|
213
|
+
const headerValue = headers[rule.headerName];
|
|
214
|
+
if (headerValue) {
|
|
215
|
+
// Verificar si coincide con el patrón de la regla
|
|
216
|
+
if (typeof rule.pattern === 'string') {
|
|
217
|
+
if (headerValue.includes(rule.pattern)) {
|
|
218
|
+
return {
|
|
219
|
+
matched: true,
|
|
220
|
+
rule: ruleId,
|
|
221
|
+
action: rule.action,
|
|
222
|
+
reason: rule.reason
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
} else if (rule.pattern instanceof RegExp) {
|
|
226
|
+
if (rule.pattern.test(headerValue)) {
|
|
227
|
+
return {
|
|
228
|
+
matched: true,
|
|
229
|
+
rule: ruleId,
|
|
230
|
+
action: rule.action,
|
|
231
|
+
reason: rule.reason
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
205
238
|
// Verificar patrones en body si es string
|
|
206
239
|
if (typeof body === 'string') {
|
|
207
240
|
for (const pattern of sqlPatterns) {
|
|
@@ -374,16 +407,35 @@ class Firewall {
|
|
|
374
407
|
|
|
375
408
|
const clientIP = this.getClientIP(req);
|
|
376
409
|
|
|
410
|
+
// Actualizar estadísticas globales de solicitudes procesadas
|
|
411
|
+
const { globalWAFStats } = require('../utils/globalWAFStats');
|
|
412
|
+
globalWAFStats.requestsProcessed++;
|
|
413
|
+
|
|
414
|
+
// Capturar el tamaño del cuerpo de la solicitud si existe
|
|
415
|
+
if (req.body) {
|
|
416
|
+
const bodySize = typeof req.body === 'string' ? Buffer.byteLength(req.body, 'utf8') :
|
|
417
|
+
typeof req.body === 'object' ? Buffer.byteLength(JSON.stringify(req.body), 'utf8') : 0;
|
|
418
|
+
globalWAFStats.requestBytes += bodySize;
|
|
419
|
+
console.log(`[FIREWALL DEBUG] Bytes de solicitud actualizados: ${globalWAFStats.requestBytes}`);
|
|
420
|
+
} else {
|
|
421
|
+
console.log('[FIREWALL DEBUG] No hay cuerpo en la solicitud');
|
|
422
|
+
}
|
|
423
|
+
|
|
377
424
|
// Verificar si la IP está bloqueada
|
|
378
425
|
const blockInfo = this.isBlocked(clientIP);
|
|
379
426
|
if (blockInfo.blocked) {
|
|
380
427
|
this.logger.warn(`Solicitud bloqueada desde IP: ${clientIP}, razón: ${blockInfo.reason}`);
|
|
428
|
+
console.log(`[FIREWALL DEBUG] Solicitud bloqueada desde IP: ${clientIP}, razón: ${blockInfo.reason}`);
|
|
381
429
|
|
|
382
430
|
// Disparar hook de evento de seguridad
|
|
383
431
|
if (hooks) {
|
|
384
432
|
hooks.doAction('firewall_ip_blocked', clientIP, blockInfo.reason, req, res);
|
|
385
433
|
}
|
|
386
434
|
|
|
435
|
+
// Actualizar estadísticas de intentos de ataque
|
|
436
|
+
globalWAFStats.attackAttempts++;
|
|
437
|
+
console.log(`[FIREWALL DEBUG] Ataque detectado, attackAttempts ahora: ${globalWAFStats.attackAttempts}`);
|
|
438
|
+
|
|
387
439
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
388
440
|
res.end(JSON.stringify({
|
|
389
441
|
error: 'Acceso denegado por firewall',
|
|
@@ -412,6 +464,14 @@ class Firewall {
|
|
|
412
464
|
hooks.doAction('firewall_request_blocked', ruleMatch, clientIP, req, res);
|
|
413
465
|
}
|
|
414
466
|
|
|
467
|
+
// Actualizar estadísticas de intentos de ataque
|
|
468
|
+
globalWAFStats.attackAttempts++;
|
|
469
|
+
console.log(`[FIREWALL DEBUG] Ataque bloqueado, attackAttempts ahora: ${globalWAFStats.attackAttempts}`);
|
|
470
|
+
|
|
471
|
+
// Registrar el tipo de ataque
|
|
472
|
+
globalWAFStats.attackTypes.add(ruleMatch.rule);
|
|
473
|
+
console.log(`[FIREWALL DEBUG] Tipo de ataque registrado: ${ruleMatch.rule}`);
|
|
474
|
+
|
|
415
475
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
416
476
|
res.end(JSON.stringify({
|
|
417
477
|
error: 'Solicitud bloqueada por firewall',
|
|
@@ -429,11 +489,46 @@ class Firewall {
|
|
|
429
489
|
}
|
|
430
490
|
}
|
|
431
491
|
|
|
492
|
+
// Registrar acceso a ruta en las estadísticas globales
|
|
493
|
+
const routeKey = `${req.method} ${req.url}`;
|
|
494
|
+
if (!globalWAFStats.routeAccesses.has(routeKey)) {
|
|
495
|
+
globalWAFStats.routeAccesses.set(routeKey, 0);
|
|
496
|
+
}
|
|
497
|
+
globalWAFStats.routeAccesses.set(routeKey, globalWAFStats.routeAccesses.get(routeKey) + 1);
|
|
498
|
+
console.log(`[FIREWALL DEBUG] Acceso a ruta registrado: ${routeKey}, total accesos: ${globalWAFStats.routeAccesses.get(routeKey)}`);
|
|
499
|
+
|
|
500
|
+
// Registrar hit al endpoint en las estadísticas globales
|
|
501
|
+
const endpointKey = `${req.method} ${req.url}`;
|
|
502
|
+
if (!globalWAFStats.endpointHits.has(endpointKey)) {
|
|
503
|
+
globalWAFStats.endpointHits.set(endpointKey, 0);
|
|
504
|
+
}
|
|
505
|
+
globalWAFStats.endpointHits.set(endpointKey, globalWAFStats.endpointHits.get(endpointKey) + 1);
|
|
506
|
+
console.log(`[FIREWALL DEBUG] Hit al endpoint registrado: ${endpointKey}, total hits: ${globalWAFStats.endpointHits.get(endpointKey)}`);
|
|
507
|
+
|
|
432
508
|
// Disparar hook antes de continuar con el siguiente middleware
|
|
433
509
|
if (hooks) {
|
|
434
510
|
hooks.doAction('firewall_request_allowed', req, res);
|
|
435
511
|
}
|
|
436
512
|
|
|
513
|
+
// Capturar el tamaño de la respuesta antes de enviarla
|
|
514
|
+
const originalEnd = res.end;
|
|
515
|
+
res.end = (chunk, encoding, callback) => {
|
|
516
|
+
console.log(`[FIREWALL DEBUG] Enviando respuesta, tamaño del chunk: ${chunk ? Buffer.byteLength(chunk, typeof encoding === 'string' ? encoding : 'utf8') : 0} bytes`);
|
|
517
|
+
|
|
518
|
+
// Actualizar estadísticas globales de respuestas enviadas
|
|
519
|
+
globalWAFStats.responsesSent++;
|
|
520
|
+
console.log(`[FIREWALL DEBUG] responsesSent ahora: ${globalWAFStats.responsesSent}`);
|
|
521
|
+
|
|
522
|
+
// Capturar el tamaño del cuerpo de la respuesta si existe
|
|
523
|
+
if (chunk) {
|
|
524
|
+
const responseSize = Buffer.byteLength(chunk, typeof encoding === 'string' ? encoding : 'utf8');
|
|
525
|
+
globalWAFStats.responseBytes += responseSize;
|
|
526
|
+
console.log(`[FIREWALL DEBUG] responseBytes ahora: ${globalWAFStats.responseBytes}`);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return originalEnd.call(res, chunk, encoding, callback);
|
|
530
|
+
};
|
|
531
|
+
|
|
437
532
|
// Continuar con el siguiente middleware
|
|
438
533
|
next();
|
|
439
534
|
};
|
package/lib/mvc/viewEngine.js
CHANGED
|
@@ -7,24 +7,29 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
+
const { globalViewCacheInfo } = require('../utils/globalViewCacheInfo');
|
|
10
11
|
|
|
11
12
|
class ViewEngine {
|
|
12
13
|
constructor(options = {}) {
|
|
13
14
|
this.viewsPath = options.viewsPath || './views';
|
|
14
15
|
this.defaultExtension = options.defaultExtension || '.html';
|
|
15
16
|
this.cacheEnabled = options.cacheEnabled !== false; // Por defecto habilitado
|
|
16
|
-
this.viewCache = new Map(); // Cache de vistas compiladas
|
|
17
17
|
this.logger = options.logger || console;
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
// Usar el mapa global para el caché de vistas
|
|
20
|
+
this.viewCache = globalViewCacheInfo.viewCache;
|
|
21
|
+
this.cacheStats = globalViewCacheInfo.cacheStats;
|
|
22
|
+
this.viewDependencies = globalViewCacheInfo.viewDependencies;
|
|
23
|
+
|
|
19
24
|
// Sistema de hooks para extensibilidad
|
|
20
25
|
this.hooks = options.hooks || null;
|
|
21
|
-
|
|
26
|
+
|
|
22
27
|
// Sistema de filtros
|
|
23
28
|
this.filters = new Map();
|
|
24
29
|
this.helpers = new Map(); // Sistema de helpers personalizados
|
|
25
30
|
this.registerDefaultFilters();
|
|
26
31
|
this.registerDefaultHelpers();
|
|
27
|
-
|
|
32
|
+
|
|
28
33
|
// Asegurar que el directorio de vistas existe
|
|
29
34
|
if (!fs.existsSync(this.viewsPath)) {
|
|
30
35
|
fs.mkdirSync(this.viewsPath, { recursive: true });
|
|
@@ -224,6 +229,7 @@ class ViewEngine {
|
|
|
224
229
|
// Intentar obtener del cache si está habilitado
|
|
225
230
|
if (this.cacheEnabled && this.viewCache.has(viewPath)) {
|
|
226
231
|
const cachedView = this.viewCache.get(viewPath);
|
|
232
|
+
this.cacheStats.hits++; // Incrementar contador de hits
|
|
227
233
|
return this.processTemplate(cachedView, data, options);
|
|
228
234
|
}
|
|
229
235
|
|
|
@@ -275,21 +281,30 @@ class ViewEngine {
|
|
|
275
281
|
// Si el cache está habilitado, guardar la vista compilada (sin variables)
|
|
276
282
|
if (this.cacheEnabled) {
|
|
277
283
|
// Aplicar filter antes de cachear la vista
|
|
278
|
-
const contentToCache = this.hooks ?
|
|
284
|
+
const contentToCache = this.hooks ?
|
|
279
285
|
this.hooks.applyFilters('before_view_cache', viewContent, viewPath) : viewContent;
|
|
280
|
-
|
|
286
|
+
|
|
281
287
|
// Verificar con hook si se debe cachear esta vista
|
|
282
|
-
const shouldCache = this.hooks ?
|
|
288
|
+
const shouldCache = this.hooks ?
|
|
283
289
|
this.hooks.applyFilters('should_cache_view', true, viewPath, contentToCache) : true;
|
|
284
|
-
|
|
290
|
+
|
|
285
291
|
if (shouldCache) {
|
|
286
292
|
this.viewCache.set(viewPath, contentToCache);
|
|
287
|
-
|
|
293
|
+
this.cacheStats.misses++; // Incrementar contador de misses (ya que se leyó del disco)
|
|
294
|
+
this.cacheStats.totalViews++; // Incrementar total de vistas únicas
|
|
295
|
+
|
|
296
|
+
// Registrar dependencias de inclusiones
|
|
297
|
+
this.trackViewDependencies(viewPath, contentToCache);
|
|
298
|
+
|
|
288
299
|
// Disparar hook después de cachear
|
|
289
300
|
if (this.hooks) {
|
|
290
301
|
this.hooks.doAction('view_cached', viewPath, contentToCache);
|
|
291
302
|
}
|
|
303
|
+
} else {
|
|
304
|
+
this.cacheStats.misses++; // Aún se leyó del disco aunque no se cacheó
|
|
292
305
|
}
|
|
306
|
+
} else {
|
|
307
|
+
this.cacheStats.misses++; // Se leyó del disco sin cache
|
|
293
308
|
}
|
|
294
309
|
|
|
295
310
|
// Procesar el template con las variables
|
|
@@ -775,6 +790,42 @@ class ViewEngine {
|
|
|
775
790
|
return value;
|
|
776
791
|
}
|
|
777
792
|
|
|
793
|
+
/**
|
|
794
|
+
* Registra las dependencias de inclusiones de una vista
|
|
795
|
+
* @param {string} viewPath - Ruta de la vista principal
|
|
796
|
+
* @param {string} content - Contenido de la vista
|
|
797
|
+
*/
|
|
798
|
+
trackViewDependencies(viewPath, content) {
|
|
799
|
+
// Patrón para encontrar bloques de inclusión como {{include:nombre_vista}}
|
|
800
|
+
const includePattern = /\{\{include:(.*?)\}\}/g;
|
|
801
|
+
let match;
|
|
802
|
+
|
|
803
|
+
while ((match = includePattern.exec(content)) !== null) {
|
|
804
|
+
const includePath = match[1].trim();
|
|
805
|
+
|
|
806
|
+
// Obtener la ruta completa de la vista incluida
|
|
807
|
+
let includedViewPath;
|
|
808
|
+
if (includePath.startsWith('./') || includePath.startsWith('../')) {
|
|
809
|
+
includedViewPath = path.resolve(path.dirname(viewPath), includePath);
|
|
810
|
+
if (!path.extname(includedViewPath)) {
|
|
811
|
+
includedViewPath += this.defaultExtension;
|
|
812
|
+
}
|
|
813
|
+
} else {
|
|
814
|
+
// Usar la ruta de vistas por defecto
|
|
815
|
+
includedViewPath = path.join(this.viewsPath, includePath.replace(/\./g, '/'));
|
|
816
|
+
if (!path.extname(includedViewPath)) {
|
|
817
|
+
includedViewPath += this.defaultExtension;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Registrar la dependencia
|
|
822
|
+
if (!this.viewDependencies.has(viewPath)) {
|
|
823
|
+
this.viewDependencies.set(viewPath, new Set());
|
|
824
|
+
}
|
|
825
|
+
this.viewDependencies.get(viewPath).add(includedViewPath);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
778
829
|
/**
|
|
779
830
|
* Parsea argumentos de filtros o helpers, respetando cadenas entre comillas
|
|
780
831
|
* @param {string} argsString - Cadena de argumentos
|
|
@@ -836,9 +887,17 @@ class ViewEngine {
|
|
|
836
887
|
if (this.hooks) {
|
|
837
888
|
this.hooks.doAction('before_view_cache_clear', this);
|
|
838
889
|
}
|
|
839
|
-
|
|
890
|
+
|
|
891
|
+
// Limpiar solo las vistas asociadas a esta instancia (si se desea)
|
|
892
|
+
// Para el sistema global, limpiar completamente
|
|
840
893
|
this.viewCache.clear();
|
|
894
|
+
this.viewDependencies.clear();
|
|
841
895
|
|
|
896
|
+
// Reiniciar estadísticas
|
|
897
|
+
this.cacheStats.hits = 0;
|
|
898
|
+
this.cacheStats.misses = 0;
|
|
899
|
+
this.cacheStats.totalViews = 0;
|
|
900
|
+
|
|
842
901
|
// Disparar hook después de limpiar el caché
|
|
843
902
|
if (this.hooks) {
|
|
844
903
|
this.hooks.doAction('view_cache_cleared');
|