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,341 @@
1
+ /**
2
+ * Carga de rutas desde archivos JSON para el framework API SDK
3
+ * Implementación del componente loader/routeLoader.js
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ class RouteLoader {
10
+ /**
11
+ * Constructor del cargador de rutas
12
+ */
13
+ constructor() {
14
+ this.loadedRoutes = [];
15
+ }
16
+
17
+ /**
18
+ * Método para cargar rutas desde un archivo JSON
19
+ * @param {Object} server - Instancia del servidor
20
+ * @param {string} filePath - Ruta al archivo JSON de rutas
21
+ * @returns {Promise<Array>} - Array de rutas cargadas
22
+ */
23
+ async loadRoutes(server, filePath) {
24
+ try {
25
+ // Disparar hook antes de cargar rutas
26
+ const hooks = require('../../index.js').hooks;
27
+ if (hooks) {
28
+ hooks.doAction('pre_route_load', filePath, server);
29
+ }
30
+
31
+ // Verificar si el archivo existe
32
+ if (!fs.existsSync(filePath)) {
33
+ throw new Error(`Archivo de rutas no encontrado: ${filePath}`);
34
+ }
35
+
36
+ // Leer y parsear el archivo JSON
37
+ const routeData = fs.readFileSync(filePath, 'utf8');
38
+ const routes = JSON.parse(routeData);
39
+
40
+ // Validar estructura del archivo de rutas
41
+ this.validateRoutesStructure(routes);
42
+
43
+ // Cargar cada ruta
44
+ for (const route of routes) {
45
+ await this.loadSingleRoute(server, route);
46
+ }
47
+
48
+ this.loadedRoutes = [...this.loadedRoutes, ...routes];
49
+
50
+ // Disparar hook después de cargar rutas
51
+ if (hooks) {
52
+ hooks.doAction('post_route_load', routes, server);
53
+ }
54
+
55
+ return routes;
56
+ } catch (error) {
57
+ throw new Error(`Error cargando rutas desde ${filePath}: ${error.message}`);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Método para validar la estructura del archivo de rutas
63
+ * @param {Array} routes - Array de rutas a validar
64
+ */
65
+ validateRoutesStructure(routes) {
66
+ if (!Array.isArray(routes)) {
67
+ throw new Error('El archivo de rutas debe contener un array de rutas');
68
+ }
69
+
70
+ for (let i = 0; i < routes.length; i++) {
71
+ const route = routes[i];
72
+
73
+ if (typeof route !== 'object' || route === null) {
74
+ throw new Error(`La ruta en la posición ${i} no es un objeto válido`);
75
+ }
76
+
77
+ if (!route.path) {
78
+ throw new Error(`La ruta en la posición ${i} no tiene propiedad 'path'`);
79
+ }
80
+
81
+ if (!route.method) {
82
+ throw new Error(`La ruta en la posición ${i} no tiene propiedad 'method'`);
83
+ }
84
+
85
+ if (!route.controller) {
86
+ throw new Error(`La ruta en la posición ${i} no tiene propiedad 'controller'`);
87
+ }
88
+
89
+ if (!route.handler) {
90
+ throw new Error(`La ruta en la posición ${i} no tiene propiedad 'handler'`);
91
+ }
92
+
93
+ // Validar que el content-type sea un string si está presente
94
+ if (route.contentType && typeof route.contentType !== 'string') {
95
+ throw new Error(`La ruta en la posición ${i} tiene un 'contentType' inválido, debe ser un string`);
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Método para cargar una sola ruta
102
+ * @param {Object} server - Instancia del servidor
103
+ * @param {Object} route - Objeto de ruta a cargar
104
+ */
105
+ async loadSingleRoute(server, route) {
106
+ // Obtener el controlador
107
+ const controllerPath = path.resolve(process.cwd(), route.controller);
108
+ let controllerModule;
109
+
110
+ try {
111
+ controllerModule = require(controllerPath);
112
+ } catch (error) {
113
+ throw new Error(`No se pudo cargar el controlador: ${route.controller}. Error: ${error.message}`);
114
+ }
115
+
116
+ // Obtener el handler del controlador
117
+ const handler = controllerModule[route.handler];
118
+ if (typeof handler !== 'function') {
119
+ throw new Error(`El handler '${route.handler}' no es una función en el controlador: ${route.controller}`);
120
+ }
121
+
122
+ // Crear un handler que establezca el content-type si está especificado
123
+ let finalHandler = handler;
124
+
125
+ if (route.contentType) {
126
+ finalHandler = async (req, res) => {
127
+ // Establecer el content-type antes de ejecutar el handler original
128
+ res.setHeader('Content-Type', route.contentType);
129
+
130
+ // Si el handler es asíncrono, esperarlo
131
+ if (handler.constructor.name === 'AsyncFunction') {
132
+ await handler(req, res);
133
+ } else {
134
+ handler(req, res);
135
+ }
136
+ };
137
+ }
138
+
139
+ // Aplicar autenticación si está especificada
140
+ if (route.auth && route.auth !== 'none') {
141
+ // Verificar si es autenticación de sesión
142
+ if (route.auth === 'session') {
143
+ // Verificar si el servidor tiene sessionManager
144
+ if (server.sessionManager) {
145
+ // Importar el middleware de autenticación de sesión
146
+ const { sessionAuth } = require('../middleware/session');
147
+ const authMiddleware = sessionAuth(server.sessionManager, route.authOptions || {});
148
+
149
+ // Crear un nuevo handler que ejecute la autenticación primero
150
+ const authenticatedHandler = async (req, res) => {
151
+ try {
152
+ // Ejecutar el middleware de autenticación y esperar a que se resuelva
153
+ await new Promise((resolve, reject) => {
154
+ const next = () => resolve();
155
+ const result = authMiddleware(req, res, next);
156
+
157
+ // Si authMiddleware devuelve una promesa, esperarla
158
+ if (result && typeof result.then === 'function') {
159
+ result.then(resolve).catch(reject);
160
+ }
161
+ });
162
+
163
+ // Si la autenticación fue exitosa (no se envió respuesta aún), ejecutar el handler original
164
+ if (!res.headersSent) {
165
+ // Si el handler es asíncrono, esperarlo también
166
+ if (finalHandler.constructor.name === 'AsyncFunction') {
167
+ await finalHandler(req, res);
168
+ } else {
169
+ finalHandler(req, res);
170
+ }
171
+ }
172
+ } catch (error) {
173
+ console.error('Error en el manejo de autenticación de sesión:', error);
174
+ if (!res.headersSent) {
175
+ res.writeHead(500, { 'Content-Type': 'application/json' });
176
+ res.end(JSON.stringify({ error: 'Error interno del servidor' }));
177
+ }
178
+ }
179
+ };
180
+
181
+ // Agregar la ruta con el handler autenticado
182
+ server.addRoute(route.method, route.path, authenticatedHandler);
183
+ } else {
184
+ // Si no hay sessionManager en el servidor, agregar la ruta normalmente
185
+ server.addRoute(route.method, route.path, finalHandler);
186
+ }
187
+ } else {
188
+ // Usar el authenticator del servidor si está disponible para otros tipos de autenticación
189
+ if (server.authenticator) {
190
+ const authMiddleware = server.authenticator.authenticate(route.auth, route.authOptions || {});
191
+
192
+ // Crear un nuevo handler que ejecute la autenticación primero
193
+ const authenticatedHandler = async (req, res) => {
194
+ try {
195
+ // Ejecutar el middleware de autenticación y esperar a que se resuelva
196
+ await new Promise((resolve, reject) => {
197
+ const next = () => resolve();
198
+ const result = authMiddleware(req, res, next);
199
+
200
+ // Si authMiddleware devuelve una promesa, esperarla
201
+ if (result && typeof result.then === 'function') {
202
+ result.then(resolve).catch(reject);
203
+ }
204
+ });
205
+
206
+ // Si la autenticación fue exitosa (no se envió respuesta aún), ejecutar el handler original
207
+ if (!res.headersSent) {
208
+ // Si el handler es asíncrono, esperarlo también
209
+ if (finalHandler.constructor.name === 'AsyncFunction') {
210
+ await finalHandler(req, res);
211
+ } else {
212
+ finalHandler(req, res);
213
+ }
214
+ }
215
+ } catch (error) {
216
+ console.error('Error en el manejo de autenticación:', error);
217
+ if (!res.headersSent) {
218
+ res.writeHead(500, { 'Content-Type': 'application/json' });
219
+ res.end(JSON.stringify({ error: 'Error interno del servidor' }));
220
+ }
221
+ }
222
+ };
223
+
224
+ // Agregar la ruta con el handler autenticado
225
+ server.addRoute(route.method, route.path, authenticatedHandler);
226
+ } else {
227
+ // Si no hay authenticator en el servidor, agregar la ruta normalmente
228
+ server.addRoute(route.method, route.path, finalHandler);
229
+ }
230
+ }
231
+ } else {
232
+ // Si no hay autenticación requerida, agregar la ruta normalmente
233
+ server.addRoute(route.method, route.path, finalHandler);
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Método para crear un middleware de autenticación para una ruta específica
239
+ * @param {string} authType - Tipo de autenticación
240
+ * @param {Object} options - Opciones para la autenticación
241
+ * @returns {Function|null} - Middleware de autenticación o null
242
+ */
243
+ createAuthenticatorForRoute(authType, options = {}) {
244
+ // Importar Authenticator dinámicamente para evitar dependencias circulares
245
+ try {
246
+ const Authenticator = require('../middleware/authenticator');
247
+ const authenticator = new Authenticator();
248
+
249
+ // Registrar estrategias predeterminadas
250
+ if (!options.jwtSecret) {
251
+ throw new Error('Se requiere un secreto JWT para la estrategia JWT');
252
+ }
253
+ authenticator.use('jwt', authenticator.jwtStrategy(options.jwtSecret));
254
+ authenticator.use('apiKey', authenticator.apiKeyStrategy(
255
+ options.apiKeyHeader || 'X-API-Key',
256
+ options.apiKeyValues || []
257
+ ));
258
+
259
+ // Crear middleware de autenticación
260
+ return authenticator.authenticate(authType, options);
261
+ } catch (error) {
262
+ console.error('Error creando middleware de autenticación:', error.message);
263
+ return null;
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Convierte una ruta con parámetros a expresión regular
269
+ * @param {string} path - Ruta con posibles parámetros
270
+ * @returns {RegExp} - Expresión regular para la ruta
271
+ */
272
+ pathToRegex(path) {
273
+ // Escapar caracteres especiales de la ruta, excepto los parámetros
274
+ // Pero dejar : sin escapar ya que lo usaremos para identificar parámetros
275
+ let escapedPath = '';
276
+ for (let i = 0; i < path.length; i++) {
277
+ const char = path[i];
278
+ if (char.match(/[.+?^${}()|[\]\\]/) && !(i > 0 && path[i-1] === ':')) {
279
+ escapedPath += '\\' + char;
280
+ } else {
281
+ escapedPath += char;
282
+ }
283
+ }
284
+ // Reemplazar parámetros :param con grupos de captura
285
+ const regexPath = escapedPath.replace(/:([a-zA-Z0-9_]+)/g, '([^/]+)');
286
+ return new RegExp(`^${regexPath}$`);
287
+ }
288
+
289
+ /**
290
+ * Método para recargar rutas desde un archivo
291
+ * @param {Object} server - Instancia del servidor
292
+ * @param {string} filePath - Ruta al archivo JSON de rutas
293
+ * @returns {Promise<Array>} - Array de rutas recargadas
294
+ */
295
+ async reloadRoutes(server, filePath) {
296
+ // Limpiar rutas previamente cargadas
297
+ this.loadedRoutes = [];
298
+
299
+ // Cargar rutas nuevamente
300
+ return await this.loadRoutes(server, filePath);
301
+ }
302
+
303
+ /**
304
+ * Método para obtener las rutas cargadas
305
+ * @returns {Array} - Array de rutas cargadas
306
+ */
307
+ getLoadedRoutes() {
308
+ return this.loadedRoutes;
309
+ }
310
+
311
+ /**
312
+ * Método para observar cambios en el archivo de rutas y recargar automáticamente
313
+ * @param {Object} server - Instancia del servidor
314
+ * @param {string} filePath - Ruta al archivo JSON de rutas
315
+ * @param {number} debounceTime - Tiempo de espera entre recargas (milisegundos)
316
+ */
317
+ watchRoutes(server, filePath, debounceTime = 1000) {
318
+ if (!fs.existsSync(filePath)) {
319
+ throw new Error(`Archivo de rutas no encontrado: ${filePath}`);
320
+ }
321
+
322
+ let timeoutId;
323
+
324
+ fs.watch(filePath, (eventType) => {
325
+ if (eventType === 'change') {
326
+ clearTimeout(timeoutId);
327
+ timeoutId = setTimeout(async () => {
328
+ try {
329
+ console.log(`Recargando rutas desde: ${filePath}`);
330
+ await this.reloadRoutes(server, filePath);
331
+ console.log('Rutas recargadas exitosamente');
332
+ } catch (error) {
333
+ console.error('Error recargando rutas:', error.message);
334
+ }
335
+ }, debounceTime);
336
+ }
337
+ });
338
+ }
339
+ }
340
+
341
+ module.exports = RouteLoader;
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Middleware de auditoría para el API SDK Framework
3
+ * Componente: lib/middleware/auditLogger.js
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ class AuditLogger {
10
+ /**
11
+ * Constructor del middleware de auditoría
12
+ * @param {Object} options - Opciones de configuración
13
+ * @param {string} options.logFile - Ruta al archivo de logs
14
+ * @param {Array} options.events - Eventos a auditar ['request', 'response', 'error']
15
+ * @param {Function} options.filter - Función para filtrar solicitudes
16
+ * @param {boolean} options.includeBody - Incluir cuerpo de la solicitud
17
+ * @param {boolean} options.includeHeaders - Incluir headers
18
+ * @param {Object} options.logger - Logger externo opcional
19
+ */
20
+ constructor(options = {}) {
21
+ this.logFile = options.logFile || './audit.log';
22
+ this.events = options.events || ['request', 'response', 'error'];
23
+ this.filter = options.filter || (() => true);
24
+ this.includeBody = options.includeBody !== false;
25
+ this.includeHeaders = options.includeHeaders !== false;
26
+ this.logger = options.logger || console;
27
+
28
+ // Asegurar que el directorio del log existe
29
+ const logDir = path.dirname(this.logFile);
30
+ if (!fs.existsSync(logDir)) {
31
+ fs.mkdirSync(logDir, { recursive: true });
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Middleware de auditoría
37
+ * @returns {Function} - Middleware de auditoría
38
+ */
39
+ middleware() {
40
+ return (req, res, next) => {
41
+ // Verificar si la solicitud debe ser auditada
42
+ if (!this.filter(req)) {
43
+ next();
44
+ return;
45
+ }
46
+
47
+ const startTime = Date.now();
48
+ const requestId = this.generateRequestId();
49
+
50
+ // Registrar la solicitud entrante si está habilitado
51
+ if (this.events.includes('request')) {
52
+ this.logRequest(req, requestId);
53
+ }
54
+
55
+ // Capturar la respuesta original para registrarla
56
+ const originalEnd = res.end;
57
+ res.end = (chunk, encoding) => {
58
+ const duration = Date.now() - startTime;
59
+
60
+ // Registrar la respuesta si está habilitado
61
+ if (this.events.includes('response')) {
62
+ this.logResponse(req, res, chunk, duration, requestId);
63
+ }
64
+
65
+ // Llamar al método original
66
+ originalEnd.call(res, chunk, encoding);
67
+ };
68
+
69
+ // Capturar errores para auditarlos
70
+ const originalOnError = req.connection && req.connection.onerror;
71
+ req.connection.onerror = (err) => {
72
+ if (this.events.includes('error')) {
73
+ this.logError(req, err, requestId);
74
+ }
75
+ if (originalOnError) originalOnError.call(req.connection, err);
76
+ };
77
+
78
+ next();
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Genera un ID único para la solicitud
84
+ * @returns {string} - ID único de solicitud
85
+ */
86
+ generateRequestId() {
87
+ return Date.now().toString(36) + Math.random().toString(36).substr(2);
88
+ }
89
+
90
+ /**
91
+ * Registra la solicitud entrante
92
+ * @param {Object} req - Objeto de solicitud
93
+ * @param {string} requestId - ID de la solicitud
94
+ */
95
+ logRequest(req, requestId) {
96
+ const logEntry = {
97
+ timestamp: new Date().toISOString(),
98
+ event: 'request',
99
+ requestId,
100
+ method: req.method,
101
+ url: req.url,
102
+ ip: this.getClientIP(req),
103
+ userAgent: req.headers['user-agent'],
104
+ headers: this.includeHeaders ? req.headers : undefined,
105
+ body: this.includeBody ? req.body : undefined
106
+ };
107
+
108
+ this.writeLog(logEntry);
109
+ }
110
+
111
+ /**
112
+ * Registra la respuesta saliente
113
+ * @param {Object} req - Objeto de solicitud
114
+ * @param {Object} res - Objeto de respuesta
115
+ * @param {any} chunk - Cuerpo de la respuesta
116
+ * @param {number} duration - Duración de la solicitud
117
+ * @param {string} requestId - ID de la solicitud
118
+ */
119
+ logResponse(req, res, chunk, duration, requestId) {
120
+ const logEntry = {
121
+ timestamp: new Date().toISOString(),
122
+ event: 'response',
123
+ requestId,
124
+ method: req.method,
125
+ url: req.url,
126
+ statusCode: res.statusCode,
127
+ duration,
128
+ ip: this.getClientIP(req),
129
+ responseSize: chunk ? Buffer.byteLength(typeof chunk === 'string' ? chunk : JSON.stringify(chunk)) : 0
130
+ };
131
+
132
+ this.writeLog(logEntry);
133
+ }
134
+
135
+ /**
136
+ * Registra un error
137
+ * @param {Object} req - Objeto de solicitud
138
+ * @param {Error} error - Error ocurrido
139
+ * @param {string} requestId - ID de la solicitud
140
+ */
141
+ logError(req, error, requestId) {
142
+ const logEntry = {
143
+ timestamp: new Date().toISOString(),
144
+ event: 'error',
145
+ requestId,
146
+ method: req.method,
147
+ url: req.url,
148
+ ip: this.getClientIP(req),
149
+ errorMessage: error.message,
150
+ stack: error.stack
151
+ };
152
+
153
+ this.writeLog(logEntry);
154
+ }
155
+
156
+ /**
157
+ * Obtiene la IP del cliente
158
+ * @param {Object} req - Objeto de solicitud
159
+ * @returns {string} - IP del cliente
160
+ */
161
+ getClientIP(req) {
162
+ return req.headers['x-forwarded-for'] ||
163
+ req.connection.remoteAddress ||
164
+ req.socket.remoteAddress ||
165
+ (req.connection?.socket ? req.connection.socket.remoteAddress : null) ||
166
+ 'unknown';
167
+ }
168
+
169
+ /**
170
+ * Escribe una entrada de log
171
+ * @param {Object} entry - Entrada de log
172
+ */
173
+ writeLog(entry) {
174
+ const logLine = JSON.stringify(entry) + '\n';
175
+
176
+ // Escribir al archivo de log
177
+ fs.appendFileSync(this.logFile, logLine);
178
+
179
+ // También escribir al logger si está disponible
180
+ if (this.logger) {
181
+ this.logger.info(`AUDIT: ${entry.event} - ${entry.method} ${entry.url} - ${entry.ip}`);
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Limpia logs antiguos
187
+ * @param {number} days - Días a mantener
188
+ */
189
+ cleanupOldLogs(days = 30) {
190
+ try {
191
+ const stats = fs.statSync(this.logFile);
192
+ const now = new Date();
193
+ const fileDate = new Date(stats.mtime);
194
+ const diffTime = Math.abs(now - fileDate);
195
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
196
+
197
+ if (diffDays > days) {
198
+ fs.unlinkSync(this.logFile);
199
+ console.log(`Archivo de log eliminado por antigüedad: ${this.logFile}`);
200
+ }
201
+ } catch (error) {
202
+ // El archivo puede no existir
203
+ console.log(`No se pudo limpiar el archivo de log: ${error.message}`);
204
+ }
205
+ }
206
+ }
207
+
208
+ module.exports = AuditLogger;