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.
- package/LICENSE +200 -0
- package/README.md +171 -0
- package/doc/EXTENSION_MANUAL.md +958 -0
- package/doc/FIREWALL_MANUAL.md +419 -0
- package/doc/HOOKS_REFERENCE_IMPROVED.md +599 -0
- package/doc/MANUAL_API_SDK.md +539 -0
- package/doc/MANUAL_MVC.md +397 -0
- package/doc/MARIADB_TOKENS_IMPLEMENTATION.md +113 -0
- package/doc/MIDDLEWARE_MANUAL.md +521 -0
- package/doc/OAUTH2_GOOGLE_MANUAL.md +408 -0
- package/doc/frontend-and-sessions.md +356 -0
- package/examples/advanced/controllers/productController.js +64 -0
- package/examples/advanced/controllers/userController.js +85 -0
- package/examples/advanced/routes.json +51 -0
- package/examples/advanced_example.js +93 -0
- package/examples/basic/controllers/userController.js +85 -0
- package/examples/basic_example.js +72 -0
- package/examples/frontend/README.md +71 -0
- package/examples/frontend/app.js +71 -0
- package/examples/frontend/controllers/apiController.js +39 -0
- package/examples/frontend/controllers/authController.js +220 -0
- package/examples/frontend/controllers/formController.js +47 -0
- package/examples/frontend/controllers/messageController.js +96 -0
- package/examples/frontend/controllers/pageController.js +178 -0
- package/examples/frontend/controllers/staticController.js +167 -0
- package/examples/frontend/routes.json +90 -0
- package/examples/mvc_example/app.js +138 -0
- package/examples/mvc_example/views/home/index.html +26 -0
- package/examples/mvc_example/views/home/simple.html +3 -0
- package/examples/mvc_example/views/layout.html +23 -0
- package/examples/mvc_example/views/test.html +3 -0
- package/examples/mvc_example/views/user/invalid.html +6 -0
- package/examples/mvc_example/views/user/list.html +36 -0
- package/examples/mvc_example/views/user/notfound.html +6 -0
- package/examples/mvc_example/views/user/profile.html +11 -0
- package/examples/mvc_routes_example/app.js +34 -0
- package/examples/mvc_routes_example/controllers/mainController.js +27 -0
- package/examples/mvc_routes_example/controllers/productController.js +47 -0
- package/examples/mvc_routes_example/controllers/userController.js +76 -0
- package/examples/mvc_routes_example/routes.json +30 -0
- package/examples/mvc_routes_example/views/layout.html +31 -0
- package/examples/mvc_routes_example/views/main/index.html +11 -0
- package/examples/mvc_routes_example/views/product/catalog.html +24 -0
- package/examples/mvc_routes_example/views/user/invalid.html +6 -0
- package/examples/mvc_routes_example/views/user/list.html +40 -0
- package/examples/mvc_routes_example/views/user/notfound.html +6 -0
- package/examples/mvc_routes_example/views/user/profile.html +18 -0
- package/examples/public/README.md +92 -0
- package/examples/public/app.js +72 -0
- package/examples/public/controllers/healthController.js +20 -0
- package/examples/public/controllers/mainController.js +22 -0
- package/examples/public/controllers/userController.js +139 -0
- package/examples/public/routes.json +51 -0
- package/examples/v2/README.md +72 -0
- package/examples/v2/app.js +74 -0
- package/examples/v2/app_fixed.js +74 -0
- package/examples/v2/controllers/authController.js +64 -0
- package/examples/v2/controllers/mainController.js +24 -0
- package/examples/v2/controllers/protectedController.js +12 -0
- package/examples/v2/controllers/userController.js +16 -0
- package/examples/v2/package.json +27 -0
- package/examples/v2/routes.json +30 -0
- package/examples/v2/test_api.sh +47 -0
- package/examples/v2/tokens_example.sqlite +0 -0
- package/examples/v2.1_firewall_demo/README.md +113 -0
- package/examples/v2.1_firewall_demo/app.js +182 -0
- package/examples/v2.1_firewall_demo/package.json +27 -0
- package/examples/v2.1_hooks_demo/README.md +85 -0
- package/examples/v2.1_hooks_demo/app.js +101 -0
- package/examples/v2.1_hooks_demo/controllers/hooksController.js +29 -0
- package/examples/v2.1_hooks_demo/controllers/mainController.js +18 -0
- package/examples/v2.1_hooks_demo/package.json +27 -0
- package/examples/v2.1_hooks_demo/routes.json +16 -0
- package/examples/v2.1_openapi_demo/README.md +82 -0
- package/examples/v2.1_openapi_demo/app.js +296 -0
- package/examples/v2.1_openapi_demo/package.json +26 -0
- package/examples/v2_cors/README.md +82 -0
- package/examples/v2_cors/app.js +108 -0
- package/examples/v2_cors/package.json +23 -0
- package/examples/v2_json_auth/README.md +83 -0
- package/examples/v2_json_auth/app.js +72 -0
- package/examples/v2_json_auth/controllers/authController.js +67 -0
- package/examples/v2_json_auth/controllers/mainController.js +16 -0
- package/examples/v2_json_auth/controllers/protectedController.js +12 -0
- package/examples/v2_json_auth/controllers/tokenController.js +28 -0
- package/examples/v2_json_auth/controllers/userController.js +15 -0
- package/examples/v2_json_auth/package.json +26 -0
- package/examples/v2_json_auth/routes.json +37 -0
- package/examples/v2_json_auth/tokens.json +20 -0
- package/examples/v2_mariadb_auth/README.md +94 -0
- package/examples/v2_mariadb_auth/app.js +81 -0
- package/examples/v2_mariadb_auth/controllers/authController.js +95 -0
- package/examples/v2_mariadb_auth/controllers/mainController.js +31 -0
- package/examples/v2_mariadb_auth/controllers/protectedController.js +12 -0
- package/examples/v2_mariadb_auth/controllers/userController.js +17 -0
- package/examples/v2_mariadb_auth/package.json +27 -0
- package/examples/v2_mariadb_auth/routes.json +37 -0
- package/examples/v2_no_auth/README.md +75 -0
- package/examples/v2_no_auth/app.js +72 -0
- package/examples/v2_no_auth/controllers/healthController.js +14 -0
- package/examples/v2_no_auth/controllers/mainController.js +19 -0
- package/examples/v2_no_auth/controllers/productController.js +31 -0
- package/examples/v2_no_auth/controllers/publicController.js +16 -0
- package/examples/v2_no_auth/package.json +22 -0
- package/examples/v2_no_auth/routes.json +37 -0
- package/examples/v2_oauth/README.md +70 -0
- package/examples/v2_oauth/app.js +90 -0
- package/examples/v2_oauth/controllers/mainController.js +45 -0
- package/examples/v2_oauth/controllers/oauthController.js +247 -0
- package/examples/v2_oauth/controllers/protectedController.js +13 -0
- package/examples/v2_oauth/controllers/userController.js +17 -0
- package/examples/v2_oauth/package.json +26 -0
- package/examples/v2_oauth/routes.json +44 -0
- package/examples/v2_openapi/README.md +77 -0
- package/examples/v2_openapi/app.js +222 -0
- package/examples/v2_openapi/controllers/authController.js +52 -0
- package/examples/v2_openapi/controllers/mainController.js +26 -0
- package/examples/v2_openapi/controllers/productController.js +17 -0
- package/examples/v2_openapi/controllers/userController.js +27 -0
- package/examples/v2_openapi/package.json +26 -0
- package/examples/v2_openapi/routes.json +37 -0
- package/generate_token.js +10 -0
- package/index.js +85 -0
- package/jerk.jpg +0 -0
- package/lib/core/handler.js +86 -0
- package/lib/core/hooks.js +224 -0
- package/lib/core/router.js +204 -0
- package/lib/core/securityEnhancedServer.js +752 -0
- package/lib/core/server.js +369 -0
- package/lib/loader/controllerLoader.js +175 -0
- package/lib/loader/routeLoader.js +341 -0
- package/lib/middleware/auditLogger.js +208 -0
- package/lib/middleware/authenticator.js +565 -0
- package/lib/middleware/compressor.js +218 -0
- package/lib/middleware/cors.js +135 -0
- package/lib/middleware/firewall.js +443 -0
- package/lib/middleware/rateLimiter.js +210 -0
- package/lib/middleware/session.js +301 -0
- package/lib/middleware/validator.js +193 -0
- package/lib/mvc/controllerBase.js +207 -0
- package/lib/mvc/viewEngine.js +752 -0
- package/lib/utils/configParser.js +223 -0
- package/lib/utils/logger.js +145 -0
- package/lib/utils/mariadbTokenAdapter.js +226 -0
- package/lib/utils/openapiGenerator.js +140 -0
- package/lib/utils/sqliteTokenAdapter.js +224 -0
- package/lib/utils/tokenManager.js +254 -0
- package/package.json +47 -0
- package/v2examplle/v2_json_auth/README.md +83 -0
- package/v2examplle/v2_json_auth/app.js +72 -0
- package/v2examplle/v2_json_auth/controllers/authController.js +67 -0
- package/v2examplle/v2_json_auth/controllers/mainController.js +16 -0
- package/v2examplle/v2_json_auth/controllers/protectedController.js +12 -0
- package/v2examplle/v2_json_auth/controllers/tokenController.js +28 -0
- package/v2examplle/v2_json_auth/controllers/userController.js +15 -0
- package/v2examplle/v2_json_auth/package.json +26 -0
- package/v2examplle/v2_json_auth/routes.json +37 -0
- package/v2examplle/v2_json_auth/tokens.json +20 -0
- package/v2examplle/v2_mariadb_auth/README.md +94 -0
- package/v2examplle/v2_mariadb_auth/app.js +81 -0
- package/v2examplle/v2_mariadb_auth/controllers/authController.js +95 -0
- package/v2examplle/v2_mariadb_auth/controllers/mainController.js +31 -0
- package/v2examplle/v2_mariadb_auth/controllers/protectedController.js +12 -0
- package/v2examplle/v2_mariadb_auth/controllers/userController.js +17 -0
- package/v2examplle/v2_mariadb_auth/package.json +27 -0
- package/v2examplle/v2_mariadb_auth/routes.json +37 -0
- package/v2examplle/v2_sqlite_auth/README.md +72 -0
- package/v2examplle/v2_sqlite_auth/app.js +74 -0
- package/v2examplle/v2_sqlite_auth/app_fixed.js +74 -0
- package/v2examplle/v2_sqlite_auth/controllers/authController.js +64 -0
- package/v2examplle/v2_sqlite_auth/controllers/mainController.js +24 -0
- package/v2examplle/v2_sqlite_auth/controllers/protectedController.js +12 -0
- package/v2examplle/v2_sqlite_auth/controllers/userController.js +16 -0
- package/v2examplle/v2_sqlite_auth/package.json +27 -0
- package/v2examplle/v2_sqlite_auth/routes.json +30 -0
- package/v2examplle/v2_sqlite_auth/test_api.sh +47 -0
- 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;
|