jerkjs 2.0.0

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 (177) hide show
  1. package/LICENSE +200 -0
  2. package/README.md +171 -0
  3. package/doc/EXTENSION_MANUAL.md +958 -0
  4. package/doc/FIREWALL_MANUAL.md +419 -0
  5. package/doc/HOOKS_REFERENCE_IMPROVED.md +599 -0
  6. package/doc/MANUAL_API_SDK.md +539 -0
  7. package/doc/MANUAL_MVC.md +397 -0
  8. package/doc/MARIADB_TOKENS_IMPLEMENTATION.md +113 -0
  9. package/doc/MIDDLEWARE_MANUAL.md +521 -0
  10. package/doc/OAUTH2_GOOGLE_MANUAL.md +408 -0
  11. package/doc/frontend-and-sessions.md +356 -0
  12. package/examples/advanced/controllers/productController.js +64 -0
  13. package/examples/advanced/controllers/userController.js +85 -0
  14. package/examples/advanced/routes.json +51 -0
  15. package/examples/advanced_example.js +93 -0
  16. package/examples/basic/controllers/userController.js +85 -0
  17. package/examples/basic_example.js +72 -0
  18. package/examples/frontend/README.md +71 -0
  19. package/examples/frontend/app.js +71 -0
  20. package/examples/frontend/controllers/apiController.js +39 -0
  21. package/examples/frontend/controllers/authController.js +220 -0
  22. package/examples/frontend/controllers/formController.js +47 -0
  23. package/examples/frontend/controllers/messageController.js +96 -0
  24. package/examples/frontend/controllers/pageController.js +178 -0
  25. package/examples/frontend/controllers/staticController.js +167 -0
  26. package/examples/frontend/routes.json +90 -0
  27. package/examples/mvc_example/app.js +138 -0
  28. package/examples/mvc_example/views/home/index.html +26 -0
  29. package/examples/mvc_example/views/home/simple.html +3 -0
  30. package/examples/mvc_example/views/layout.html +23 -0
  31. package/examples/mvc_example/views/test.html +3 -0
  32. package/examples/mvc_example/views/user/invalid.html +6 -0
  33. package/examples/mvc_example/views/user/list.html +36 -0
  34. package/examples/mvc_example/views/user/notfound.html +6 -0
  35. package/examples/mvc_example/views/user/profile.html +11 -0
  36. package/examples/mvc_routes_example/app.js +34 -0
  37. package/examples/mvc_routes_example/controllers/mainController.js +27 -0
  38. package/examples/mvc_routes_example/controllers/productController.js +47 -0
  39. package/examples/mvc_routes_example/controllers/userController.js +76 -0
  40. package/examples/mvc_routes_example/routes.json +30 -0
  41. package/examples/mvc_routes_example/views/layout.html +31 -0
  42. package/examples/mvc_routes_example/views/main/index.html +11 -0
  43. package/examples/mvc_routes_example/views/product/catalog.html +24 -0
  44. package/examples/mvc_routes_example/views/user/invalid.html +6 -0
  45. package/examples/mvc_routes_example/views/user/list.html +40 -0
  46. package/examples/mvc_routes_example/views/user/notfound.html +6 -0
  47. package/examples/mvc_routes_example/views/user/profile.html +18 -0
  48. package/examples/public/README.md +92 -0
  49. package/examples/public/app.js +72 -0
  50. package/examples/public/controllers/healthController.js +20 -0
  51. package/examples/public/controllers/mainController.js +22 -0
  52. package/examples/public/controllers/userController.js +139 -0
  53. package/examples/public/routes.json +51 -0
  54. package/examples/v2/README.md +72 -0
  55. package/examples/v2/app.js +74 -0
  56. package/examples/v2/app_fixed.js +74 -0
  57. package/examples/v2/controllers/authController.js +64 -0
  58. package/examples/v2/controllers/mainController.js +24 -0
  59. package/examples/v2/controllers/protectedController.js +12 -0
  60. package/examples/v2/controllers/userController.js +16 -0
  61. package/examples/v2/package.json +27 -0
  62. package/examples/v2/routes.json +30 -0
  63. package/examples/v2/test_api.sh +47 -0
  64. package/examples/v2/tokens_example.sqlite +0 -0
  65. package/examples/v2.1_firewall_demo/README.md +113 -0
  66. package/examples/v2.1_firewall_demo/app.js +182 -0
  67. package/examples/v2.1_firewall_demo/package.json +27 -0
  68. package/examples/v2.1_hooks_demo/README.md +85 -0
  69. package/examples/v2.1_hooks_demo/app.js +101 -0
  70. package/examples/v2.1_hooks_demo/controllers/hooksController.js +29 -0
  71. package/examples/v2.1_hooks_demo/controllers/mainController.js +18 -0
  72. package/examples/v2.1_hooks_demo/package.json +27 -0
  73. package/examples/v2.1_hooks_demo/routes.json +16 -0
  74. package/examples/v2.1_openapi_demo/README.md +82 -0
  75. package/examples/v2.1_openapi_demo/app.js +296 -0
  76. package/examples/v2.1_openapi_demo/package.json +26 -0
  77. package/examples/v2_cors/README.md +82 -0
  78. package/examples/v2_cors/app.js +108 -0
  79. package/examples/v2_cors/package.json +23 -0
  80. package/examples/v2_json_auth/README.md +83 -0
  81. package/examples/v2_json_auth/app.js +72 -0
  82. package/examples/v2_json_auth/controllers/authController.js +67 -0
  83. package/examples/v2_json_auth/controllers/mainController.js +16 -0
  84. package/examples/v2_json_auth/controllers/protectedController.js +12 -0
  85. package/examples/v2_json_auth/controllers/tokenController.js +28 -0
  86. package/examples/v2_json_auth/controllers/userController.js +15 -0
  87. package/examples/v2_json_auth/package.json +26 -0
  88. package/examples/v2_json_auth/routes.json +37 -0
  89. package/examples/v2_json_auth/tokens.json +20 -0
  90. package/examples/v2_mariadb_auth/README.md +94 -0
  91. package/examples/v2_mariadb_auth/app.js +81 -0
  92. package/examples/v2_mariadb_auth/controllers/authController.js +95 -0
  93. package/examples/v2_mariadb_auth/controllers/mainController.js +31 -0
  94. package/examples/v2_mariadb_auth/controllers/protectedController.js +12 -0
  95. package/examples/v2_mariadb_auth/controllers/userController.js +17 -0
  96. package/examples/v2_mariadb_auth/package.json +27 -0
  97. package/examples/v2_mariadb_auth/routes.json +37 -0
  98. package/examples/v2_no_auth/README.md +75 -0
  99. package/examples/v2_no_auth/app.js +72 -0
  100. package/examples/v2_no_auth/controllers/healthController.js +14 -0
  101. package/examples/v2_no_auth/controllers/mainController.js +19 -0
  102. package/examples/v2_no_auth/controllers/productController.js +31 -0
  103. package/examples/v2_no_auth/controllers/publicController.js +16 -0
  104. package/examples/v2_no_auth/package.json +22 -0
  105. package/examples/v2_no_auth/routes.json +37 -0
  106. package/examples/v2_oauth/README.md +70 -0
  107. package/examples/v2_oauth/app.js +90 -0
  108. package/examples/v2_oauth/controllers/mainController.js +45 -0
  109. package/examples/v2_oauth/controllers/oauthController.js +247 -0
  110. package/examples/v2_oauth/controllers/protectedController.js +13 -0
  111. package/examples/v2_oauth/controllers/userController.js +17 -0
  112. package/examples/v2_oauth/package.json +26 -0
  113. package/examples/v2_oauth/routes.json +44 -0
  114. package/examples/v2_openapi/README.md +77 -0
  115. package/examples/v2_openapi/app.js +222 -0
  116. package/examples/v2_openapi/controllers/authController.js +52 -0
  117. package/examples/v2_openapi/controllers/mainController.js +26 -0
  118. package/examples/v2_openapi/controllers/productController.js +17 -0
  119. package/examples/v2_openapi/controllers/userController.js +27 -0
  120. package/examples/v2_openapi/package.json +26 -0
  121. package/examples/v2_openapi/routes.json +37 -0
  122. package/generate_token.js +10 -0
  123. package/index.js +85 -0
  124. package/jerk.jpg +0 -0
  125. package/lib/core/handler.js +86 -0
  126. package/lib/core/hooks.js +224 -0
  127. package/lib/core/router.js +204 -0
  128. package/lib/core/securityEnhancedServer.js +752 -0
  129. package/lib/core/server.js +369 -0
  130. package/lib/loader/controllerLoader.js +175 -0
  131. package/lib/loader/routeLoader.js +341 -0
  132. package/lib/middleware/auditLogger.js +208 -0
  133. package/lib/middleware/authenticator.js +565 -0
  134. package/lib/middleware/compressor.js +218 -0
  135. package/lib/middleware/cors.js +135 -0
  136. package/lib/middleware/firewall.js +443 -0
  137. package/lib/middleware/rateLimiter.js +210 -0
  138. package/lib/middleware/session.js +301 -0
  139. package/lib/middleware/validator.js +193 -0
  140. package/lib/mvc/controllerBase.js +207 -0
  141. package/lib/mvc/viewEngine.js +752 -0
  142. package/lib/utils/configParser.js +223 -0
  143. package/lib/utils/logger.js +145 -0
  144. package/lib/utils/mariadbTokenAdapter.js +226 -0
  145. package/lib/utils/openapiGenerator.js +140 -0
  146. package/lib/utils/sqliteTokenAdapter.js +224 -0
  147. package/lib/utils/tokenManager.js +254 -0
  148. package/package.json +47 -0
  149. package/v2examplle/v2_json_auth/README.md +83 -0
  150. package/v2examplle/v2_json_auth/app.js +72 -0
  151. package/v2examplle/v2_json_auth/controllers/authController.js +67 -0
  152. package/v2examplle/v2_json_auth/controllers/mainController.js +16 -0
  153. package/v2examplle/v2_json_auth/controllers/protectedController.js +12 -0
  154. package/v2examplle/v2_json_auth/controllers/tokenController.js +28 -0
  155. package/v2examplle/v2_json_auth/controllers/userController.js +15 -0
  156. package/v2examplle/v2_json_auth/package.json +26 -0
  157. package/v2examplle/v2_json_auth/routes.json +37 -0
  158. package/v2examplle/v2_json_auth/tokens.json +20 -0
  159. package/v2examplle/v2_mariadb_auth/README.md +94 -0
  160. package/v2examplle/v2_mariadb_auth/app.js +81 -0
  161. package/v2examplle/v2_mariadb_auth/controllers/authController.js +95 -0
  162. package/v2examplle/v2_mariadb_auth/controllers/mainController.js +31 -0
  163. package/v2examplle/v2_mariadb_auth/controllers/protectedController.js +12 -0
  164. package/v2examplle/v2_mariadb_auth/controllers/userController.js +17 -0
  165. package/v2examplle/v2_mariadb_auth/package.json +27 -0
  166. package/v2examplle/v2_mariadb_auth/routes.json +37 -0
  167. package/v2examplle/v2_sqlite_auth/README.md +72 -0
  168. package/v2examplle/v2_sqlite_auth/app.js +74 -0
  169. package/v2examplle/v2_sqlite_auth/app_fixed.js +74 -0
  170. package/v2examplle/v2_sqlite_auth/controllers/authController.js +64 -0
  171. package/v2examplle/v2_sqlite_auth/controllers/mainController.js +24 -0
  172. package/v2examplle/v2_sqlite_auth/controllers/protectedController.js +12 -0
  173. package/v2examplle/v2_sqlite_auth/controllers/userController.js +16 -0
  174. package/v2examplle/v2_sqlite_auth/package.json +27 -0
  175. package/v2examplle/v2_sqlite_auth/routes.json +30 -0
  176. package/v2examplle/v2_sqlite_auth/test_api.sh +47 -0
  177. package/v2examplle/v2_sqlite_auth/tokens_example.sqlite +0 -0
@@ -0,0 +1,752 @@
1
+ /**
2
+ * Implementación completa del sistema de seguridad avanzada (WAF)
3
+ * usando el sistema de hooks y filters para extensibilidad
4
+ * Web Application Firewall con capacidades de detección y prevención de ataques
5
+ */
6
+
7
+ const APIServer = require('../core/server');
8
+ const Authenticator = require('../middleware/authenticator');
9
+ const RateLimiter = require('../middleware/rateLimiter');
10
+ const { Logger } = require('../utils/logger');
11
+ const HookSystem = require('./hooks');
12
+
13
+ class SecurityEnhancedServer {
14
+ constructor(options = {}) {
15
+ this.server = new APIServer(options);
16
+ this.logger = new Logger({ level: 'info' });
17
+ this.authenticator = new Authenticator({ logger: this.logger });
18
+ this.rateLimiter = new RateLimiter(options.rateLimiter || {});
19
+ this.hooks = new HookSystem();
20
+
21
+ // Inicializar componentes de seguridad
22
+ this.attackDetector = new AttackDetector({ logger: this.logger });
23
+ this.clientFingerprinter = new ClientFingerprinter({ logger: this.logger });
24
+ this.firewall = new Firewall({
25
+ logger: this.logger,
26
+ maxAttempts: options.maxAttempts || 5,
27
+ blockDuration: options.blockDuration || 900000, // 15 minutos
28
+ whitelist: options.whitelist || [],
29
+ blacklist: options.blacklist || [],
30
+ rules: options.rules || []
31
+ });
32
+
33
+ // Registrar hooks y filtros de seguridad
34
+ this.registerSecurityHooks();
35
+ }
36
+
37
+ /**
38
+ * Registra los hooks y filtros de seguridad
39
+ */
40
+ registerSecurityHooks() {
41
+ // Hook para detectar posibles ataques antes de procesar la solicitud
42
+ this.hooks.addAction('before_request_processing', async (req, res) => {
43
+ // Usar el detector de ataque avanzado
44
+ const attackResult = this.attackDetector.detect(req);
45
+
46
+ if (attackResult) {
47
+ this.logger.warn(`Patrón de ataque detectado: ${attackResult} desde IP: ${this.getClientIP(req)}`);
48
+
49
+ // Disparar hook de evento de seguridad
50
+ this.hooks.doAction('security_attack_detected', attackResult, req, res);
51
+
52
+ // Bloquear IP
53
+ this.firewall.blockIP(this.getClientIP(req), attackResult);
54
+
55
+ res.writeHead(403, { 'Content-Type': 'application/json' });
56
+ res.end(JSON.stringify({ error: 'Solicitud bloqueada por seguridad', reason: attackResult }));
57
+ return false;
58
+ }
59
+
60
+ return true;
61
+ });
62
+
63
+ // Filtro para mejorar la huella digital del cliente
64
+ this.hooks.addFilter('client_fingerprint', (fingerprint, req) => {
65
+ const enhancedFingerprint = {
66
+ ...fingerprint,
67
+ userAgentHash: this.hashString(req.headers['user-agent'] || ''),
68
+ acceptHeaders: req.headers['accept'] || '',
69
+ language: req.headers['accept-language'] || '',
70
+ encoding: req.headers['accept-encoding'] || '',
71
+ contentType: req.headers['content-type'] || '',
72
+ forwardedFor: req.headers['x-forwarded-for'] || '',
73
+ realIP: req.headers['x-real-ip'] || '',
74
+ // Agregar información adicional para WAF
75
+ acceptCharset: req.headers['accept-charset'] || '',
76
+ connection: req.headers['connection'] || '',
77
+ pragma: req.headers['pragma'] || '',
78
+ cacheControl: req.headers['cache-control'] || '',
79
+ userAgentTokens: this.tokenizeUserAgent(req.headers['user-agent'] || ''),
80
+ suspiciousHeaders: this.detectSuspiciousHeaders(req.headers)
81
+ };
82
+
83
+ // Permitir que otros módulos modifiquen la huella digital
84
+ return this.hooks.applyFilters('enhanced_client_fingerprint', enhancedFingerprint, req);
85
+ });
86
+
87
+ // Hook para aplicar firewall a la solicitud
88
+ this.hooks.addAction('request_validation', (req, res) => {
89
+ const clientIP = this.getClientIP(req);
90
+
91
+ // Verificar si la IP está bloqueada
92
+ const blockInfo = this.firewall.isBlocked(clientIP);
93
+ if (blockInfo.blocked) {
94
+ this.logger.warn(`Solicitud bloqueada desde IP: ${clientIP}, razón: ${blockInfo.reason}`);
95
+
96
+ // Disparar hook de evento de seguridad
97
+ this.hooks.doAction('security_ip_blocked', clientIP, blockInfo, req, res);
98
+
99
+ res.writeHead(403, { 'Content-Type': 'application/json' });
100
+ res.end(JSON.stringify({
101
+ error: 'IP bloqueada por violaciones de seguridad',
102
+ reason: blockInfo.reason,
103
+ blockedUntil: blockInfo.blockedUntil
104
+ }));
105
+ return false;
106
+ }
107
+
108
+ // Verificar reglas personalizadas de firewall
109
+ const ruleMatch = this.firewall.checkRules(req);
110
+ if (ruleMatch) {
111
+ this.logger.warn(`Regla de firewall activada: ${ruleMatch.rule} para IP: ${clientIP}, razón: ${ruleMatch.reason}`);
112
+
113
+ if (ruleMatch.action === 'block') {
114
+ res.writeHead(403, { 'Content-Type': 'application/json' });
115
+ res.end(JSON.stringify({
116
+ error: 'Solicitud bloqueada por regla de firewall',
117
+ reason: ruleMatch.reason,
118
+ rule: ruleMatch.rule
119
+ }));
120
+ return false;
121
+ } else if (ruleMatch.action === 'monitor') {
122
+ // Registrar pero permitir continuar
123
+ this.logger.info(`Solicitud monitoreada por regla: ${ruleMatch.rule}, IP: ${clientIP}`);
124
+ }
125
+ }
126
+
127
+ return true;
128
+ });
129
+
130
+ // Hook para aplicar rate limiting basado en huella digital
131
+ this.hooks.addAction('apply_rate_limiting', (req, res, next) => {
132
+ // Obtener huella digital del cliente
133
+ const fingerprint = this.clientFingerprinter.generate(req);
134
+ const enhancedFingerprint = this.hooks.applyFilters('client_fingerprint', fingerprint, req);
135
+ const clientId = this.generateClientId(enhancedFingerprint);
136
+
137
+ // Aplicar rate limiting basado en huella digital
138
+ this.applyRateLimiting(clientId, req, res, next);
139
+ });
140
+
141
+ // Hook para auditoría de seguridad
142
+ this.hooks.addAction('security_audit', (req, res, action, details) => {
143
+ const auditLog = {
144
+ timestamp: new Date().toISOString(),
145
+ clientIP: this.getClientIP(req),
146
+ userAgent: req.headers['user-agent'],
147
+ method: req.method,
148
+ url: req.url,
149
+ action: action,
150
+ details: details,
151
+ fingerprint: this.clientFingerprinter.generate(req)
152
+ };
153
+
154
+ // Disparar hook para procesamiento de logs de auditoría
155
+ this.hooks.doAction('security_log_recorded', auditLog);
156
+ });
157
+ }
158
+
159
+ /**
160
+ * Aplica rate limiting basado en huella digital
161
+ */
162
+ applyRateLimiting(clientId, req, res, next) {
163
+ // Permitir que otros módulos modifiquen el comportamiento de rate limiting
164
+ const rateLimitResult = this.hooks.applyFilters('modify_rate_limit_behavior', {
165
+ clientId,
166
+ currentCount: this.getCurrentRequestCount(clientId),
167
+ maxRequests: this.rateLimiter.maxRequests,
168
+ windowMs: this.rateLimiter.windowMs
169
+ }, req);
170
+
171
+ if (rateLimitResult.currentCount >= rateLimitResult.maxRequests) {
172
+ // Disparar hook de evento de seguridad
173
+ this.hooks.doAction('rate_limit_exceeded', clientId, req, res);
174
+
175
+ const timeLeft = this.getTimeUntilReset(clientId);
176
+ if (!res.headersSent) {
177
+ res.writeHead(429, { 'Content-Type': 'application/json' });
178
+ res.end(JSON.stringify({
179
+ error: 'Límite de solicitudes excedido',
180
+ retryAfter: Math.floor(timeLeft / 1000) + ' segundos'
181
+ }));
182
+ }
183
+ return;
184
+ }
185
+
186
+ // Incrementar conteo y continuar
187
+ this.incrementRequestCount(clientId);
188
+ if (next) {
189
+ next();
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Obtiene la IP del cliente
195
+ */
196
+ getClientIP(req) {
197
+ return req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
198
+ req.headers['x-real-ip'] ||
199
+ req.connection.remoteAddress ||
200
+ req.socket.remoteAddress ||
201
+ (req.connection?.socket ? req.connection.socket.remoteAddress : 'unknown');
202
+ }
203
+
204
+ /**
205
+ * Tokeniza el user agent para análisis
206
+ */
207
+ tokenizeUserAgent(userAgent) {
208
+ // Dividir el user agent en tokens para análisis
209
+ return userAgent
210
+ .toLowerCase()
211
+ .replace(/[()]/g, ' ')
212
+ .split(/[\s;/_\-,.]+/)
213
+ .filter(token => token.length > 0);
214
+ }
215
+
216
+ /**
217
+ * Detecta headers sospechosos
218
+ */
219
+ detectSuspiciousHeaders(headers) {
220
+ const suspiciousHeaders = [
221
+ 'x-forwarded-for',
222
+ 'x-real-ip',
223
+ 'x-originating-ip',
224
+ 'x-remote-ip',
225
+ 'x-remote-addr',
226
+ 'x-proxy-user-ip',
227
+ 'cf-connecting-ip',
228
+ 'true-client-ip',
229
+ 'x-cluster-client-ip',
230
+ 'x-forwarded',
231
+ 'x-forwarded-host',
232
+ 'x-forwarded-server',
233
+ 'x-original-forwarded-for',
234
+ 'x-original-host',
235
+ 'x-proxy-id'
236
+ ];
237
+
238
+ const detected = [];
239
+ for (const [header, value] of Object.entries(headers)) {
240
+ if (suspiciousHeaders.includes(header.toLowerCase())) {
241
+ detected.push({ header, value });
242
+ }
243
+ }
244
+
245
+ return detected;
246
+ }
247
+
248
+ /**
249
+ * Detecta patrones de ataque en la solicitud
250
+ */
251
+ detectAttackPatterns(req) {
252
+ const path = req.url;
253
+ const body = req.body || '';
254
+ const headers = req.headers;
255
+
256
+ // Patrones de SQL Injection
257
+ const sqlPatterns = [
258
+ /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION|SCRIPT|TRUNCATE|DECLARE|MERGE|GRANT|REVOKE|CALL|LOAD|COPY|BULK|INTO|OUTFILE|DUMPFILE)\b)/gi,
259
+ /('|--|#|\/\*|\*\/|;|xp_|sp_|sysobjects|syscolumns|information_schema)/gi
260
+ ];
261
+
262
+ // Patrones de XSS
263
+ const xssPatterns = [
264
+ /(<script|javascript:|vbscript:|onload|onerror|onmouseover|onclick|onfocus|onblur|onchange|onselect|onsubmit|onkeydown|onkeypress|onkeyup|onabort|onafterprint|onbeforeprint|onbeforeunload|onblur|oncanplay|oncanplaythrough|onchange|onclick|oncontextmenu|oncopy|oncut|ondblclick|ondrag|ondragend|ondragenter|ondragleave|ondragover|ondragstart|ondrop|ondurationchange|onended|onerror|onfocus|onfocusin|onfocusout|onfullscreenchange|onfullscreenerror|onhashchange|oninput|oninvalid|onkeydown|onkeypress|onkeyup|onload|onloadeddata|onloadedmetadata|onloadstart|onmessage|onmousedown|onmouseenter|onmouseleave|onmousemove|onmouseout|onmouseover|onmouseup|onmousewheel|onoffline|ononline|onpagehide|onpageshow|onpaste|onpause|onplay|onplaying|onpopstate|onprogress|onratechange|onreset|onresize|onscroll|onsearch|onseeked|onseeking|onselect|onshow|onstalled|onstorage|onsubmit|onsuspend|ontimeupdate|ontoggle|onunload|onvolumechange|onwaiting|onwheel)/gi,
265
+ /(src|href|background|action)=["']?\s*(javascript:|data:|vbscript:)/gi,
266
+ /<iframe/gi,
267
+ /<img[^>]*src[\\s]*=[\\s]*["'][\\s]*(javascript:|data:)/gi,
268
+ /eval\s*\(/gi,
269
+ /expression\s*\(/gi
270
+ ];
271
+
272
+ // Patrones de Path Traversal
273
+ const pathTraversal = /(\.\.\/|\.\.\\|%2e%2e%2f|%2e%2e%5c|%c0%ae%c0%ae%c0%af|%uff0e%uff0e%u2215|%uff0e%uff0e%u2216)/gi;
274
+
275
+ // Patrones de Command Injection
276
+ const cmdInjection = [
277
+ /(;|\||`|>|<|&)/g,
278
+ /\b(cat|ls|dir|pwd|whoami|uname|ps|kill|rm|mv|cp|touch|mkdir|rmdir|chmod|chown|wget|curl|nc|netcat|ping|traceroute|nslookup|dig|ifconfig|ip|route|netstat|crontab|passwd|shadow|sudo|su)\b/gi
279
+ ];
280
+
281
+ // Verificar patrones en URL
282
+ for (const pattern of sqlPatterns) {
283
+ if (pattern.test(path)) {
284
+ return { type: 'sql_injection', pattern: pattern.toString() };
285
+ }
286
+ }
287
+
288
+ for (const pattern of xssPatterns) {
289
+ if (pattern.test(path)) {
290
+ return { type: 'xss_attack', pattern: pattern.toString() };
291
+ }
292
+ }
293
+
294
+ if (pathTraversal.test(path)) {
295
+ return { type: 'path_traversal', pattern: pathTraversal.toString() };
296
+ }
297
+
298
+ for (const pattern of cmdInjection) {
299
+ if (pattern.test(path)) {
300
+ return { type: 'command_injection', pattern: pattern.toString() };
301
+ }
302
+ }
303
+
304
+ // Verificar patrones en body si es string
305
+ if (typeof body === 'string') {
306
+ for (const pattern of sqlPatterns) {
307
+ if (pattern.test(body)) {
308
+ return { type: 'sql_injection', pattern: pattern.toString() };
309
+ }
310
+ }
311
+
312
+ for (const pattern of xssPatterns) {
313
+ if (pattern.test(body)) {
314
+ return { type: 'xss_attack', pattern: pattern.toString() };
315
+ }
316
+ }
317
+
318
+ for (const pattern of cmdInjection) {
319
+ if (pattern.test(body)) {
320
+ return { type: 'command_injection', pattern: pattern.toString() };
321
+ }
322
+ }
323
+ }
324
+
325
+ // Verificar headers sospechosos
326
+ const suspiciousHeaders = [
327
+ 'x-forwarded-for',
328
+ 'x-real-ip',
329
+ 'x-originating-ip',
330
+ 'x-remote-ip',
331
+ 'x-remote-addr',
332
+ 'x-proxy-user-ip',
333
+ 'cf-connecting-ip'
334
+ ];
335
+
336
+ let suspiciousHeaderCount = 0;
337
+ for (const header of suspiciousHeaders) {
338
+ if (headers[header]) {
339
+ suspiciousHeaderCount++;
340
+ }
341
+ }
342
+
343
+ if (suspiciousHeaderCount > 3) {
344
+ return { type: 'header_spoofing', count: suspiciousHeaderCount };
345
+ }
346
+
347
+ return null;
348
+ }
349
+
350
+ /**
351
+ * Genera un ID de cliente basado en huella digital
352
+ */
353
+ generateClientId(fingerprint) {
354
+ // Generar un hash único basado en la huella digital
355
+ return this.hashString(JSON.stringify(fingerprint));
356
+ }
357
+
358
+ /**
359
+ * Hash simple para cadenas
360
+ */
361
+ hashString(str) {
362
+ let hash = 0;
363
+ for (let i = 0; i < str.length; i++) {
364
+ const char = str.charCodeAt(i);
365
+ hash = ((hash << 5) - hash) + char;
366
+ hash = hash & hash; // Convertir a 32-bit integer
367
+ }
368
+ return hash.toString();
369
+ }
370
+
371
+ /**
372
+ * Obtiene el conteo actual de solicitudes
373
+ */
374
+ getCurrentRequestCount(clientId) {
375
+ if (!this.requestCounts) this.requestCounts = new Map();
376
+ const entry = this.requestCounts.get(clientId);
377
+ if (!entry) return 0;
378
+
379
+ // Verificar si ha expirado
380
+ if (Date.now() - entry.startTime > this.rateLimiter.windowMs) {
381
+ this.requestCounts.delete(clientId);
382
+ return 0;
383
+ }
384
+
385
+ return entry.count;
386
+ }
387
+
388
+ /**
389
+ * Incrementa el conteo de solicitudes
390
+ */
391
+ incrementRequestCount(clientId) {
392
+ if (!this.requestCounts) this.requestCounts = new Map();
393
+
394
+ const entry = this.requestCounts.get(clientId);
395
+ const now = Date.now();
396
+
397
+ if (!entry) {
398
+ this.requestCounts.set(clientId, { count: 1, startTime: now });
399
+ } else {
400
+ if (now - entry.startTime > this.rateLimiter.windowMs) {
401
+ this.requestCounts.set(clientId, { count: 1, startTime: now });
402
+ } else {
403
+ entry.count++;
404
+ }
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Obtiene el tiempo restante hasta el reset del rate limit
410
+ */
411
+ getTimeUntilReset(clientId) {
412
+ if (!this.requestCounts) this.requestCounts = new Map();
413
+ const entry = this.requestCounts.get(clientId);
414
+ if (!entry) return this.rateLimiter.windowMs;
415
+
416
+ const elapsed = Date.now() - entry.startTime;
417
+ return Math.max(0, this.rateLimiter.windowMs - elapsed);
418
+ }
419
+
420
+ /**
421
+ * Middleware de seguridad avanzada
422
+ */
423
+ securityMiddleware() {
424
+ return async (req, res, next) => {
425
+ try {
426
+ // Asegurarse de que req tenga las propiedades necesarias
427
+ if (!req || typeof req !== 'object') {
428
+ this.logger.error('Solicitud inválida recibida en middleware de seguridad');
429
+ res.writeHead(500, { 'Content-Type': 'application/json' });
430
+ res.end(JSON.stringify({ error: 'Solicitud inválida' }));
431
+ return;
432
+ }
433
+
434
+ // Asegurarse de que req.headers exista
435
+ if (!req.headers) {
436
+ req.headers = {};
437
+ }
438
+
439
+ // Disparar hook antes de procesar la solicitud
440
+ this.hooks.doAction('pre_request_processing', req, res);
441
+
442
+ // Aplicar hooks de seguridad en orden
443
+ const continueProcessing = await this.hooks.applyFilters('before_request_processing', true, req, res);
444
+
445
+ if (!continueProcessing) {
446
+ return; // La solicitud fue bloqueada por seguridad
447
+ }
448
+
449
+ // Generar huella digital del cliente
450
+ const fingerprint = this.clientFingerprinter.generate(req);
451
+ const enhancedFingerprint = this.hooks.applyFilters('client_fingerprint', fingerprint, req);
452
+
453
+ // Validar con firewall - el firewall modifica directamente la respuesta si es necesario
454
+ this.hooks.doAction('request_validation', req, res);
455
+ // Si headers ya fueron enviados, significa que el firewall bloqueó la solicitud
456
+ if (res.headersSent) {
457
+ return; // La solicitud fue bloqueada por el firewall
458
+ }
459
+
460
+ // Aplicar rate limiting - el rate limiter llama a next() internamente si no hay límite excedido
461
+ this.hooks.doAction('apply_rate_limiting', req, res, next);
462
+
463
+ // Disparar hook después de procesar la solicitud (pero antes de next)
464
+ this.hooks.doAction('post_request_processing', req, res);
465
+ } catch (error) {
466
+ this.logger.error('Error en middleware de seguridad:', error.message);
467
+ if (!res.headersSent) {
468
+ res.writeHead(500, { 'Content-Type': 'application/json' });
469
+ res.end(JSON.stringify({ error: 'Error interno del servidor' }));
470
+ }
471
+ }
472
+ };
473
+ }
474
+
475
+ /**
476
+ * Inicia el servidor con seguridad avanzada
477
+ */
478
+ start() {
479
+ // Disparar hook antes de iniciar el servidor
480
+ this.hooks.doAction('pre_server_start', this.server);
481
+
482
+ // Aplicar middleware de seguridad antes de otros middlewares
483
+ this.server.use(this.securityMiddleware());
484
+
485
+ // Iniciar el servidor
486
+ this.server.start();
487
+
488
+ // Disparar hook después de iniciar el servidor
489
+ this.hooks.doAction('post_server_start', this.server);
490
+
491
+ this.logger.info('Servidor iniciado con funcionalidades de seguridad avanzada');
492
+ }
493
+
494
+ /**
495
+ * Registra una regla personalizada de firewall
496
+ * @param {string} name - Nombre de la regla
497
+ * @param {Function} condition - Función de condición que evalúa la solicitud
498
+ * @param {string} action - Acción a tomar ('block', 'monitor', etc.)
499
+ * @param {string} reason - Razón para el bloqueo o monitoreo
500
+ */
501
+ addFirewallRule(name, condition, action = 'block', reason = 'Violación de regla personalizada') {
502
+ this.firewall.addRule(name, condition, action, reason);
503
+ }
504
+
505
+ /**
506
+ * Registra un detector de ataques personalizado
507
+ * @param {string} name - Nombre del detector
508
+ * @param {Function} detector - Función que detecta patrones de ataque
509
+ */
510
+ addAttackDetector(name, detector) {
511
+ this.attackDetector.addCustomDetector(name, detector);
512
+ }
513
+ }
514
+
515
+ // Clases auxiliares para las funcionalidades de seguridad
516
+
517
+ class AttackDetector {
518
+ constructor(options = {}) {
519
+ this.logger = options.logger || console;
520
+ this.customDetectors = new Map(); // Map<name, detectorFunction>
521
+
522
+ // Patrones comunes de ataque
523
+ this.sqlPatterns = [
524
+ /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION|SCRIPT)\b)/gi,
525
+ /('|--|#|\/\*|\*\/|;)/g
526
+ ];
527
+
528
+ this.xssPatterns = [
529
+ /(<script|javascript:|vbscript:|onload|onerror|onmouseover|onclick|onfocus|onblur)/gi,
530
+ /(src|href)=["']javascript:/gi,
531
+ /<iframe/gi,
532
+ /<img[^>]*src[\\s]*=[\\s]*["'][\\s]*(javascript:|data:)/gi
533
+ ];
534
+
535
+ this.pathTraversal = /\.\.\//g;
536
+ }
537
+
538
+ /**
539
+ * Detecta patrones de ataque en la solicitud
540
+ * @param {Object} req - Objeto de solicitud
541
+ * @returns {string|null} - Tipo de ataque detectado o null si ninguno
542
+ */
543
+ detect(req) {
544
+ // Usar el método de detección de patrones avanzados
545
+ const attackResult = this.detectAttackPatterns(req);
546
+
547
+ if (attackResult) {
548
+ return attackResult.type;
549
+ }
550
+
551
+ // Ejecutar detectores personalizados
552
+ for (const [name, detector] of this.customDetectors) {
553
+ const result = detector(req);
554
+ if (result) {
555
+ return result;
556
+ }
557
+ }
558
+
559
+ return null;
560
+ }
561
+
562
+ /**
563
+ * Agrega un detector de ataques personalizado
564
+ * @param {string} name - Nombre del detector
565
+ * @param {Function} detector - Función de detección
566
+ */
567
+ addCustomDetector(name, detector) {
568
+ this.customDetectors.set(name, detector);
569
+ this.logger.info(`Detector de ataque personalizado agregado: ${name}`);
570
+ }
571
+ }
572
+
573
+ class ClientFingerprinter {
574
+ constructor(options = {}) {
575
+ this.logger = options.logger || console;
576
+ }
577
+
578
+ /**
579
+ * Genera una huella digital única para el cliente
580
+ * @param {Object} req - Objeto de solicitud
581
+ * @returns {Object} - Huella digital del cliente
582
+ */
583
+ generate(req) {
584
+ return {
585
+ ip: req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
586
+ req.headers['x-real-ip'] ||
587
+ req.connection.remoteAddress ||
588
+ req.socket.remoteAddress ||
589
+ (req.connection?.socket ? req.connection.socket.remoteAddress : 'unknown'),
590
+ userAgent: req.headers['user-agent'] || '',
591
+ acceptLanguage: req.headers['accept-language'] || '',
592
+ acceptEncoding: req.headers['accept-encoding'] || '',
593
+ accept: req.headers['accept'] || '',
594
+ referer: req.headers['referer'] || '',
595
+ contentType: req.headers['content-type'] || '',
596
+ timestamp: Date.now()
597
+ };
598
+ }
599
+ }
600
+
601
+ class Firewall {
602
+ constructor(options = {}) {
603
+ this.logger = options.logger || console;
604
+ this.blockedIPs = new Map(); // Map<IP, { blockedUntil, reason, attempts }>
605
+ this.maxAttempts = options.maxAttempts || 5;
606
+ this.blockDuration = options.blockDuration || 900000; // 15 minutos
607
+ this.whitelist = options.whitelist || []; // IPs que no deben ser bloqueadas
608
+ this.blacklist = options.blacklist || []; // IPs que siempre deben ser bloqueadas
609
+ this.rules = options.rules || []; // Reglas personalizadas de firewall
610
+ }
611
+
612
+ /**
613
+ * Verifica si una IP está bloqueada
614
+ * @param {string} ip - IP a verificar
615
+ * @returns {Object} - Información del bloqueo
616
+ */
617
+ isBlocked(ip) {
618
+ // Verificar si está en la blacklist
619
+ if (this.blacklist.includes(ip)) {
620
+ return {
621
+ blocked: true,
622
+ reason: 'IP en lista negra',
623
+ permanent: true,
624
+ blockedUntil: null
625
+ };
626
+ }
627
+
628
+ // Verificar si está en la whitelist
629
+ if (this.whitelist.includes(ip)) {
630
+ return { blocked: false };
631
+ }
632
+
633
+ const blockInfo = this.blockedIPs.get(ip);
634
+ if (!blockInfo) {
635
+ return { blocked: false };
636
+ }
637
+
638
+ // Verificar si el bloqueo ha expirado
639
+ if (Date.now() > blockInfo.blockedUntil) {
640
+ this.blockedIPs.delete(ip); // Limpiar bloqueo expirado
641
+ return { blocked: false };
642
+ }
643
+
644
+ return {
645
+ blocked: true,
646
+ reason: blockInfo.reason,
647
+ blockedUntil: blockInfo.blockedUntil,
648
+ attempts: blockInfo.attempts
649
+ };
650
+ }
651
+
652
+ /**
653
+ * Bloquea una IP
654
+ * @param {string} ip - IP a bloquear
655
+ * @param {string} reason - Razón del bloqueo
656
+ */
657
+ blockIP(ip, reason) {
658
+ if (this.whitelist.includes(ip)) {
659
+ this.logger.info(`IP ${ip} está en la whitelist, no se bloqueará`);
660
+ return false;
661
+ }
662
+
663
+ const blockedUntil = Date.now() + this.blockDuration;
664
+ const currentInfo = this.blockedIPs.get(ip);
665
+ const attempts = currentInfo ? currentInfo.attempts + 1 : 1;
666
+
667
+ this.blockedIPs.set(ip, {
668
+ blockedUntil,
669
+ reason,
670
+ attempts
671
+ });
672
+
673
+ this.logger.warn(`IP ${ip} bloqueada por: ${reason}. Intentos: ${attempts}`);
674
+ return true;
675
+ }
676
+
677
+ /**
678
+ * Incrementa el contador de intentos fallidos para una IP
679
+ * @param {string} ip - IP a incrementar intentos
680
+ * @param {string} reason - Razón del intento fallido
681
+ */
682
+ incrementFailedAttempts(ip, reason) {
683
+ if (this.whitelist.includes(ip)) {
684
+ return;
685
+ }
686
+
687
+ const currentInfo = this.blockedIPs.get(ip);
688
+ const attempts = currentInfo ? currentInfo.attempts + 1 : 1;
689
+ const blockedUntil = currentInfo ? currentInfo.blockedUntil : Date.now() + this.blockDuration;
690
+
691
+ this.blockedIPs.set(ip, {
692
+ blockedUntil,
693
+ reason,
694
+ attempts
695
+ });
696
+
697
+ // Si se alcanza el límite de intentos, bloquear permanentemente
698
+ if (attempts >= this.maxAttempts) {
699
+ this.logger.warn(`IP ${ip} bloqueada permanentemente tras ${attempts} intentos fallidos`);
700
+ }
701
+ }
702
+
703
+ /**
704
+ * Agrega una regla personalizada de firewall
705
+ * @param {string} name - Nombre de la regla
706
+ * @param {Function} condition - Condición que evalúa la solicitud
707
+ * @param {string} action - Acción a tomar ('block', 'monitor', etc.)
708
+ * @param {string} reason - Razón para la acción
709
+ */
710
+ addRule(name, condition, action = 'block', reason = 'Violación de regla personalizada') {
711
+ this.rules.push({ name, condition, action, reason });
712
+ this.logger.info(`Regla de firewall agregada: ${name}`);
713
+ }
714
+
715
+ /**
716
+ * Verifica si una solicitud coincide con alguna regla de firewall
717
+ * @param {Object} req - Objeto de solicitud
718
+ * @returns {Object|null} - Regla que coincide o null si ninguna
719
+ */
720
+ checkRules(req) {
721
+ const clientIP = this.getClientIP(req);
722
+
723
+ // Verificar reglas personalizadas
724
+ for (const rule of this.rules) {
725
+ if (rule.condition(req, clientIP)) {
726
+ return {
727
+ matched: true,
728
+ rule: rule.name,
729
+ action: rule.action || 'block',
730
+ reason: rule.reason || 'Violación de regla de firewall'
731
+ };
732
+ }
733
+ }
734
+
735
+ return null;
736
+ }
737
+
738
+ /**
739
+ * Obtiene la IP del cliente
740
+ * @param {Object} req - Objeto de solicitud
741
+ * @returns {string} - IP del cliente
742
+ */
743
+ getClientIP(req) {
744
+ return req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
745
+ req.headers['x-real-ip'] ||
746
+ req.connection.remoteAddress ||
747
+ req.socket.remoteAddress ||
748
+ (req.connection?.socket ? req.connection.socket.remoteAddress : 'unknown');
749
+ }
750
+ }
751
+
752
+ module.exports = SecurityEnhancedServer;