jerkjs 2.5.6 → 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.
Files changed (46) hide show
  1. package/CHANGELOG.md +167 -79
  2. package/README.md +134 -146
  3. package/RESULTADOS_WAF.md +63 -0
  4. package/doc-2.5/ADMIN_EXTENSION_COMMANDS_MANUAL.md +261 -0
  5. package/doc-2.5/ADMIN_EXTENSION_HOOK_EXAMPLE.md +28 -0
  6. package/doc-2.5/ADMIN_EXTENSION_INTEGRATION_MANUAL.md +232 -0
  7. package/doc-2.5/CACHE_SYSTEM_MAP.md +206 -0
  8. package/doc-2.5/MANUAL_MODULOS_ADMIN.md +287 -0
  9. package/doc-2.5/QUEUE_CLI_MODULE_MANUAL.md +289 -0
  10. package/doc-2.5/QUEUE_SYSTEM_MANUAL.md +320 -0
  11. package/doc-2.5/ROUTE_CACHE_MODULE_MANUAL.md +205 -0
  12. package/doc-2.5/WAF_MODULE_MANUAL.md +229 -0
  13. package/index.js +19 -4
  14. package/jerk-admin-client/README.md +69 -0
  15. package/jerk-admin-client/package.json +23 -0
  16. package/jerk-admin-client.js +257 -0
  17. package/lib/admin/AdminExtension.js +491 -0
  18. package/lib/admin/ModuleLoader.js +77 -0
  19. package/lib/admin/config.js +21 -0
  20. package/lib/admin/modules/CacheModule.js +145 -0
  21. package/lib/admin/modules/ControllerGeneratorModule.js +414 -0
  22. package/lib/admin/modules/QueueManagementModule.js +265 -0
  23. package/lib/admin/modules/RouteCacheModule.js +227 -0
  24. package/lib/admin/modules/RouteManagerModule.js +468 -0
  25. package/lib/admin/modules/STATS_MODULE_README.md +113 -0
  26. package/lib/admin/modules/StatsModule.js +140 -0
  27. package/lib/admin/modules/SystemModule.js +140 -0
  28. package/lib/admin/modules/TimeModule.js +95 -0
  29. package/lib/admin/modules/ViewCacheStatsModule.js +92 -0
  30. package/lib/admin/modules/WAFModule.js +737 -0
  31. package/lib/cache/CacheHooks.js +141 -0
  32. package/lib/core/server.js +223 -77
  33. package/lib/middleware/firewall.js +112 -17
  34. package/lib/mvc/viewEngine.js +89 -5
  35. package/lib/queue/GlobalQueueStorage.js +38 -0
  36. package/lib/queue/QueueSystem.js +451 -0
  37. package/lib/queue/admin_example.js +114 -0
  38. package/lib/queue/example.js +268 -0
  39. package/lib/queue/integration.js +109 -0
  40. package/lib/router/RouteMatcher.js +242 -54
  41. package/lib/utils/globalStats.js +16 -0
  42. package/lib/utils/globalViewCacheInfo.js +16 -0
  43. package/lib/utils/globalWAFStats.js +54 -0
  44. package/package.json +2 -2
  45. package/test-colors.js +46 -0
  46. package/test-help-alias.js +31 -0
@@ -170,26 +170,31 @@ class Firewall {
170
170
  // Path traversal
171
171
  const pathTraversal = /\.\.\//g;
172
172
 
173
- // Verificar patrones en URL
174
- for (const pattern of sqlPatterns) {
175
- if (pattern.test(path)) {
176
- return {
177
- matched: true,
178
- rule: 'sql_injection',
179
- action: 'block',
180
- reason: 'Patrón de SQL Injection detectado'
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
- for (const pattern of xssPatterns) {
186
- if (pattern.test(path)) {
187
- return {
188
- matched: true,
189
- rule: 'xss_attack',
190
- action: 'block',
191
- reason: 'Patrón de XSS detectado'
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
  };
@@ -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
 
@@ -274,7 +280,31 @@ class ViewEngine {
274
280
 
275
281
  // Si el cache está habilitado, guardar la vista compilada (sin variables)
276
282
  if (this.cacheEnabled) {
277
- this.viewCache.set(viewPath, viewContent);
283
+ // Aplicar filter antes de cachear la vista
284
+ const contentToCache = this.hooks ?
285
+ this.hooks.applyFilters('before_view_cache', viewContent, viewPath) : viewContent;
286
+
287
+ // Verificar con hook si se debe cachear esta vista
288
+ const shouldCache = this.hooks ?
289
+ this.hooks.applyFilters('should_cache_view', true, viewPath, contentToCache) : true;
290
+
291
+ if (shouldCache) {
292
+ this.viewCache.set(viewPath, contentToCache);
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
+
299
+ // Disparar hook después de cachear
300
+ if (this.hooks) {
301
+ this.hooks.doAction('view_cached', viewPath, contentToCache);
302
+ }
303
+ } else {
304
+ this.cacheStats.misses++; // Aún se leyó del disco aunque no se cacheó
305
+ }
306
+ } else {
307
+ this.cacheStats.misses++; // Se leyó del disco sin cache
278
308
  }
279
309
 
280
310
  // Procesar el template con las variables
@@ -760,6 +790,42 @@ class ViewEngine {
760
790
  return value;
761
791
  }
762
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
+
763
829
  /**
764
830
  * Parsea argumentos de filtros o helpers, respetando cadenas entre comillas
765
831
  * @param {string} argsString - Cadena de argumentos
@@ -817,7 +883,25 @@ class ViewEngine {
817
883
  * Limpia el cache de vistas
818
884
  */
819
885
  clearCache() {
886
+ // Disparar hook antes de limpiar el caché
887
+ if (this.hooks) {
888
+ this.hooks.doAction('before_view_cache_clear', this);
889
+ }
890
+
891
+ // Limpiar solo las vistas asociadas a esta instancia (si se desea)
892
+ // Para el sistema global, limpiar completamente
820
893
  this.viewCache.clear();
894
+ this.viewDependencies.clear();
895
+
896
+ // Reiniciar estadísticas
897
+ this.cacheStats.hits = 0;
898
+ this.cacheStats.misses = 0;
899
+ this.cacheStats.totalViews = 0;
900
+
901
+ // Disparar hook después de limpiar el caché
902
+ if (this.hooks) {
903
+ this.hooks.doAction('view_cache_cleared');
904
+ }
821
905
  }
822
906
  }
823
907
 
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Almacenamiento Global de Colas para el Framework JERK
3
+ * Implementación del componente queue/GlobalQueueStorage.js
4
+ * Sistema de almacenamiento compartido globalmente para las colas
5
+ */
6
+
7
+ // Crear espacio de almacenamiento global para las colas si no existe
8
+ if (!global.jerkQueues) {
9
+ global.jerkQueues = new Map();
10
+ }
11
+
12
+ if (!global.jerkQueueSystems) {
13
+ global.jerkQueueSystems = new Map();
14
+ }
15
+
16
+ if (!global.jerkQueueSystem) {
17
+ // Importar dinámicamente para evitar dependencias circulares
18
+ const QueueSystem = require('./QueueSystem');
19
+ const HookSystem = require('../core/hooks');
20
+
21
+ // Obtener hooks globales del framework si están disponibles
22
+ let globalHooks = null;
23
+ try {
24
+ const framework = require('../../index.js');
25
+ globalHooks = framework.hooks;
26
+ } catch (error) {
27
+ // Si no están disponibles, crear instancia local
28
+ globalHooks = new HookSystem();
29
+ }
30
+
31
+ global.jerkQueueSystem = new QueueSystem({ hooks: globalHooks });
32
+ }
33
+
34
+ module.exports = {
35
+ queues: global.jerkQueues,
36
+ queueSystems: global.jerkQueueSystems,
37
+ queueSystem: global.jerkQueueSystem
38
+ };