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,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser de configuración para el framework API SDK
|
|
3
|
+
* Implementación del componente utils/configParser.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
class ConfigParser {
|
|
10
|
+
/**
|
|
11
|
+
* Constructor del parser de configuración
|
|
12
|
+
*/
|
|
13
|
+
constructor() {
|
|
14
|
+
this.config = {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Método para cargar configuración desde un archivo JSON
|
|
19
|
+
* @param {string} filePath - Ruta al archivo de configuración
|
|
20
|
+
* @returns {Object} - Configuración cargada
|
|
21
|
+
*/
|
|
22
|
+
loadFromFile(filePath) {
|
|
23
|
+
try {
|
|
24
|
+
if (!fs.existsSync(filePath)) {
|
|
25
|
+
throw new Error(`Archivo de configuración no encontrado: ${filePath}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const configData = fs.readFileSync(filePath, 'utf8');
|
|
29
|
+
const parsedConfig = JSON.parse(configData);
|
|
30
|
+
|
|
31
|
+
this.config = { ...this.config, ...parsedConfig };
|
|
32
|
+
return this.config;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new Error(`Error cargando configuración desde ${filePath}: ${error.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Método para cargar configuración desde variables de entorno
|
|
40
|
+
* @param {Object} env - Objeto con variables de entorno (por defecto process.env)
|
|
41
|
+
* @param {Object} mapping - Objeto que mapea variables de entorno a claves de configuración
|
|
42
|
+
* @returns {Object} - Configuración actualizada
|
|
43
|
+
*/
|
|
44
|
+
loadFromEnv(env = process.env, mapping = {}) {
|
|
45
|
+
const envConfig = {};
|
|
46
|
+
|
|
47
|
+
// Si se proporciona un mapeo, usarlo para leer variables de entorno
|
|
48
|
+
if (Object.keys(mapping).length > 0) {
|
|
49
|
+
for (const [configKey, envVar] of Object.entries(mapping)) {
|
|
50
|
+
if (env[envVar] !== undefined) {
|
|
51
|
+
envConfig[configKey] = this.parseEnvValue(env[envVar]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
// Si no hay mapeo, buscar variables con prefijo API_
|
|
56
|
+
for (const [key, value] of Object.entries(env)) {
|
|
57
|
+
if (key.startsWith('API_')) {
|
|
58
|
+
const configKey = this.snakeToCamel(key.substring(4).toLowerCase()); // Remover API_ y convertir
|
|
59
|
+
envConfig[configKey] = this.parseEnvValue(value);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.config = { ...this.config, ...envConfig };
|
|
65
|
+
return this.config;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Método para parsear valores de variables de entorno
|
|
70
|
+
* @param {string} value - Valor de la variable de entorno
|
|
71
|
+
* @returns {*} - Valor parseado (string, number, boolean, etc.)
|
|
72
|
+
*/
|
|
73
|
+
parseEnvValue(value) {
|
|
74
|
+
// Intentar parsear como booleano
|
|
75
|
+
if (value.toLowerCase() === 'true') return true;
|
|
76
|
+
if (value.toLowerCase() === 'false') return false;
|
|
77
|
+
|
|
78
|
+
// Intentar parsear como número
|
|
79
|
+
if (!isNaN(value) && value.trim() !== '') {
|
|
80
|
+
const numValue = Number(value);
|
|
81
|
+
if (!isNaN(numValue)) return numValue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Si contiene comas, podría ser un array
|
|
85
|
+
if (value.includes(',')) {
|
|
86
|
+
return value.split(',').map(item => this.parseEnvValue(item.trim()));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Por defecto, retornar como string
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Método para convertir snake_case a camelCase
|
|
95
|
+
* @param {string} str - Cadena en formato snake_case
|
|
96
|
+
* @returns {string} - Cadena en formato camelCase
|
|
97
|
+
*/
|
|
98
|
+
snakeToCamel(str) {
|
|
99
|
+
return str.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Método para cargar configuración por defecto
|
|
104
|
+
* @param {Object} defaults - Configuración por defecto
|
|
105
|
+
* @returns {Object} - Configuración actualizada
|
|
106
|
+
*/
|
|
107
|
+
loadDefaults(defaults) {
|
|
108
|
+
this.config = { ...defaults, ...this.config };
|
|
109
|
+
return this.config;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Método para obtener un valor de configuración
|
|
114
|
+
* @param {string} key - Clave de la configuración (puede usar notación de punto para objetos anidados)
|
|
115
|
+
* @param {*} defaultValue - Valor por defecto si no se encuentra la clave
|
|
116
|
+
* @returns {*} - Valor de la configuración
|
|
117
|
+
*/
|
|
118
|
+
get(key, defaultValue = undefined) {
|
|
119
|
+
// Soportar notación de punto para acceder a propiedades anidadas
|
|
120
|
+
const keys = key.split('.');
|
|
121
|
+
let value = this.config;
|
|
122
|
+
|
|
123
|
+
for (const k of keys) {
|
|
124
|
+
if (value === null || value === undefined || typeof value !== 'object') {
|
|
125
|
+
return defaultValue;
|
|
126
|
+
}
|
|
127
|
+
value = value[k];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return value !== undefined ? value : defaultValue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Método para establecer un valor de configuración
|
|
135
|
+
* @param {string} key - Clave de la configuración (puede usar notación de punto para objetos anidados)
|
|
136
|
+
* @param {*} value - Valor a establecer
|
|
137
|
+
*/
|
|
138
|
+
set(key, value) {
|
|
139
|
+
// Soportar notación de punto para establecer propiedades anidadas
|
|
140
|
+
const keys = key.split('.');
|
|
141
|
+
let current = this.config;
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
144
|
+
const k = keys[i];
|
|
145
|
+
if (current[k] === undefined || current[k] === null) {
|
|
146
|
+
current[k] = {};
|
|
147
|
+
}
|
|
148
|
+
current = current[k];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
current[keys[keys.length - 1]] = value;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Método para obtener toda la configuración
|
|
156
|
+
* @returns {Object} - Objeto con toda la configuración
|
|
157
|
+
*/
|
|
158
|
+
getAll() {
|
|
159
|
+
return { ...this.config };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Método para reiniciar la configuración
|
|
164
|
+
*/
|
|
165
|
+
reset() {
|
|
166
|
+
this.config = {};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Método para validar la configuración contra un esquema
|
|
171
|
+
* @param {Object} schema - Esquema de validación
|
|
172
|
+
* @returns {Array} - Array de errores de validación
|
|
173
|
+
*/
|
|
174
|
+
validate(schema) {
|
|
175
|
+
const errors = [];
|
|
176
|
+
|
|
177
|
+
for (const [key, rules] of Object.entries(schema)) {
|
|
178
|
+
const value = this.get(key);
|
|
179
|
+
|
|
180
|
+
if (rules.required && (value === undefined || value === null)) {
|
|
181
|
+
errors.push(`Campo requerido: ${key}`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (value !== undefined && rules.type) {
|
|
186
|
+
const valueType = typeof value;
|
|
187
|
+
if (rules.type === 'array' && !Array.isArray(value)) {
|
|
188
|
+
errors.push(`Campo ${key} debe ser un array`);
|
|
189
|
+
} else if (rules.type !== 'array' && valueType !== rules.type) {
|
|
190
|
+
errors.push(`Campo ${key} debe ser de tipo ${rules.type}, pero es ${valueType}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (value !== undefined && rules.validator && typeof rules.validator === 'function') {
|
|
195
|
+
const isValid = rules.validator(value);
|
|
196
|
+
if (!isValid) {
|
|
197
|
+
errors.push(`Campo ${key} no pasó la validación personalizada`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return errors;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Método para guardar la configuración en un archivo
|
|
207
|
+
* @param {string} filePath - Ruta al archivo donde guardar la configuración
|
|
208
|
+
*/
|
|
209
|
+
saveToFile(filePath) {
|
|
210
|
+
try {
|
|
211
|
+
const dir = path.dirname(filePath);
|
|
212
|
+
if (!fs.existsSync(dir)) {
|
|
213
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
fs.writeFileSync(filePath, JSON.stringify(this.config, null, 2));
|
|
217
|
+
} catch (error) {
|
|
218
|
+
throw new Error(`Error guardando configuración en ${filePath}: ${error.message}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = ConfigParser;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sistema de logging para el framework API SDK
|
|
3
|
+
* Implementación del componente utils/logger.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class Logger {
|
|
7
|
+
/**
|
|
8
|
+
* Constructor del logger
|
|
9
|
+
* @param {Object} options - Opciones de configuración
|
|
10
|
+
* @param {string} options.level - Nivel de log (debug, info, warn, error)
|
|
11
|
+
* @param {boolean} options.timestamp - Incluir timestamp en los logs
|
|
12
|
+
* @param {string} options.format - Formato de salida (simple, json)
|
|
13
|
+
*/
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.level = options.level || 'info';
|
|
16
|
+
this.timestamp = options.timestamp !== false; // Por defecto true
|
|
17
|
+
this.format = options.format || 'simple';
|
|
18
|
+
this.levels = {
|
|
19
|
+
debug: 0,
|
|
20
|
+
info: 1,
|
|
21
|
+
warn: 2,
|
|
22
|
+
error: 3
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Método para registrar un mensaje de nivel debug
|
|
28
|
+
* @param {...any} args - Argumentos a loggear
|
|
29
|
+
*/
|
|
30
|
+
debug(...args) {
|
|
31
|
+
if (this.levels[this.level] <= this.levels.debug) {
|
|
32
|
+
this._log('DEBUG', ...args);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Método para registrar un mensaje de nivel info
|
|
38
|
+
* @param {...any} args - Argumentos a loggear
|
|
39
|
+
*/
|
|
40
|
+
info(...args) {
|
|
41
|
+
if (this.levels[this.level] <= this.levels.info) {
|
|
42
|
+
this._log('INFO', ...args);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Método para registrar un mensaje de nivel warn
|
|
48
|
+
* @param {...any} args - Argumentos a loggear
|
|
49
|
+
*/
|
|
50
|
+
warn(...args) {
|
|
51
|
+
if (this.levels[this.level] <= this.levels.warn) {
|
|
52
|
+
this._log('WARN', ...args);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Método para registrar un mensaje de nivel error
|
|
58
|
+
* @param {...any} args - Argumentos a loggear
|
|
59
|
+
*/
|
|
60
|
+
error(...args) {
|
|
61
|
+
if (this.levels[this.level] <= this.levels.error) {
|
|
62
|
+
this._log('ERROR', ...args);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Método privado para escribir el log
|
|
68
|
+
* @param {string} level - Nivel del log
|
|
69
|
+
* @param {...any} args - Argumentos a loggear
|
|
70
|
+
*/
|
|
71
|
+
_log(level, ...args) {
|
|
72
|
+
const timestamp = this.timestamp ? new Date().toISOString() : '';
|
|
73
|
+
const message = args.map(arg => {
|
|
74
|
+
if (typeof arg === 'object') {
|
|
75
|
+
return JSON.stringify(arg, null, 2);
|
|
76
|
+
}
|
|
77
|
+
return String(arg);
|
|
78
|
+
}).join(' ');
|
|
79
|
+
|
|
80
|
+
if (this.format === 'json') {
|
|
81
|
+
const logEntry = {
|
|
82
|
+
timestamp,
|
|
83
|
+
level,
|
|
84
|
+
message
|
|
85
|
+
};
|
|
86
|
+
console.log(JSON.stringify(logEntry));
|
|
87
|
+
} else {
|
|
88
|
+
const prefix = timestamp ? `[${timestamp}] ${level}:` : `${level}:`;
|
|
89
|
+
console.log(prefix, message);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Método para cambiar el nivel de log
|
|
95
|
+
* @param {string} level - Nuevo nivel de log
|
|
96
|
+
*/
|
|
97
|
+
setLevel(level) {
|
|
98
|
+
if (this.levels[level] !== undefined) {
|
|
99
|
+
this.level = level;
|
|
100
|
+
} else {
|
|
101
|
+
throw new Error(`Nivel de log inválido: ${level}. Niveles válidos: ${Object.keys(this.levels).join(', ')}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Método para crear un logger con contexto adicional
|
|
107
|
+
* @param {Object} context - Contexto adicional para los logs
|
|
108
|
+
* @returns {Logger} - Nueva instancia de Logger con contexto
|
|
109
|
+
*/
|
|
110
|
+
withContext(context) {
|
|
111
|
+
const logger = new Logger({
|
|
112
|
+
level: this.level,
|
|
113
|
+
timestamp: this.timestamp,
|
|
114
|
+
format: this.format
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Sobrescribir el método _log para incluir contexto
|
|
118
|
+
logger._log = (level, ...args) => {
|
|
119
|
+
const timestamp = this.timestamp ? new Date().toISOString() : '';
|
|
120
|
+
const message = args.map(arg => {
|
|
121
|
+
if (typeof arg === 'object') {
|
|
122
|
+
return JSON.stringify(arg, null, 2);
|
|
123
|
+
}
|
|
124
|
+
return String(arg);
|
|
125
|
+
}).join(' ');
|
|
126
|
+
|
|
127
|
+
if (this.format === 'json') {
|
|
128
|
+
const logEntry = {
|
|
129
|
+
timestamp,
|
|
130
|
+
level,
|
|
131
|
+
context,
|
|
132
|
+
message
|
|
133
|
+
};
|
|
134
|
+
console.log(JSON.stringify(logEntry));
|
|
135
|
+
} else {
|
|
136
|
+
const prefix = timestamp ? `[${timestamp}] ${level}:` : `${level}:`;
|
|
137
|
+
console.log(prefix, `[${JSON.stringify(context)}]`, message);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return logger;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = { Logger };
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptador de almacenamiento para MariaDB - Implementación Real
|
|
3
|
+
* Componente: lib/utils/mariadbTokenAdapter.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const mariadb = require('mariadb');
|
|
7
|
+
|
|
8
|
+
class MariaDBTokenAdapter {
|
|
9
|
+
/**
|
|
10
|
+
* Constructor del adaptador
|
|
11
|
+
* @param {Object} config - Configuración de conexión a MariaDB
|
|
12
|
+
*/
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.config = {
|
|
15
|
+
host: config.host || 'localhost',
|
|
16
|
+
port: config.port || 3306,
|
|
17
|
+
user: config.user || 'root',
|
|
18
|
+
password: config.password || '',
|
|
19
|
+
database: config.database || 'token_db',
|
|
20
|
+
connectionLimit: config.connectionLimit || 5
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
this.pool = null;
|
|
24
|
+
this.tableName = config.tableName || 'tokens';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Inicializa la conexión y la tabla de tokens
|
|
29
|
+
*/
|
|
30
|
+
async initialize() {
|
|
31
|
+
try {
|
|
32
|
+
// Crear pool de conexiones
|
|
33
|
+
this.pool = mariadb.createPool(this.config);
|
|
34
|
+
|
|
35
|
+
// Crear tabla de tokens si no existe
|
|
36
|
+
await this.initializeTable();
|
|
37
|
+
|
|
38
|
+
console.log('✅ Conexión a MariaDB establecida y tabla de tokens inicializada');
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('❌ Error inicializando MariaDB Token Adapter:', error.message);
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Inicializa la tabla de tokens en la base de datos
|
|
47
|
+
*/
|
|
48
|
+
async initializeTable() {
|
|
49
|
+
const createTableQuery = `
|
|
50
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
51
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
52
|
+
token VARCHAR(500) UNIQUE NOT NULL,
|
|
53
|
+
user_id INT NOT NULL,
|
|
54
|
+
token_type ENUM('access', 'refresh') DEFAULT 'access',
|
|
55
|
+
expires_at TIMESTAMP NOT NULL,
|
|
56
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
57
|
+
revoked BOOLEAN DEFAULT FALSE,
|
|
58
|
+
INDEX idx_user_id (user_id),
|
|
59
|
+
INDEX idx_token (token),
|
|
60
|
+
INDEX idx_expires_at (expires_at)
|
|
61
|
+
)
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
let conn;
|
|
65
|
+
try {
|
|
66
|
+
conn = await this.pool.getConnection();
|
|
67
|
+
await conn.query(createTableQuery);
|
|
68
|
+
console.log(`✅ Tabla ${this.tableName} inicializada correctamente`);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error('❌ Error inicializando tabla de tokens:', error.message);
|
|
71
|
+
throw error;
|
|
72
|
+
} finally {
|
|
73
|
+
if (conn) conn.release();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Guarda un token en la base de datos
|
|
79
|
+
* @param {string} token - Token a guardar
|
|
80
|
+
* @param {Object} userData - Datos del usuario
|
|
81
|
+
* @param {string} tokenType - Tipo de token ('access' o 'refresh')
|
|
82
|
+
* @param {Date} expiresAt - Fecha de expiración
|
|
83
|
+
*/
|
|
84
|
+
async saveToken(token, userData, tokenType = 'access', expiresAt) {
|
|
85
|
+
const insertQuery = `
|
|
86
|
+
INSERT INTO ${this.tableName} (token, user_id, token_type, expires_at)
|
|
87
|
+
VALUES (?, ?, ?, ?)
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
let conn;
|
|
91
|
+
try {
|
|
92
|
+
conn = await this.pool.getConnection();
|
|
93
|
+
await conn.query(insertQuery, [
|
|
94
|
+
token,
|
|
95
|
+
userData.userId || userData.id,
|
|
96
|
+
tokenType,
|
|
97
|
+
expiresAt
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
console.log(`✅ Token ${tokenType} guardado para usuario ${userData.userId || userData.id}`);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('❌ Error guardando token:', error.message);
|
|
103
|
+
throw error;
|
|
104
|
+
} finally {
|
|
105
|
+
if (conn) conn.release();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Verifica si un token existe y es válido
|
|
111
|
+
* @param {string} token - Token a verificar
|
|
112
|
+
* @returns {Object|null} - Datos del token o null si no es válido
|
|
113
|
+
*/
|
|
114
|
+
async validateToken(token) {
|
|
115
|
+
const selectQuery = `
|
|
116
|
+
SELECT * FROM ${this.tableName}
|
|
117
|
+
WHERE token = ? AND revoked = FALSE AND expires_at > NOW()
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
let conn;
|
|
121
|
+
try {
|
|
122
|
+
conn = await this.pool.getConnection();
|
|
123
|
+
const rows = await conn.query(selectQuery, [token]);
|
|
124
|
+
return rows.length > 0 ? rows[0] : null;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error('❌ Error validando token:', error.message);
|
|
127
|
+
return null;
|
|
128
|
+
} finally {
|
|
129
|
+
if (conn) conn.release();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Revoca un token
|
|
135
|
+
* @param {string} token - Token a revocar
|
|
136
|
+
* @returns {boolean} - True si se revocó exitosamente
|
|
137
|
+
*/
|
|
138
|
+
async revokeToken(token) {
|
|
139
|
+
const updateQuery = `
|
|
140
|
+
UPDATE ${this.tableName}
|
|
141
|
+
SET revoked = TRUE
|
|
142
|
+
WHERE token = ?
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
let conn;
|
|
146
|
+
try {
|
|
147
|
+
conn = await this.pool.getConnection();
|
|
148
|
+
const result = await conn.query(updateQuery, [token]);
|
|
149
|
+
const revoked = result.affectedRows > 0;
|
|
150
|
+
|
|
151
|
+
if (revoked) {
|
|
152
|
+
console.log(`✅ Token revocado: ${token}`);
|
|
153
|
+
} else {
|
|
154
|
+
console.log(`⚠️ Token no encontrado o ya revocado: ${token}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return revoked;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error('❌ Error revocando token:', error.message);
|
|
160
|
+
return false;
|
|
161
|
+
} finally {
|
|
162
|
+
if (conn) conn.release();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Obtiene tokens de un usuario
|
|
168
|
+
* @param {number} userId - ID del usuario
|
|
169
|
+
* @returns {Array} - Array de tokens del usuario
|
|
170
|
+
*/
|
|
171
|
+
async getUserTokens(userId) {
|
|
172
|
+
const selectQuery = `
|
|
173
|
+
SELECT * FROM ${this.tableName}
|
|
174
|
+
WHERE user_id = ? AND revoked = FALSE AND expires_at > NOW()
|
|
175
|
+
`;
|
|
176
|
+
|
|
177
|
+
let conn;
|
|
178
|
+
try {
|
|
179
|
+
conn = await this.pool.getConnection();
|
|
180
|
+
const rows = await conn.query(selectQuery, [userId]);
|
|
181
|
+
return rows;
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error('❌ Error obteniendo tokens de usuario:', error.message);
|
|
184
|
+
return [];
|
|
185
|
+
} finally {
|
|
186
|
+
if (conn) conn.release();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Limpia tokens expirados
|
|
192
|
+
*/
|
|
193
|
+
async cleanupExpiredTokens() {
|
|
194
|
+
const deleteQuery = `
|
|
195
|
+
DELETE FROM ${this.tableName}
|
|
196
|
+
WHERE expires_at <= NOW()
|
|
197
|
+
`;
|
|
198
|
+
|
|
199
|
+
let conn;
|
|
200
|
+
try {
|
|
201
|
+
conn = await this.pool.getConnection();
|
|
202
|
+
const result = await conn.query(deleteQuery);
|
|
203
|
+
const deletedCount = result.affectedRows;
|
|
204
|
+
|
|
205
|
+
console.log(`✅ Eliminados ${deletedCount} tokens expirados`);
|
|
206
|
+
return deletedCount;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error('❌ Error limpiando tokens expirados:', error.message);
|
|
209
|
+
return 0;
|
|
210
|
+
} finally {
|
|
211
|
+
if (conn) conn.release();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Cierra la conexión al pool
|
|
217
|
+
*/
|
|
218
|
+
async close() {
|
|
219
|
+
if (this.pool) {
|
|
220
|
+
await this.pool.end();
|
|
221
|
+
console.log('🔒 Conexión a MariaDB cerrada');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = MariaDBTokenAdapter;
|