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.
- package/CHANGELOG.md +167 -79
- package/README.md +134 -146
- package/RESULTADOS_WAF.md +63 -0
- package/doc-2.5/ADMIN_EXTENSION_COMMANDS_MANUAL.md +261 -0
- package/doc-2.5/ADMIN_EXTENSION_HOOK_EXAMPLE.md +28 -0
- package/doc-2.5/ADMIN_EXTENSION_INTEGRATION_MANUAL.md +232 -0
- package/doc-2.5/CACHE_SYSTEM_MAP.md +206 -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 +19 -4
- 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 +491 -0
- package/lib/admin/ModuleLoader.js +77 -0
- package/lib/admin/config.js +21 -0
- package/lib/admin/modules/CacheModule.js +145 -0
- 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 +113 -0
- package/lib/admin/modules/StatsModule.js +140 -0
- package/lib/admin/modules/SystemModule.js +140 -0
- package/lib/admin/modules/TimeModule.js +95 -0
- package/lib/admin/modules/ViewCacheStatsModule.js +92 -0
- package/lib/admin/modules/WAFModule.js +737 -0
- package/lib/cache/CacheHooks.js +141 -0
- package/lib/core/server.js +223 -77
- package/lib/middleware/firewall.js +112 -17
- package/lib/mvc/viewEngine.js +89 -5
- 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/router/RouteMatcher.js +242 -54
- package/lib/utils/globalStats.js +16 -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
|
@@ -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
|
|
|
@@ -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
|
-
|
|
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
|
+
};
|