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.
Files changed (36) hide show
  1. package/CHANGELOG.md +162 -99
  2. package/README.md +113 -190
  3. package/RESULTADOS_WAF.md +63 -0
  4. package/doc-2.5/MANUAL_MODULOS_ADMIN.md +287 -0
  5. package/doc-2.5/QUEUE_CLI_MODULE_MANUAL.md +289 -0
  6. package/doc-2.5/QUEUE_SYSTEM_MANUAL.md +320 -0
  7. package/doc-2.5/ROUTE_CACHE_MODULE_MANUAL.md +205 -0
  8. package/doc-2.5/WAF_MODULE_MANUAL.md +229 -0
  9. package/index.js +12 -3
  10. package/jerk-admin-client/README.md +69 -0
  11. package/jerk-admin-client/package.json +23 -0
  12. package/jerk-admin-client.js +257 -0
  13. package/lib/admin/AdminExtension.js +74 -19
  14. package/lib/admin/modules/ControllerGeneratorModule.js +414 -0
  15. package/lib/admin/modules/QueueManagementModule.js +265 -0
  16. package/lib/admin/modules/RouteCacheModule.js +227 -0
  17. package/lib/admin/modules/RouteManagerModule.js +468 -0
  18. package/lib/admin/modules/STATS_MODULE_README.md +15 -0
  19. package/lib/admin/modules/ViewCacheStatsModule.js +92 -0
  20. package/lib/admin/modules/WAFModule.js +737 -0
  21. package/lib/core/server.js +72 -69
  22. package/lib/middleware/firewall.js +112 -17
  23. package/lib/mvc/viewEngine.js +69 -10
  24. package/lib/queue/GlobalQueueStorage.js +38 -0
  25. package/lib/queue/QueueSystem.js +451 -0
  26. package/lib/queue/admin_example.js +114 -0
  27. package/lib/queue/example.js +268 -0
  28. package/lib/queue/integration.js +109 -0
  29. package/lib/utils/globalViewCacheInfo.js +16 -0
  30. package/lib/utils/globalWAFStats.js +54 -0
  31. package/package.json +2 -2
  32. package/test-colors.js +46 -0
  33. package/test-help-alias.js +31 -0
  34. package/ESTADISTICAS_RENDIMIENTO.md +0 -106
  35. package/debug_hook.js +0 -11
  36. package/docs/CACHE_SYSTEM_MAP.md +0 -206
@@ -0,0 +1,737 @@
1
+ /**
2
+ * Módulo de Web Application Firewall (WAF) para la Extensión de Administración de JERK Framework
3
+ * Módulo personalizado para mostrar estadísticas del firewall
4
+ */
5
+
6
+ class WAFModule {
7
+ /**
8
+ * Constructor del módulo WAF
9
+ * @param {Object} adminExtension - Instancia de la extensión de administración
10
+ */
11
+ constructor(adminExtension) {
12
+ this.adminExtension = adminExtension;
13
+ this.name = 'WAF Module';
14
+ this.description = 'Módulo para mostrar estadísticas del Web Application Firewall';
15
+ this.commands = ['waf-status', 'waf-stats', 'waf-blocked', 'waf-security', 'waf-block-ip', 'waf-unblock-ip', 'waf-whitelist', 'waf-blacklist', 'waf-x-headers', 'create-x-rule', 'remove-x-rule', 'save-rules', 'load-rules'];
16
+
17
+ // Mapa para almacenar el estado de procesos interactivos
18
+ this.currentProcess = new Map();
19
+
20
+ // Cargar el objeto global de estadísticas del firewall
21
+ this.globalWAFStats = require('../../utils/globalWAFStats').globalWAFStats;
22
+ }
23
+
24
+ /**
25
+ * Manejador para los comandos del módulo WAF
26
+ * @param {string} command - Comando a ejecutar
27
+ * @param {Object} socket - Socket de la conexión
28
+ */
29
+ handleCommand(command, socket) {
30
+ switch (command) {
31
+ case 'waf-status':
32
+ case 'waf-stats':
33
+ this.showWAFStats(socket);
34
+ break;
35
+ case 'waf-blocked':
36
+ case 'waf-security':
37
+ this.showWAFBlockedStats(socket);
38
+ break;
39
+ case 'waf-block-ip':
40
+ this.startInteractiveProcess(socket, 'block_ip');
41
+ this.promptForIPBlock(socket);
42
+ break;
43
+ case 'waf-unblock-ip':
44
+ this.startInteractiveProcess(socket, 'unblock_ip');
45
+ this.promptForIPUnblock(socket);
46
+ break;
47
+ case 'waf-whitelist':
48
+ this.startInteractiveProcess(socket, 'whitelist_ip');
49
+ this.promptForWhitelist(socket);
50
+ break;
51
+ case 'waf-blacklist':
52
+ this.startInteractiveProcess(socket, 'blacklist_ip');
53
+ this.promptForBlacklist(socket);
54
+ break;
55
+ case 'waf-x-headers':
56
+ this.showXHeaderRules(socket);
57
+ break;
58
+ case 'create-x-rule':
59
+ this.startInteractiveProcess(socket, 'x_header_name');
60
+ this.promptForXHeaderRule(socket);
61
+ break;
62
+ case 'remove-x-rule':
63
+ this.listXHeaderRulesToRemove(socket);
64
+ break;
65
+ case 'save-rules':
66
+ this.saveRulesToFile(socket);
67
+ break;
68
+ case 'load-rules':
69
+ this.loadRulesFromFile(socket);
70
+ break;
71
+ default:
72
+ socket.write(`Comando desconocido para el módulo WAF: ${command}\n`);
73
+ socket.write(`Comandos disponibles: waf-status, waf-stats, waf-blocked, waf-security, waf-block-ip, waf-unblock-ip, waf-whitelist, waf-blacklist, waf-x-headers, create-x-rule, remove-x-rule, save-rules, load-rules\n\n`);
74
+ socket.write(`> `);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Muestra estadísticas generales del WAF
80
+ * @param {Object} socket - Socket de la conexión
81
+ */
82
+ showWAFStats(socket) {
83
+ const formattedRequestBytes = this.formatBytes(this.globalWAFStats.requestBytes);
84
+ const formattedResponseBytes = this.formatBytes(this.globalWAFStats.responseBytes);
85
+
86
+ socket.write('\n=== Estadísticas del WAF ===\n');
87
+ socket.write(`Solicitudes procesadas: ${this.globalWAFStats.requestsProcessed}\n`);
88
+ socket.write(`Solicitudes procesadas (KB): ${formattedRequestBytes}\n`);
89
+ socket.write(`Respuestas enviadas: ${this.globalWAFStats.responsesSent}\n`);
90
+ socket.write(`Respuestas enviadas (KB): ${formattedResponseBytes}\n`);
91
+ socket.write(`IPs en lista blanca: ${this.globalWAFStats.whitelist.size}\n`);
92
+ socket.write(`IPs en lista negra: ${this.globalWAFStats.blacklist.size}\n`);
93
+ socket.write(`Reglas de seguridad activas: ${this.globalWAFStats.securityRules.size}\n\n`);
94
+ socket.write(`> `);
95
+ }
96
+
97
+ /**
98
+ * Muestra estadísticas de bloqueos del WAF
99
+ * @param {Object} socket - Socket de la conexión
100
+ */
101
+ showWAFBlockedStats(socket) {
102
+ socket.write('\n=== Estadísticas de Seguridad del WAF ===\n');
103
+ socket.write(`IPs bloqueadas temporalmente: ${this.globalWAFStats.blockedIPs.size}\n`);
104
+ socket.write(`Intentos de ataque detectados: ${this.globalWAFStats.attackAttempts}\n`);
105
+ socket.write(`Tipos de ataques detectados: ${this.globalWAFStats.attackTypes.size}\n`);
106
+ socket.write(`Registros de seguridad: ${this.globalWAFStats.securityLogs.length}\n`);
107
+
108
+ // Mostrar accesos a rutas
109
+ if (this.globalWAFStats.routeAccesses.size > 0) {
110
+ socket.write('\nAccesos a rutas (Input/Output):\n');
111
+ for (const [route, count] of this.globalWAFStats.routeAccesses) {
112
+ socket.write(` ${route}: ${count} accesos\n`);
113
+ }
114
+ } else {
115
+ socket.write('\nNo hay accesos a rutas registrados.\n');
116
+ }
117
+
118
+ // Mostrar endpoints más accedidos
119
+ if (this.globalWAFStats.endpointHits.size > 0) {
120
+ socket.write('\nEndpoints más accedidos:\n');
121
+
122
+ // Convertir el mapa a array y ordenar por número de hits
123
+ const sortedEndpoints = Array.from(this.globalWAFStats.endpointHits.entries())
124
+ .sort((a, b) => b[1] - a[1])
125
+ .slice(0, 10); // Mostrar top 10
126
+
127
+ for (const [endpoint, hits] of sortedEndpoints) {
128
+ socket.write(` ${endpoint}: ${hits} hits\n`);
129
+ }
130
+ } else {
131
+ socket.write('\nNo hay datos de endpoints accedidos.\n');
132
+ }
133
+
134
+ socket.write('\n> ');
135
+ }
136
+
137
+ /**
138
+ * Inicia un proceso interactivo
139
+ * @param {Object} socket - Socket de la conexión
140
+ * @param {string} step - Paso del proceso
141
+ */
142
+ startInteractiveProcess(socket, step) {
143
+ const sessionId = this.getSessionId(socket);
144
+ this.currentProcess.set(sessionId, { step });
145
+ }
146
+
147
+ /**
148
+ * Solicita la IP para bloquear
149
+ * @param {Object} socket - Socket de la conexión
150
+ */
151
+ promptForIPBlock(socket) {
152
+ socket.write('\nIntroduce la IP a bloquear temporalmente (ej: 192.168.1.100):\n> ');
153
+ }
154
+
155
+ /**
156
+ * Solicita la IP para desbloquear
157
+ * @param {Object} socket - Socket de la conexión
158
+ */
159
+ promptForIPUnblock(socket) {
160
+ socket.write('\nIntroduce la IP a desbloquear (ej: 192.168.1.100):\n> ');
161
+ }
162
+
163
+ /**
164
+ * Solicita la IP para agregar a la lista blanca
165
+ * @param {Object} socket - Socket de la conexión
166
+ */
167
+ promptForWhitelist(socket) {
168
+ socket.write('\nIntroduce la IP a agregar a la lista blanca (ej: 192.168.1.100):\n> ');
169
+ }
170
+
171
+ /**
172
+ * Solicita la IP para agregar a la lista negra
173
+ * @param {Object} socket - Socket de la conexión
174
+ */
175
+ promptForBlacklist(socket) {
176
+ socket.write('\nIntroduce la IP a agregar a la lista negra (ej: 192.168.1.100):\n> ');
177
+ }
178
+
179
+ /**
180
+ * Muestra las reglas de headers X-
181
+ * @param {Object} socket - Socket de la conexión
182
+ */
183
+ showXHeaderRules(socket) {
184
+ socket.write('\n=== Reglas de Headers X- ===\n');
185
+
186
+ if (this.globalWAFStats.xHeaderRules.size === 0) {
187
+ socket.write('No hay reglas de headers X- configuradas.\n\n');
188
+ socket.write('¿Deseas crear una nueva regla? (sí/no):\n> ');
189
+ // Aquí se podría implementar la lógica para crear reglas interactivamente
190
+ return;
191
+ }
192
+
193
+ socket.write(`Total de reglas: ${this.globalWAFStats.xHeaderRules.size}\n\n`);
194
+
195
+ for (const [ruleId, rule] of this.globalWAFStats.xHeaderRules) {
196
+ socket.write(`ID: ${ruleId}\n`);
197
+ socket.write(` Header: ${rule.headerName}\n`);
198
+ socket.write(` Patrón: ${rule.pattern}\n`);
199
+ socket.write(` Acción: ${rule.action}\n`);
200
+ socket.write(` Razón: ${rule.reason}\n\n`);
201
+ }
202
+
203
+ socket.write(`> `);
204
+ }
205
+
206
+ /**
207
+ * Solicita la creación de una regla de header X-
208
+ * @param {Object} socket - Socket de la conexión
209
+ */
210
+ promptForXHeaderRule(socket) {
211
+ const sessionId = this.getSessionId(socket);
212
+
213
+ // Iniciar proceso interactivo para crear regla
214
+ this.currentProcess.set(sessionId, {
215
+ step: 'x_header_name',
216
+ action: 'create_rule'
217
+ });
218
+
219
+ socket.write('\n=== Crear Regla de Header X- ===\n');
220
+ socket.write('Introduce el nombre del header X- (ej: X-Forwarded-For, X-Real-IP):\n> ');
221
+ }
222
+
223
+ /**
224
+ * Maneja la entrada del nombre del header X-
225
+ * @param {Object} socket - Socket de la conexión
226
+ * @param {string} headerName - Nombre del header
227
+ */
228
+ handleXHeaderName(socket, headerName) {
229
+ if (!headerName.toLowerCase().startsWith('x-')) {
230
+ socket.write('El nombre del header debe comenzar con "X-". Introduce un nombre válido:\n> ');
231
+ return;
232
+ }
233
+
234
+ const sessionId = this.getSessionId(socket);
235
+ const processState = this.currentProcess.get(sessionId);
236
+ processState.headerName = headerName.toLowerCase();
237
+ processState.step = 'x_header_pattern';
238
+
239
+ socket.write(`\nHeader seleccionado: ${headerName}\n`);
240
+ socket.write('Introduce el patrón para coincidir (cadena o expresión regular):\n> ');
241
+ }
242
+
243
+ /**
244
+ * Maneja la entrada del patrón para la regla X-
245
+ * @param {Object} socket - Socket de la conexión
246
+ * @param {string} pattern - Patrón para coincidir
247
+ */
248
+ handleXHeaderPattern(socket, pattern) {
249
+ if (!pattern) {
250
+ socket.write('El patrón no puede estar vacío. Introduce un patrón válido:\n> ');
251
+ return;
252
+ }
253
+
254
+ const sessionId = this.getSessionId(socket);
255
+ const processState = this.currentProcess.get(sessionId);
256
+ processState.pattern = pattern;
257
+ processState.step = 'x_header_action';
258
+
259
+ socket.write(`\nPatrón: ${pattern}\n`);
260
+ socket.write('Selecciona la acción a tomar:\n');
261
+ socket.write('1. block - Bloquear solicitudes que coincidan\n');
262
+ socket.write('2. log - Registrar solicitudes que coincidan\n');
263
+ socket.write('3. monitor - Monitorear solicitudes que coincidan\n');
264
+ socket.write('Escribe el número o el nombre de la acción:\n> ');
265
+ }
266
+
267
+ /**
268
+ * Maneja la selección de acción para la regla X-
269
+ * @param {Object} socket - Socket de la conexión
270
+ * @param {string} action - Acción seleccionada
271
+ */
272
+ handleXHeaderAction(socket, action) {
273
+ const actionMap = {
274
+ '1': 'block',
275
+ '2': 'log',
276
+ '3': 'monitor',
277
+ 'block': 'block',
278
+ 'log': 'log',
279
+ 'monitor': 'monitor'
280
+ };
281
+
282
+ const selectedAction = actionMap[action.toLowerCase()];
283
+ if (!selectedAction) {
284
+ socket.write('Acción inválida. Por favor selecciona 1 (block), 2 (log) o 3 (monitor):\n> ');
285
+ return;
286
+ }
287
+
288
+ const sessionId = this.getSessionId(socket);
289
+ const processState = this.currentProcess.get(sessionId);
290
+ processState.action = selectedAction;
291
+ processState.step = 'x_header_reason';
292
+
293
+ socket.write(`\nAcción seleccionada: ${selectedAction}\n`);
294
+ socket.write('Introduce la razón para la regla (opcional):\n> ');
295
+ }
296
+
297
+ /**
298
+ * Maneja la entrada de la razón para la regla X-
299
+ * @param {Object} socket - Socket de la conexión
300
+ * @param {string} reason - Razón para la regla
301
+ */
302
+ handleXHeaderReason(socket, reason) {
303
+ const sessionId = this.getSessionId(socket);
304
+ const processState = this.currentProcess.get(sessionId);
305
+
306
+ const ruleReason = reason || `Regla de header X-${processState.headerName} creada por administrador`;
307
+ processState.reason = ruleReason;
308
+
309
+ // Crear la regla
310
+ const ruleId = `xheader_${processState.headerName.replace(/[^a-z0-9]/g, '_')}_${Date.now()}`;
311
+ this.globalWAFStats.xHeaderRules.set(ruleId, {
312
+ headerName: processState.headerName,
313
+ pattern: processState.pattern,
314
+ action: processState.action,
315
+ reason: processState.reason,
316
+ createdAt: new Date().toISOString()
317
+ });
318
+
319
+ socket.write(`\n✅ Regla de header X- creada exitosamente:\n`);
320
+ socket.write(` ID: ${ruleId}\n`);
321
+ socket.write(` Header: ${processState.headerName}\n`);
322
+ socket.write(` Patrón: ${processState.pattern}\n`);
323
+ socket.write(` Acción: ${processState.action}\n`);
324
+ socket.write(` Razón: ${processState.reason}\n\n`);
325
+
326
+ socket.write(`> `);
327
+
328
+ // Limpiar el proceso
329
+ this.currentProcess.delete(sessionId);
330
+ }
331
+
332
+ /**
333
+ * Obtiene el ID de sesión para el socket
334
+ * @param {Object} socket - Socket de la conexión
335
+ * @returns {string} - ID de sesión
336
+ */
337
+ getSessionId(socket) {
338
+ return `${socket.remoteAddress}:${socket.remotePort}`;
339
+ }
340
+
341
+ /**
342
+ * Procesa la entrada del usuario durante el proceso interactivo
343
+ * @param {Object} socket - Socket de la conexión
344
+ * @param {string} input - Entrada del usuario
345
+ */
346
+ processInput(socket, input) {
347
+ const sessionId = this.getSessionId(socket);
348
+ const processState = this.currentProcess.get(sessionId);
349
+
350
+ if (!processState) {
351
+ socket.write('No hay proceso interactivo activo.\n> ');
352
+ return;
353
+ }
354
+
355
+ const userInput = input.trim();
356
+
357
+ if (userInput.toLowerCase() === 'cancel' || userInput.toLowerCase() === 'back') {
358
+ socket.write('\nOperación cancelada.\n> ');
359
+ this.currentProcess.delete(sessionId);
360
+ return;
361
+ }
362
+
363
+ // Validar formato de IP solo para operaciones de IP
364
+ if (['block_ip', 'unblock_ip', 'whitelist_ip', 'blacklist_ip'].includes(processState.step)) {
365
+ const ipPattern = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
366
+ if (!ipPattern.test(userInput)) {
367
+ socket.write('\nFormato de IP inválido.\n> ');
368
+ return;
369
+ }
370
+ }
371
+
372
+ switch (processState.step) {
373
+ case 'block_ip':
374
+ this.handleIPBlock(socket, sessionId, userInput);
375
+ break;
376
+ case 'unblock_ip':
377
+ this.handleIPUnblock(socket, sessionId, userInput);
378
+ break;
379
+ case 'whitelist_ip':
380
+ this.handleWhitelistIP(socket, sessionId, userInput);
381
+ break;
382
+ case 'blacklist_ip':
383
+ this.handleBlacklistIP(socket, sessionId, userInput);
384
+ break;
385
+ case 'x_header_name':
386
+ this.handleXHeaderName(socket, userInput);
387
+ break;
388
+ case 'x_header_pattern':
389
+ this.handleXHeaderPattern(socket, userInput);
390
+ break;
391
+ case 'x_header_action':
392
+ this.handleXHeaderAction(socket, userInput);
393
+ break;
394
+ case 'x_header_reason':
395
+ this.handleXHeaderReason(socket, userInput);
396
+ break;
397
+ default:
398
+ socket.write(`\nEstado desconocido: ${processState.step}\n> `);
399
+ this.currentProcess.delete(sessionId);
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Maneja el bloqueo de una IP
405
+ * @param {Object} socket - Socket de la conexión
406
+ * @param {string} sessionId - ID de la sesión
407
+ * @param {string} ip - IP a bloquear
408
+ */
409
+ handleIPBlock(socket, sessionId, ip) {
410
+ // Agregar IP a la lista negra temporal
411
+ this.globalWAFStats.blockedIPs.set(ip, {
412
+ blockedUntil: Date.now() + 900000, // 15 minutos
413
+ reason: 'Bloqueada manualmente por administrador',
414
+ attempts: 1,
415
+ timestamp: new Date().toISOString()
416
+ });
417
+
418
+ socket.write(`\nIP ${ip} bloqueada temporalmente.\n\n> `);
419
+ this.currentProcess.delete(sessionId);
420
+ }
421
+
422
+ /**
423
+ * Maneja el desbloqueo de una IP
424
+ * @param {Object} socket - Socket de la conexión
425
+ * @param {string} sessionId - ID de la sesión
426
+ * @param {string} ip - IP a desbloquear
427
+ */
428
+ handleIPUnblock(socket, sessionId, ip) {
429
+ if (this.globalWAFStats.blockedIPs.has(ip)) {
430
+ this.globalWAFStats.blockedIPs.delete(ip);
431
+ socket.write(`\nIP ${ip} desbloqueada.\n\n> `);
432
+ } else {
433
+ socket.write(`\nLa IP ${ip} no está bloqueada.\n\n> `);
434
+ }
435
+ this.currentProcess.delete(sessionId);
436
+ }
437
+
438
+ /**
439
+ * Maneja la adición de una IP a la lista blanca
440
+ * @param {Object} socket - Socket de la conexión
441
+ * @param {string} sessionId - ID de la sesión
442
+ * @param {string} ip - IP a agregar a la lista blanca
443
+ */
444
+ handleWhitelistIP(socket, sessionId, ip) {
445
+ this.globalWAFStats.whitelist.add(ip);
446
+ socket.write(`\nIP ${ip} agregada a la lista blanca.\n\n> `);
447
+ this.currentProcess.delete(sessionId);
448
+ }
449
+
450
+ /**
451
+ * Maneja la adición de una IP a la lista negra
452
+ * @param {Object} socket - Socket de la conexión
453
+ * @param {string} sessionId - ID de la sesión
454
+ * @param {string} ip - IP a agregar a la lista negra
455
+ */
456
+ handleBlacklistIP(socket, sessionId, ip) {
457
+ this.globalWAFStats.blacklist.add(ip);
458
+ // También eliminar de la lista blanca si estaba
459
+ this.globalWAFStats.whitelist.delete(ip);
460
+ // Y eliminar de la lista de bloqueadas temporalmente si estaba
461
+ this.globalWAFStats.blockedIPs.delete(ip);
462
+
463
+ socket.write(`\nIP ${ip} agregada a la lista negra.\n\n> `);
464
+ this.currentProcess.delete(sessionId);
465
+ }
466
+
467
+ /**
468
+ * Lista las reglas X-Header disponibles para remover
469
+ * @param {Object} socket - Socket de la conexión
470
+ */
471
+ listXHeaderRulesToRemove(socket) {
472
+ if (this.globalWAFStats.xHeaderRules.size === 0) {
473
+ socket.write('\nNo hay reglas X-Header para remover.\n\n> ');
474
+ return;
475
+ }
476
+
477
+ socket.write('\n=== Reglas X-Header Disponibles para Remover ===\n');
478
+ const rulesArray = Array.from(this.globalWAFStats.xHeaderRules.entries());
479
+
480
+ for (let i = 0; i < rulesArray.length; i++) {
481
+ const [ruleId, rule] = rulesArray[i];
482
+ socket.write(`${i + 1}. ${ruleId}\n`);
483
+ socket.write(` Header: ${rule.headerName}\n`);
484
+ socket.write(` Patrón: ${rule.pattern}\n`);
485
+ socket.write(` Acción: ${rule.action}\n`);
486
+ socket.write(` Razón: ${rule.reason}\n\n`);
487
+ }
488
+
489
+ socket.write('Introduce el número de la regla a remover (o "cancel" para cancelar):\n> ');
490
+
491
+ // Iniciar proceso interactivo para remover regla
492
+ const sessionId = this.getSessionId(socket);
493
+ this.currentProcess.set(sessionId, { step: 'remove_x_rule_selection' });
494
+ }
495
+
496
+ /**
497
+ * Guarda las reglas a un archivo
498
+ * @param {Object} socket - Socket de la conexión
499
+ */
500
+ saveRulesToFile(socket) {
501
+ const fs = require('fs');
502
+ const path = require('path');
503
+
504
+ try {
505
+ // Convertir las reglas a un formato serializable
506
+ const rulesData = {
507
+ xHeaderRules: Array.from(this.globalWAFStats.xHeaderRules.entries()),
508
+ timestamp: new Date().toISOString(),
509
+ version: '2.5.8'
510
+ };
511
+
512
+ const rulesDir = path.join(process.cwd(), 'waf-rules');
513
+ if (!fs.existsSync(rulesDir)) {
514
+ fs.mkdirSync(rulesDir, { recursive: true });
515
+ }
516
+
517
+ const filePath = path.join(rulesDir, `waf-rules-${Date.now()}.json`);
518
+ fs.writeFileSync(filePath, JSON.stringify(rulesData, null, 2));
519
+
520
+ socket.write(`\n✅ Reglas guardadas exitosamente en: ${filePath}\n`);
521
+ socket.write(`Total de reglas guardadas: ${this.globalWAFStats.xHeaderRules.size}\n\n> `);
522
+ } catch (error) {
523
+ socket.write(`\n❌ Error al guardar reglas: ${error.message}\n\n> `);
524
+ }
525
+ }
526
+
527
+ /**
528
+ * Carga reglas desde un archivo
529
+ * @param {Object} socket - Socket de la conexión
530
+ */
531
+ loadRulesFromFile(socket) {
532
+ const fs = require('fs');
533
+ const path = require('path');
534
+
535
+ try {
536
+ const rulesDir = path.join(process.cwd(), 'waf-rules');
537
+ if (!fs.existsSync(rulesDir)) {
538
+ socket.write('\nNo hay directorio de reglas para cargar.\n\n> ');
539
+ return;
540
+ }
541
+
542
+ const files = fs.readdirSync(rulesDir);
543
+ const jsonFiles = files.filter(file => file.endsWith('.json'));
544
+
545
+ if (jsonFiles.length === 0) {
546
+ socket.write('\nNo hay archivos de reglas para cargar.\n\n> ');
547
+ return;
548
+ }
549
+
550
+ socket.write('\n=== Archivos de Reglas Disponibles ===\n');
551
+ for (let i = 0; i < jsonFiles.length; i++) {
552
+ socket.write(`${i + 1}. ${jsonFiles[i]}\n`);
553
+ }
554
+
555
+ socket.write('\nIntroduce el número del archivo a cargar (o "cancel" para cancelar):\n> ');
556
+
557
+ // Iniciar proceso interactivo para cargar regla
558
+ const sessionId = this.getSessionId(socket);
559
+ this.currentProcess.set(sessionId, { step: 'load_rules_selection', files: jsonFiles });
560
+ } catch (error) {
561
+ socket.write(`\n❌ Error al cargar reglas: ${error.message}\n\n> `);
562
+ }
563
+ }
564
+
565
+ /**
566
+ * Formatea bytes a una unidad legible
567
+ * @param {number} bytes - Cantidad en bytes
568
+ * @returns {string} - Cantidad formateada con unidad
569
+ */
570
+ formatBytes(bytes) {
571
+ // Manejar valores nulos o indefinidos
572
+ if (bytes == null || isNaN(bytes) || bytes === 0) return '0 Bytes';
573
+ const k = 1024;
574
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
575
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
576
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
577
+ }
578
+
579
+ /**
580
+ * Procesa la entrada del usuario durante el proceso interactivo
581
+ * @param {Object} socket - Socket de la conexión
582
+ * @param {string} input - Entrada del usuario
583
+ */
584
+ processInput(socket, input) {
585
+ const sessionId = this.getSessionId(socket);
586
+ const processState = this.currentProcess.get(sessionId);
587
+
588
+ if (!processState) {
589
+ socket.write('No hay proceso interactivo activo.\n> ');
590
+ return;
591
+ }
592
+
593
+ const userInput = input.trim();
594
+
595
+ if (userInput.toLowerCase() === 'cancel') {
596
+ socket.write('\nOperación cancelada.\n> ');
597
+ this.currentProcess.delete(sessionId);
598
+ return;
599
+ }
600
+
601
+ switch (processState.step) {
602
+ case 'x_header_name':
603
+ this.handleXHeaderName(socket, userInput);
604
+ break;
605
+ case 'x_header_pattern':
606
+ this.handleXHeaderPattern(socket, userInput);
607
+ break;
608
+ case 'x_header_action':
609
+ this.handleXHeaderAction(socket, userInput);
610
+ break;
611
+ case 'x_header_reason':
612
+ this.handleXHeaderReason(socket, userInput);
613
+ break;
614
+ case 'remove_x_rule_selection':
615
+ this.handleRemoveXRuleSelection(socket, sessionId, userInput);
616
+ break;
617
+ case 'load_rules_selection':
618
+ this.handleLoadRulesSelection(socket, sessionId, userInput, processState.files);
619
+ break;
620
+ case 'confirm_load_rules':
621
+ // Actualizar el estado para usar el método correcto
622
+ this.currentProcess.set(sessionId, {
623
+ step: 'waiting_confirmation',
624
+ rulesData: processState.rulesData,
625
+ filePath: processState.filePath
626
+ });
627
+ if (userInput.toLowerCase() === 'sí' || userInput.toLowerCase() === 'si' || userInput.toLowerCase() === 'yes') {
628
+ // Reemplazar las reglas actuales con las del archivo
629
+ this.globalWAFStats.xHeaderRules.clear();
630
+ for (const [ruleId, rule] of processState.rulesData) {
631
+ this.globalWAFStats.xHeaderRules.set(ruleId, rule);
632
+ }
633
+
634
+ socket.write(`\n✅ Reglas cargadas exitosamente desde: ${processState.filePath}\n`);
635
+ socket.write(`Total de reglas cargadas: ${processState.rulesData.length}\n\n> `);
636
+ } else {
637
+ socket.write('\nCarga de reglas cancelada.\n\n> ');
638
+ }
639
+ this.currentProcess.delete(sessionId);
640
+ break;
641
+ default:
642
+ socket.write(`\nEstado desconocido: ${processState.step}\n> `);
643
+ this.currentProcess.delete(sessionId);
644
+ }
645
+ }
646
+
647
+ /**
648
+ * Maneja la selección de regla X-Header a remover
649
+ * @param {Object} socket - Socket de la conexión
650
+ * @param {string} sessionId - ID de la sesión
651
+ * @param {string} selection - Selección del usuario
652
+ */
653
+ handleRemoveXRuleSelection(socket, sessionId, selection) {
654
+ const rulesArray = Array.from(this.globalWAFStats.xHeaderRules.entries());
655
+ const ruleIndex = parseInt(selection) - 1;
656
+
657
+ if (isNaN(ruleIndex) || ruleIndex < 0 || ruleIndex >= rulesArray.length) {
658
+ socket.write('\nNúmero de regla inválido.\n> ');
659
+ return;
660
+ }
661
+
662
+ const [ruleId] = rulesArray[ruleIndex];
663
+
664
+ // Remover la regla
665
+ this.globalWAFStats.xHeaderRules.delete(ruleId);
666
+
667
+ socket.write(`\n✅ Regla "${ruleId}" removida exitosamente.\n\n> `);
668
+ this.currentProcess.delete(sessionId);
669
+ }
670
+
671
+ /**
672
+ * Maneja la selección de archivo de reglas a cargar
673
+ * @param {Object} socket - Socket de la conexión
674
+ * @param {string} sessionId - ID de la sesión
675
+ * @param {string} selection - Selección del usuario
676
+ * @param {Array} files - Lista de archivos disponibles
677
+ */
678
+ handleLoadRulesSelection(socket, sessionId, selection, files) {
679
+ const fileIndex = parseInt(selection) - 1;
680
+
681
+ if (isNaN(fileIndex) || fileIndex < 0 || fileIndex >= files.length) {
682
+ socket.write('\nNúmero de archivo inválido.\n> ');
683
+ return;
684
+ }
685
+
686
+ const fs = require('fs');
687
+ const path = require('path');
688
+ const filePath = path.join(process.cwd(), 'waf-rules', files[fileIndex]);
689
+
690
+ try {
691
+ const rulesData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
692
+
693
+ // Cargar las reglas
694
+ if (rulesData.xHeaderRules && Array.isArray(rulesData.xHeaderRules)) {
695
+ // Limpiar reglas existentes si se desea
696
+ socket.write('\n¿Deseas reemplazar las reglas actuales? (sí/no):\n> ');
697
+
698
+ // Actualizar el estado para esperar confirmación
699
+ this.currentProcess.set(sessionId, {
700
+ step: 'confirm_load_rules',
701
+ rulesData: rulesData.xHeaderRules,
702
+ filePath: filePath
703
+ });
704
+ }
705
+ } catch (error) {
706
+ socket.write(`\n❌ Error al cargar archivo: ${error.message}\n\n> `);
707
+ this.currentProcess.delete(sessionId);
708
+ }
709
+ }
710
+
711
+ /**
712
+ * Maneja la confirmación de carga de reglas
713
+ * @param {Object} socket - Socket de la conexión
714
+ * @param {string} sessionId - ID de la sesión
715
+ * @param {string} confirmation - Confirmación del usuario
716
+ * @param {Array} rulesData - Datos de reglas a cargar
717
+ * @param {string} filePath - Ruta del archivo
718
+ */
719
+ handleLoadRulesConfirmation(socket, sessionId, confirmation, rulesData, filePath) {
720
+ if (confirmation.toLowerCase() === 'sí' || confirmation.toLowerCase() === 'si' || confirmation.toLowerCase() === 'yes') {
721
+ // Reemplazar las reglas actuales con las del archivo
722
+ this.globalWAFStats.xHeaderRules.clear();
723
+ for (const [ruleId, rule] of rulesData) {
724
+ this.globalWAFStats.xHeaderRules.set(ruleId, rule);
725
+ }
726
+
727
+ socket.write(`\n✅ Reglas cargadas exitosamente desde: ${filePath}\n`);
728
+ socket.write(`Total de reglas cargadas: ${rulesData.length}\n\n> `);
729
+ } else {
730
+ socket.write('\nCarga de reglas cancelada.\n\n> ');
731
+ }
732
+
733
+ this.currentProcess.delete(sessionId);
734
+ }
735
+ }
736
+
737
+ module.exports = WAFModule;