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,958 @@
|
|
|
1
|
+
# Manual de Extensión del Framework API SDK
|
|
2
|
+
|
|
3
|
+
Visita nuestra página web: https://jerk.page.gd/
|
|
4
|
+
Repositorio oficial: https://gitlab.com/bytedogssyndicate1/jerk/
|
|
5
|
+
|
|
6
|
+
## Tabla de Contenidos
|
|
7
|
+
|
|
8
|
+
1. [Introducción a la Extensión del Framework](#introducción-a-la-extensión-del-framework)
|
|
9
|
+
2. [Arquitectura de Extensibilidad](#arquitectura-de-extensibilidad)
|
|
10
|
+
3. [Patrones de Extensión Comunes](#patrones-de-extensión-comunes)
|
|
11
|
+
4. [Ejemplo Práctico: Extensión para SQLite](#ejemplo-práctico-extensión-para-sqlite)
|
|
12
|
+
5. [Guía de Implementación](#guía-de-implementación)
|
|
13
|
+
6. [Pruebas y Validación](#pruebas-y-validación)
|
|
14
|
+
7. [Integración con el Framework](#integración-con-el-framework)
|
|
15
|
+
8. [Documentación y Distribución](#documentación-y-distribución)
|
|
16
|
+
|
|
17
|
+
## Introducción a la Extensión del Framework
|
|
18
|
+
|
|
19
|
+
El API SDK Framework está diseñado con una arquitectura modular y extensible que permite a los desarrolladores crear extensiones personalizadas para satisfacer necesidades específicas. La extensibilidad es un principio fundamental del framework que permite:
|
|
20
|
+
|
|
21
|
+
- **Personalización**: Adaptar el framework a necesidades específicas
|
|
22
|
+
- **Integración**: Conectar con sistemas y tecnologías externas
|
|
23
|
+
- **Reutilización**: Crear componentes reutilizables
|
|
24
|
+
- **Mantenibilidad**: Aislar funcionalidades en módulos independientes
|
|
25
|
+
|
|
26
|
+
## Arquitectura de Extensibilidad
|
|
27
|
+
|
|
28
|
+
El framework proporciona varias interfaces y patrones para la extensión:
|
|
29
|
+
|
|
30
|
+
### 1. Patrón de Adaptador
|
|
31
|
+
El framework utiliza el patrón de adaptador para permitir diferentes implementaciones de servicios comunes como el almacenamiento de tokens.
|
|
32
|
+
|
|
33
|
+
### 2. Middleware
|
|
34
|
+
Los componentes pueden extenderse mediante middleware que se inserta en el pipeline de procesamiento.
|
|
35
|
+
|
|
36
|
+
### 3. Estrategias de Autenticación
|
|
37
|
+
El sistema de autenticación permite registrar nuevas estrategias personalizadas.
|
|
38
|
+
|
|
39
|
+
### 4. Sistemas de Carga
|
|
40
|
+
Los loaders permiten extender la funcionalidad de carga de rutas y controladores.
|
|
41
|
+
|
|
42
|
+
## Patrones de Extensión Comunes
|
|
43
|
+
|
|
44
|
+
### 1. Adaptador de Almacenamiento
|
|
45
|
+
Patrón utilizado para diferentes sistemas de almacenamiento (JSON, MariaDB, etc.)
|
|
46
|
+
|
|
47
|
+
### 2. Middleware Personalizado
|
|
48
|
+
Extensión de funcionalidad a través de middleware
|
|
49
|
+
|
|
50
|
+
### 3. Estrategias de Autenticación
|
|
51
|
+
Nuevas formas de autenticar usuarios
|
|
52
|
+
|
|
53
|
+
### 4. Sistemas de Logging Personalizados
|
|
54
|
+
Adaptadores para diferentes sistemas de logging
|
|
55
|
+
|
|
56
|
+
## Ejemplo Práctico: Extensión para SQLite
|
|
57
|
+
|
|
58
|
+
Vamos a crear una extensión completa para almacenar tokens en SQLite, siguiendo los mismos patrones que el adaptador de MariaDB.
|
|
59
|
+
|
|
60
|
+
### 1. Crear el Adaptador de SQLite
|
|
61
|
+
|
|
62
|
+
Primero, crearemos el archivo para el adaptador de SQLite:
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
// lib/utils/sqliteTokenAdapter.js
|
|
66
|
+
const sqlite3 = require('sqlite3').verbose();
|
|
67
|
+
const path = require('path');
|
|
68
|
+
|
|
69
|
+
class SQLiteTokenAdapter {
|
|
70
|
+
/**
|
|
71
|
+
* Constructor del adaptador
|
|
72
|
+
* @param {Object} config - Configuración de conexión
|
|
73
|
+
* @param {string} config.dbPath - Ruta a la base de datos SQLite
|
|
74
|
+
* @param {string} config.tableName - Nombre de la tabla de tokens
|
|
75
|
+
*/
|
|
76
|
+
constructor(config) {
|
|
77
|
+
this.dbPath = config.dbPath || './tokens.sqlite';
|
|
78
|
+
this.tableName = config.tableName || 'tokens';
|
|
79
|
+
this.db = null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Inicializa la conexión y la tabla de tokens
|
|
84
|
+
*/
|
|
85
|
+
async initialize() {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
this.db = new sqlite3.Database(this.dbPath, (err) => {
|
|
88
|
+
if (err) {
|
|
89
|
+
console.error('Error conectando a SQLite:', err.message);
|
|
90
|
+
reject(err);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log(`✅ Conexión a SQLite establecida: ${this.dbPath}`);
|
|
95
|
+
|
|
96
|
+
// Crear tabla de tokens si no existe
|
|
97
|
+
this.initializeTable()
|
|
98
|
+
.then(() => {
|
|
99
|
+
console.log(`✅ Tabla ${this.tableName} inicializada correctamente`);
|
|
100
|
+
resolve();
|
|
101
|
+
})
|
|
102
|
+
.catch(reject);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Inicializa la tabla de tokens en la base de datos
|
|
109
|
+
*/
|
|
110
|
+
async initializeTable() {
|
|
111
|
+
const createTableQuery = `
|
|
112
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
113
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
114
|
+
token TEXT UNIQUE NOT NULL,
|
|
115
|
+
user_id INTEGER NOT NULL,
|
|
116
|
+
token_type TEXT DEFAULT 'access',
|
|
117
|
+
expires_at DATETIME NOT NULL,
|
|
118
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
119
|
+
revoked BOOLEAN DEFAULT 0,
|
|
120
|
+
INDEX_idx_user_id (user_id),
|
|
121
|
+
INDEX_idx_token (token),
|
|
122
|
+
INDEX_idx_expires_at (expires_at)
|
|
123
|
+
)
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
this.db.run(createTableQuery, (err) => {
|
|
128
|
+
if (err) {
|
|
129
|
+
console.error('Error inicializando tabla de tokens:', err.message);
|
|
130
|
+
reject(err);
|
|
131
|
+
} else {
|
|
132
|
+
resolve();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Guarda un token en la base de datos
|
|
140
|
+
* @param {string} token - Token a guardar
|
|
141
|
+
* @param {Object} userData - Datos del usuario
|
|
142
|
+
* @param {string} tokenType - Tipo de token ('access' o 'refresh')
|
|
143
|
+
* @param {Date} expiresAt - Fecha de expiración
|
|
144
|
+
*/
|
|
145
|
+
async saveToken(token, userData, tokenType = 'access', expiresAt) {
|
|
146
|
+
const insertQuery = `
|
|
147
|
+
INSERT INTO ${this.tableName} (token, user_id, token_type, expires_at)
|
|
148
|
+
VALUES (?, ?, ?, ?)
|
|
149
|
+
`;
|
|
150
|
+
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
this.db.run(insertQuery, [
|
|
153
|
+
token,
|
|
154
|
+
userData.userId || userData.id,
|
|
155
|
+
tokenType,
|
|
156
|
+
expiresAt.toISOString()
|
|
157
|
+
], function(err) {
|
|
158
|
+
if (err) {
|
|
159
|
+
console.error('Error guardando token:', err.message);
|
|
160
|
+
reject(err);
|
|
161
|
+
} else {
|
|
162
|
+
console.log(`✅ Token ${tokenType} guardado para usuario ${userData.userId || userData.id}`);
|
|
163
|
+
resolve();
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Verifica si un token existe y es válido
|
|
171
|
+
* @param {string} token - Token a verificar
|
|
172
|
+
* @returns {Object|null} - Datos del token o null si no es válido
|
|
173
|
+
*/
|
|
174
|
+
async validateToken(token) {
|
|
175
|
+
const selectQuery = `
|
|
176
|
+
SELECT * FROM ${this.tableName}
|
|
177
|
+
WHERE token = ? AND revoked = 0 AND expires_at > datetime('now')
|
|
178
|
+
`;
|
|
179
|
+
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
this.db.get(selectQuery, [token], (err, row) => {
|
|
182
|
+
if (err) {
|
|
183
|
+
console.error('Error validando token:', err.message);
|
|
184
|
+
reject(err);
|
|
185
|
+
} else {
|
|
186
|
+
resolve(row || null);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Revoca un token
|
|
194
|
+
* @param {string} token - Token a revocar
|
|
195
|
+
* @returns {boolean} - True si se revocó exitosamente
|
|
196
|
+
*/
|
|
197
|
+
async revokeToken(token) {
|
|
198
|
+
const updateQuery = `
|
|
199
|
+
UPDATE ${this.tableName}
|
|
200
|
+
SET revoked = 1
|
|
201
|
+
WHERE token = ?
|
|
202
|
+
`;
|
|
203
|
+
|
|
204
|
+
return new Promise((resolve, reject) => {
|
|
205
|
+
this.db.run(updateQuery, [token], function(err) {
|
|
206
|
+
if (err) {
|
|
207
|
+
console.error('Error revocando token:', err.message);
|
|
208
|
+
reject(err);
|
|
209
|
+
} else {
|
|
210
|
+
const revoked = this.changes > 0;
|
|
211
|
+
if (revoked) {
|
|
212
|
+
console.log(`✅ Token revocado: ${token.substring(0, 20)}...`);
|
|
213
|
+
} else {
|
|
214
|
+
console.log(`⚠️ Token no encontrado o ya revocado: ${token.substring(0, 20)}...`);
|
|
215
|
+
}
|
|
216
|
+
resolve(revoked);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Obtiene tokens de un usuario
|
|
224
|
+
* @param {number} userId - ID del usuario
|
|
225
|
+
* @returns {Array} - Array de tokens del usuario
|
|
226
|
+
*/
|
|
227
|
+
async getUserTokens(userId) {
|
|
228
|
+
const selectQuery = `
|
|
229
|
+
SELECT * FROM ${this.tableName}
|
|
230
|
+
WHERE user_id = ? AND revoked = 0 AND expires_at > datetime('now')
|
|
231
|
+
`;
|
|
232
|
+
|
|
233
|
+
return new Promise((resolve, reject) => {
|
|
234
|
+
this.db.all(selectQuery, [userId], (err, rows) => {
|
|
235
|
+
if (err) {
|
|
236
|
+
console.error('Error obteniendo tokens de usuario:', err.message);
|
|
237
|
+
reject(err);
|
|
238
|
+
} else {
|
|
239
|
+
resolve(rows || []);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Limpia tokens expirados
|
|
247
|
+
*/
|
|
248
|
+
async cleanupExpiredTokens() {
|
|
249
|
+
const deleteQuery = `
|
|
250
|
+
DELETE FROM ${this.tableName}
|
|
251
|
+
WHERE expires_at <= datetime('now')
|
|
252
|
+
`;
|
|
253
|
+
|
|
254
|
+
return new Promise((resolve, reject) => {
|
|
255
|
+
this.db.run(deleteQuery, function(err) {
|
|
256
|
+
if (err) {
|
|
257
|
+
console.error('Error limpiando tokens expirados:', err.message);
|
|
258
|
+
reject(err);
|
|
259
|
+
} else {
|
|
260
|
+
console.log(`✅ Eliminados ${this.changes} tokens expirados`);
|
|
261
|
+
resolve(this.changes);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Cierra la conexión a la base de datos
|
|
269
|
+
*/
|
|
270
|
+
async close() {
|
|
271
|
+
if (this.db) {
|
|
272
|
+
return new Promise((resolve, reject) => {
|
|
273
|
+
this.db.close((err) => {
|
|
274
|
+
if (err) {
|
|
275
|
+
console.error('Error cerrando conexión a SQLite:', err.message);
|
|
276
|
+
reject(err);
|
|
277
|
+
} else {
|
|
278
|
+
console.log('🔒 Conexión a SQLite cerrada');
|
|
279
|
+
resolve();
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = SQLiteTokenAdapter;
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### 2. Instalar la dependencia de SQLite
|
|
291
|
+
|
|
292
|
+
Para usar SQLite, necesitamos instalar la dependencia:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
npm install sqlite3
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### 3. Crear un ejemplo de uso
|
|
299
|
+
|
|
300
|
+
Ahora crearemos un ejemplo que demuestra cómo usar el adaptador de SQLite:
|
|
301
|
+
|
|
302
|
+
```javascript
|
|
303
|
+
// examples/v2/sqlite_tokens_example.js
|
|
304
|
+
const { JERK, Authenticator, Logger } = require('../../index');
|
|
305
|
+
const jwt = require('jsonwebtoken');
|
|
306
|
+
const SQLiteTokenAdapter = require('../../lib/utils/sqliteTokenAdapter');
|
|
307
|
+
|
|
308
|
+
// Crear instancia del logger
|
|
309
|
+
const logger = new Logger({ level: 'info', timestamp: true });
|
|
310
|
+
|
|
311
|
+
logger.info('🔐 Iniciando ejemplo con tokens en SQLite');
|
|
312
|
+
|
|
313
|
+
// Configuración de conexión a SQLite
|
|
314
|
+
const dbConfig = {
|
|
315
|
+
dbPath: './tokens.sqlite', // Ruta a la base de datos SQLite
|
|
316
|
+
tableName: 'tokens'
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// Crear instancia del adaptador de tokens
|
|
320
|
+
const tokenAdapter = new SQLiteTokenAdapter(dbConfig);
|
|
321
|
+
|
|
322
|
+
// Inicializar la conexión y tabla
|
|
323
|
+
tokenAdapter.initialize()
|
|
324
|
+
.then(async () => {
|
|
325
|
+
logger.info('✅ Conexión a SQLite establecida');
|
|
326
|
+
|
|
327
|
+
// Crear instancia del servidor
|
|
328
|
+
const server = new JERK({
|
|
329
|
+
port: 8083,
|
|
330
|
+
host: 'localhost'
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Crear instancia del autenticador
|
|
334
|
+
const authenticator = new Authenticator({ logger: logger });
|
|
335
|
+
|
|
336
|
+
// Secretos para tokens
|
|
337
|
+
const jwtSecret = 'sqlite-jwt-secret-key';
|
|
338
|
+
const refreshSecret = 'sqlite-refresh-secret-key';
|
|
339
|
+
|
|
340
|
+
// Estrategia de autenticación con tokens en SQLite
|
|
341
|
+
authenticator.use('sqliteJwt', async (req, options = {}) => {
|
|
342
|
+
const authHeader = req.headers.authorization;
|
|
343
|
+
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
|
344
|
+
|
|
345
|
+
if (!token) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
// Primero verificar si el token es válido en la base de datos
|
|
351
|
+
const tokenRecord = await tokenAdapter.validateToken(token);
|
|
352
|
+
|
|
353
|
+
if (!tokenRecord) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Luego verificar si el token JWT es válido
|
|
358
|
+
const decoded = jwt.verify(token, jwtSecret);
|
|
359
|
+
|
|
360
|
+
// Agregar el payload decodificado a la solicitud
|
|
361
|
+
req.user = decoded;
|
|
362
|
+
return true;
|
|
363
|
+
} catch (error) {
|
|
364
|
+
if (error.name === 'TokenExpiredError') {
|
|
365
|
+
// Token expirado, verificar si hay refresh token
|
|
366
|
+
const refreshToken = req.headers['x-refresh-token'];
|
|
367
|
+
if (refreshToken) {
|
|
368
|
+
const refreshRecord = await tokenAdapter.validateToken(refreshToken);
|
|
369
|
+
if (refreshRecord && refreshRecord.token_type === 'refresh') {
|
|
370
|
+
try {
|
|
371
|
+
// Decodificar el refresh token para obtener los datos del usuario
|
|
372
|
+
const refreshDecoded = jwt.verify(refreshToken, refreshSecret);
|
|
373
|
+
|
|
374
|
+
// Generar nuevo token de acceso
|
|
375
|
+
const newAccessToken = jwt.sign(
|
|
376
|
+
{
|
|
377
|
+
userId: refreshDecoded.userId || refreshDecoded.id,
|
|
378
|
+
username: refreshDecoded.username,
|
|
379
|
+
role: refreshDecoded.role,
|
|
380
|
+
tokenType: 'access'
|
|
381
|
+
},
|
|
382
|
+
jwtSecret,
|
|
383
|
+
{ expiresIn: '15m' }
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
// Guardar nuevo token de acceso en la base de datos
|
|
387
|
+
const accessExpiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutos
|
|
388
|
+
await tokenAdapter.saveToken(
|
|
389
|
+
newAccessToken,
|
|
390
|
+
refreshDecoded,
|
|
391
|
+
'access',
|
|
392
|
+
accessExpiresAt
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
// Agregar nuevo token a la respuesta para que el cliente lo actualice
|
|
396
|
+
if (req.res) {
|
|
397
|
+
req.res.setHeader('X-New-Access-Token', newAccessToken);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Agregar el payload decodificado a la solicitud
|
|
401
|
+
req.user = refreshDecoded;
|
|
402
|
+
return true;
|
|
403
|
+
} catch (verifyError) {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// Middleware para adjuntar la respuesta al request para headers
|
|
419
|
+
server.use((req, res, next) => {
|
|
420
|
+
req.res = res;
|
|
421
|
+
next();
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Simulación de base de datos de usuarios
|
|
425
|
+
const users = [
|
|
426
|
+
{ id: 1, username: 'sqlite_admin', password: 'password', role: 'admin' },
|
|
427
|
+
{ id: 2, username: 'sqlite_user', password: 'password', role: 'user' }
|
|
428
|
+
];
|
|
429
|
+
|
|
430
|
+
// Ruta de login para generar tokens en SQLite
|
|
431
|
+
server.addRoute('POST', '/api/login', async (req, res) => {
|
|
432
|
+
try {
|
|
433
|
+
const { username, password } = req.body;
|
|
434
|
+
|
|
435
|
+
// Validar credenciales
|
|
436
|
+
const user = users.find(u => u.username === username && u.password === password);
|
|
437
|
+
|
|
438
|
+
if (!user) {
|
|
439
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
440
|
+
res.end(JSON.stringify({
|
|
441
|
+
success: false,
|
|
442
|
+
message: 'Credenciales inválidas'
|
|
443
|
+
}));
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Generar tokens JWT
|
|
448
|
+
const accessToken = jwt.sign(
|
|
449
|
+
{
|
|
450
|
+
userId: user.id,
|
|
451
|
+
username: user.username,
|
|
452
|
+
role: user.role,
|
|
453
|
+
tokenType: 'access'
|
|
454
|
+
},
|
|
455
|
+
jwtSecret,
|
|
456
|
+
{ expiresIn: '15m' }
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
const refreshToken = jwt.sign(
|
|
460
|
+
{
|
|
461
|
+
userId: user.id,
|
|
462
|
+
username: user.username,
|
|
463
|
+
role: user.role,
|
|
464
|
+
tokenType: 'refresh'
|
|
465
|
+
},
|
|
466
|
+
refreshSecret,
|
|
467
|
+
{ expiresIn: '7d' }
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
// Guardar tokens en la base de datos SQLite
|
|
471
|
+
const accessExpiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutos
|
|
472
|
+
const refreshExpiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 días
|
|
473
|
+
|
|
474
|
+
await tokenAdapter.saveToken(accessToken, user, 'access', accessExpiresAt);
|
|
475
|
+
await tokenAdapter.saveToken(refreshToken, user, 'refresh', refreshExpiresAt);
|
|
476
|
+
|
|
477
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
478
|
+
res.end(JSON.stringify({
|
|
479
|
+
success: true,
|
|
480
|
+
message: 'Login exitoso',
|
|
481
|
+
tokens: {
|
|
482
|
+
accessToken,
|
|
483
|
+
refreshToken
|
|
484
|
+
},
|
|
485
|
+
user: {
|
|
486
|
+
id: user.id,
|
|
487
|
+
username: user.username,
|
|
488
|
+
role: user.role
|
|
489
|
+
}
|
|
490
|
+
}));
|
|
491
|
+
} catch (error) {
|
|
492
|
+
logger.error('Error en login:', error.message);
|
|
493
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
494
|
+
res.end(JSON.stringify({
|
|
495
|
+
success: false,
|
|
496
|
+
message: 'Error en el proceso de login'
|
|
497
|
+
}));
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Ruta para renovar tokens
|
|
502
|
+
server.addRoute('POST', '/api/refresh', async (req, res) => {
|
|
503
|
+
try {
|
|
504
|
+
const refreshToken = req.headers['x-refresh-token'];
|
|
505
|
+
|
|
506
|
+
if (!refreshToken) {
|
|
507
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
508
|
+
res.end(JSON.stringify({
|
|
509
|
+
success: false,
|
|
510
|
+
message: 'Refresh token requerido'
|
|
511
|
+
}));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Verificar si el refresh token es válido en la base de datos
|
|
516
|
+
const tokenRecord = await tokenAdapter.validateToken(refreshToken);
|
|
517
|
+
|
|
518
|
+
if (!tokenRecord || tokenRecord.token_type !== 'refresh') {
|
|
519
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
520
|
+
res.end(JSON.stringify({
|
|
521
|
+
success: false,
|
|
522
|
+
message: 'Refresh token inválido o expirado'
|
|
523
|
+
}));
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
try {
|
|
528
|
+
// Decodificar el refresh token para obtener los datos del usuario
|
|
529
|
+
const decoded = jwt.verify(refreshToken, refreshSecret);
|
|
530
|
+
|
|
531
|
+
// Generar nuevo token de acceso
|
|
532
|
+
const newAccessToken = jwt.sign(
|
|
533
|
+
{
|
|
534
|
+
userId: decoded.userId || decoded.id,
|
|
535
|
+
username: decoded.username,
|
|
536
|
+
role: decoded.role,
|
|
537
|
+
tokenType: 'access'
|
|
538
|
+
},
|
|
539
|
+
jwtSecret,
|
|
540
|
+
{ expiresIn: '15m' }
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
// Guardar nuevo token de acceso en la base de datos
|
|
544
|
+
const accessExpiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutos
|
|
545
|
+
await tokenAdapter.saveToken(newAccessToken, decoded, 'access', accessExpiresAt);
|
|
546
|
+
|
|
547
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
548
|
+
res.end(JSON.stringify({
|
|
549
|
+
success: true,
|
|
550
|
+
message: 'Tokens renovados exitosamente',
|
|
551
|
+
tokens: {
|
|
552
|
+
accessToken: newAccessToken,
|
|
553
|
+
refreshToken: refreshToken // Devolver el mismo refresh token
|
|
554
|
+
}
|
|
555
|
+
}));
|
|
556
|
+
} catch (verifyError) {
|
|
557
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
558
|
+
res.end(JSON.stringify({
|
|
559
|
+
success: false,
|
|
560
|
+
message: 'Refresh token inválido'
|
|
561
|
+
}));
|
|
562
|
+
}
|
|
563
|
+
} catch (error) {
|
|
564
|
+
logger.error('Error al renovar token:', error.message);
|
|
565
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
566
|
+
res.end(JSON.stringify({
|
|
567
|
+
success: false,
|
|
568
|
+
message: 'Error al renovar token'
|
|
569
|
+
}));
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// Ruta protegida con tokens de SQLite
|
|
574
|
+
server.addRoute('GET', '/api/data', authenticator.authenticate('sqliteJwt'), (req, res) => {
|
|
575
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
576
|
+
res.end(JSON.stringify({
|
|
577
|
+
success: true,
|
|
578
|
+
message: 'Datos protegidos accesados con token de SQLite',
|
|
579
|
+
user: req.user,
|
|
580
|
+
data: {
|
|
581
|
+
id: 1,
|
|
582
|
+
title: 'Datos de SQLite',
|
|
583
|
+
content: 'Este contenido está protegido por tokens almacenados en SQLite',
|
|
584
|
+
timestamp: new Date().toISOString()
|
|
585
|
+
}
|
|
586
|
+
}));
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
// Ruta de perfil protegida
|
|
590
|
+
server.addRoute('GET', '/api/profile', authenticator.authenticate('sqliteJwt'), (req, res) => {
|
|
591
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
592
|
+
res.end(JSON.stringify({
|
|
593
|
+
success: true,
|
|
594
|
+
message: 'Perfil obtenido con token de SQLite',
|
|
595
|
+
user: req.user
|
|
596
|
+
}));
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// Middleware de logging
|
|
600
|
+
server.use((req, res, next) => {
|
|
601
|
+
logger.info(`${req.method} ${req.url} - IP: ${req.connection.remoteAddress}`);
|
|
602
|
+
next();
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
logger.info('✅ Rutas configuradas con tokens en SQLite');
|
|
606
|
+
|
|
607
|
+
// Iniciar el servidor
|
|
608
|
+
const httpServer = server.start();
|
|
609
|
+
|
|
610
|
+
logger.info('✅ Servidor iniciado en http://localhost:8083');
|
|
611
|
+
logger.info('📋 Endpoints disponibles:');
|
|
612
|
+
logger.info(' POST /api/login - Login para tokens en SQLite');
|
|
613
|
+
logger.info(' POST /api/refresh - Renovar tokens');
|
|
614
|
+
logger.info(' GET /api/data - Datos (JWT req)');
|
|
615
|
+
logger.info(' GET /api/profile - Perfil (JWT req)');
|
|
616
|
+
|
|
617
|
+
logger.info('\n🔧 Comandos de prueba con curl:');
|
|
618
|
+
logger.info(' # Login para obtener tokens en SQLite:');
|
|
619
|
+
logger.info(' curl -X POST http://localhost:8083/api/login \\');
|
|
620
|
+
logger.info(' -H "Content-Type: application/json" \\');
|
|
621
|
+
logger.info(' -d \'{"username":"sqlite_admin", "password":"password"}\'');
|
|
622
|
+
logger.info('');
|
|
623
|
+
logger.info(' # Acceder a datos protegidos (reemplaza con tu JWT):');
|
|
624
|
+
logger.info(' curl -H "Authorization: Bearer TU_JWT_TOKEN_AQUI" http://localhost:8083/api/data');
|
|
625
|
+
logger.info('');
|
|
626
|
+
logger.info(' # Renovar tokens (reemplaza con tu refresh token):');
|
|
627
|
+
logger.info(' curl -X POST http://localhost:8083/api/refresh \\');
|
|
628
|
+
logger.info(' -H "X-Refresh-Token: TU_REFRESH_TOKEN_AQUI"');
|
|
629
|
+
|
|
630
|
+
// Manejo de cierre
|
|
631
|
+
const gracefulShutdown = async () => {
|
|
632
|
+
logger.info('🛑 Cerrando servidor...');
|
|
633
|
+
httpServer.close(() => {
|
|
634
|
+
logger.info('🔌 Servidor detenido');
|
|
635
|
+
|
|
636
|
+
// Cerrar conexión a SQLite
|
|
637
|
+
tokenAdapter.close()
|
|
638
|
+
.then(() => {
|
|
639
|
+
logger.info('🔒 Conexión a SQLite cerrada');
|
|
640
|
+
process.exit(0);
|
|
641
|
+
})
|
|
642
|
+
.catch(err => {
|
|
643
|
+
console.error('Error cerrando conexión a SQLite:', err.message);
|
|
644
|
+
process.exit(1);
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
650
|
+
process.on('SIGINT', gracefulShutdown);
|
|
651
|
+
|
|
652
|
+
})
|
|
653
|
+
.catch(error => {
|
|
654
|
+
logger.error('❌ Error inicializando adaptador de tokens:', error.message);
|
|
655
|
+
process.exit(1);
|
|
656
|
+
});
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
### 4. Actualizar el package.json
|
|
660
|
+
|
|
661
|
+
Agregamos la dependencia de SQLite al package.json:
|
|
662
|
+
|
|
663
|
+
```json
|
|
664
|
+
{
|
|
665
|
+
"name": "jerk",
|
|
666
|
+
"version": "1.0.0",
|
|
667
|
+
"description": "Framework para agilizar la creación de APIs",
|
|
668
|
+
"main": "index.js",
|
|
669
|
+
"scripts": {
|
|
670
|
+
"start": "node examples/basic/server.js",
|
|
671
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
672
|
+
"build": "mkdir -p dist && cp -r lib index.js package.json README.md examples dist/",
|
|
673
|
+
"example:v2:security": "node examples/v2/advanced_security_example.js",
|
|
674
|
+
"example:v2:nested": "node examples/v2/nested_routes_example.js",
|
|
675
|
+
"example:v2:full": "node examples/v2/full_features_example.js",
|
|
676
|
+
"example:v2:auth": "node examples/v2/advanced_auth_example.js",
|
|
677
|
+
"example:v2:routes": "node examples/v2/routes_json_example.js",
|
|
678
|
+
"example:v2:sqlite": "node examples/v2/sqlite_tokens_example.js"
|
|
679
|
+
},
|
|
680
|
+
"keywords": [
|
|
681
|
+
"api",
|
|
682
|
+
"framework",
|
|
683
|
+
"server",
|
|
684
|
+
"routing"
|
|
685
|
+
],
|
|
686
|
+
"author": "",
|
|
687
|
+
"license": "MIT",
|
|
688
|
+
"dependencies": {
|
|
689
|
+
"jsonwebtoken": "^9.0.0",
|
|
690
|
+
"sqlite3": "^5.1.6"
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
### 5. Actualizar el index.js para exportar el nuevo adaptador
|
|
696
|
+
|
|
697
|
+
```javascript
|
|
698
|
+
// Actualizar index.js para incluir el adaptador de SQLite
|
|
699
|
+
const JERK = require('./lib/core/server');
|
|
700
|
+
const Router = require('./lib/core/router');
|
|
701
|
+
const HandlerManager = require('./lib/core/handler');
|
|
702
|
+
const Authenticator = require('./lib/middleware/authenticator');
|
|
703
|
+
const Validator = require('./lib/middleware/validator');
|
|
704
|
+
const RouteLoader = require('./lib/loader/routeLoader');
|
|
705
|
+
const ControllerLoader = require('./lib/loader/controllerLoader');
|
|
706
|
+
const ConfigParser = require('./lib/utils/configParser');
|
|
707
|
+
const { Logger } = require('./lib/utils/logger');
|
|
708
|
+
|
|
709
|
+
// Componentes adicionales de la versión 2.0
|
|
710
|
+
const Cors = require('./lib/middleware/cors');
|
|
711
|
+
const RateLimiter = require('./lib/middleware/rateLimiter');
|
|
712
|
+
const Compressor = require('./lib/middleware/compressor');
|
|
713
|
+
const TokenManager = require('./lib/utils/tokenManager');
|
|
714
|
+
const MariaDBTokenAdapter = require('./lib/utils/mariadbTokenAdapter');
|
|
715
|
+
const SQLiteTokenAdapter = require('./lib/utils/sqliteTokenAdapter'); // Nuevo adaptador
|
|
716
|
+
|
|
717
|
+
module.exports = {
|
|
718
|
+
// Componentes fundamentales (v1.0)
|
|
719
|
+
JERK,
|
|
720
|
+
Router,
|
|
721
|
+
HandlerManager,
|
|
722
|
+
Authenticator,
|
|
723
|
+
Validator,
|
|
724
|
+
RouteLoader,
|
|
725
|
+
ControllerLoader,
|
|
726
|
+
ConfigParser,
|
|
727
|
+
Logger,
|
|
728
|
+
|
|
729
|
+
// Componentes de seguridad y rendimiento (v2.0)
|
|
730
|
+
Cors,
|
|
731
|
+
RateLimiter,
|
|
732
|
+
Compressor,
|
|
733
|
+
|
|
734
|
+
// Componentes de utilidad (v2.0)
|
|
735
|
+
TokenManager,
|
|
736
|
+
MariaDBTokenAdapter,
|
|
737
|
+
SQLiteTokenAdapter // Exportar el nuevo adaptador
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
// También exportar clases individuales por conveniencia
|
|
741
|
+
module.exports.JERK = APIServer;
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
## Guía de Implementación
|
|
745
|
+
|
|
746
|
+
### Paso 1: Analizar los Requisitos
|
|
747
|
+
|
|
748
|
+
Antes de crear cualquier extensión, es importante:
|
|
749
|
+
|
|
750
|
+
1. **Definir el propósito**: ¿Qué problema resolverá la extensión?
|
|
751
|
+
2. **Identificar los puntos de integración**: ¿Dónde se integrará con el framework?
|
|
752
|
+
3. **Definir la interfaz**: ¿Qué métodos/propiedades debe implementar?
|
|
753
|
+
4. **Considerar la compatibilidad**: ¿Cómo se integrará con versiones existentes?
|
|
754
|
+
|
|
755
|
+
### Paso 2: Implementar el Patrón de Adaptador
|
|
756
|
+
|
|
757
|
+
El patrón de adaptador es clave para la extensibilidad:
|
|
758
|
+
|
|
759
|
+
```javascript
|
|
760
|
+
// Plantilla para nuevos adaptadores
|
|
761
|
+
class NuevoAdaptador {
|
|
762
|
+
constructor(config) {
|
|
763
|
+
// Inicializar con configuración
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
async initialize() {
|
|
767
|
+
// Lógica de inicialización
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Métodos comunes que deben implementarse
|
|
771
|
+
async save(data) { /* ... */ }
|
|
772
|
+
async get(id) { /* ... */ }
|
|
773
|
+
async validate(data) { /* ... */ }
|
|
774
|
+
async close() { /* ... */ }
|
|
775
|
+
}
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
### Paso 3: Asegurar la Consistencia de la API
|
|
779
|
+
|
|
780
|
+
Mantener una API consistente con otros componentes del framework:
|
|
781
|
+
|
|
782
|
+
- Usar los mismos patrones de nomenclatura
|
|
783
|
+
- Mantener la misma estructura de callbacks/promesas
|
|
784
|
+
- Seguir las mismas convenciones de manejo de errores
|
|
785
|
+
- Proporcionar mensajes de log consistentes
|
|
786
|
+
|
|
787
|
+
### Paso 4: Implementar la Lógica de Negocio
|
|
788
|
+
|
|
789
|
+
Implementar la funcionalidad específica del nuevo sistema:
|
|
790
|
+
|
|
791
|
+
- Conexión y desconexión
|
|
792
|
+
- Operaciones CRUD básicas
|
|
793
|
+
- Manejo de errores específicos del sistema
|
|
794
|
+
- Optimizaciones de rendimiento
|
|
795
|
+
|
|
796
|
+
### Paso 5: Probar la Integración
|
|
797
|
+
|
|
798
|
+
Verificar que la extensión funcione correctamente con el framework:
|
|
799
|
+
|
|
800
|
+
- Pruebas unitarias de los métodos del adaptador
|
|
801
|
+
- Pruebas de integración con el sistema de autenticación
|
|
802
|
+
- Pruebas de rendimiento y seguridad
|
|
803
|
+
- Pruebas de compatibilidad con diferentes versiones
|
|
804
|
+
|
|
805
|
+
## Pruebas y Validación
|
|
806
|
+
|
|
807
|
+
### Pruebas Unitarias
|
|
808
|
+
|
|
809
|
+
```javascript
|
|
810
|
+
// test_sqlite_adapter.js
|
|
811
|
+
const assert = require('assert');
|
|
812
|
+
const SQLiteTokenAdapter = require('./lib/utils/sqliteTokenAdapter');
|
|
813
|
+
|
|
814
|
+
describe('SQLiteTokenAdapter', () => {
|
|
815
|
+
let adapter;
|
|
816
|
+
|
|
817
|
+
beforeEach(async () => {
|
|
818
|
+
adapter = new SQLiteTokenAdapter({ dbPath: ':memory:' }); // Usar memoria para pruebas
|
|
819
|
+
await adapter.initialize();
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
afterEach(async () => {
|
|
823
|
+
await adapter.close();
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
it('debería guardar y validar tokens correctamente', async () => {
|
|
827
|
+
const token = 'test-token';
|
|
828
|
+
const userData = { userId: 1, username: 'testuser' };
|
|
829
|
+
const expiresAt = new Date(Date.now() + 3600000); // 1 hora
|
|
830
|
+
|
|
831
|
+
await adapter.saveToken(token, userData, 'access', expiresAt);
|
|
832
|
+
const result = await adapter.validateToken(token);
|
|
833
|
+
|
|
834
|
+
assert.ok(result);
|
|
835
|
+
assert.equal(result.user_id, 1);
|
|
836
|
+
assert.equal(result.token_type, 'access');
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
it('debería revocar tokens correctamente', async () => {
|
|
840
|
+
const token = 'test-token-revoke';
|
|
841
|
+
const userData = { userId: 1, username: 'testuser' };
|
|
842
|
+
const expiresAt = new Date(Date.now() + 3600000);
|
|
843
|
+
|
|
844
|
+
await adapter.saveToken(token, userData, 'access', expiresAt);
|
|
845
|
+
const revoked = await adapter.revokeToken(token);
|
|
846
|
+
|
|
847
|
+
assert.ok(revoked);
|
|
848
|
+
|
|
849
|
+
const validated = await adapter.validateToken(token);
|
|
850
|
+
assert.ok(!validated);
|
|
851
|
+
});
|
|
852
|
+
});
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
### Pruebas de Integración
|
|
856
|
+
|
|
857
|
+
```javascript
|
|
858
|
+
// test_integration.js
|
|
859
|
+
const { JERK, Authenticator } = require('./index');
|
|
860
|
+
const SQLiteTokenAdapter = require('./lib/utils/sqliteTokenAdapter');
|
|
861
|
+
|
|
862
|
+
async function testIntegration() {
|
|
863
|
+
const adapter = new SQLiteTokenAdapter({ dbPath: './integration_test.sqlite' });
|
|
864
|
+
await adapter.initialize();
|
|
865
|
+
|
|
866
|
+
const server = new JERK({ port: 9999 });
|
|
867
|
+
const authenticator = new Authenticator();
|
|
868
|
+
|
|
869
|
+
// Registrar estrategia con SQLite
|
|
870
|
+
authenticator.use('sqliteAuth', async (req) => {
|
|
871
|
+
// Implementación de autenticación con SQLite
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
// Agregar rutas y probar la funcionalidad
|
|
875
|
+
// ...
|
|
876
|
+
}
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
## Integración con el Framework
|
|
880
|
+
|
|
881
|
+
### 1. Registro de Componentes
|
|
882
|
+
|
|
883
|
+
Asegurar que la extensión esté disponible a través del punto de entrada:
|
|
884
|
+
|
|
885
|
+
```javascript
|
|
886
|
+
// index.js - Ya implementado arriba
|
|
887
|
+
module.exports.SQLiteTokenAdapter = require('./lib/utils/sqliteTokenAdapter');
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
### 2. Documentación de la API
|
|
891
|
+
|
|
892
|
+
Proporcionar documentación clara sobre cómo usar la extensión:
|
|
893
|
+
|
|
894
|
+
```javascript
|
|
895
|
+
/**
|
|
896
|
+
* Uso del adaptador de SQLite
|
|
897
|
+
*/
|
|
898
|
+
const { JERK, Authenticator, SQLiteTokenAdapter } = require('jerk');
|
|
899
|
+
|
|
900
|
+
const adapter = new SQLiteTokenAdapter({
|
|
901
|
+
dbPath: './tokens.sqlite',
|
|
902
|
+
tableName: 'tokens'
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
await adapter.initialize();
|
|
906
|
+
|
|
907
|
+
// Usar en autenticación
|
|
908
|
+
authenticator.use('sqliteJwt', (req) => {
|
|
909
|
+
// Lógica de autenticación con SQLite
|
|
910
|
+
});
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
### 3. Ejemplos de Uso
|
|
914
|
+
|
|
915
|
+
Proporcionar ejemplos completos que demuestren el uso de la extensión.
|
|
916
|
+
|
|
917
|
+
## Documentación y Distribución
|
|
918
|
+
|
|
919
|
+
### 1. Documentación del API
|
|
920
|
+
|
|
921
|
+
Crear documentación detallada de todos los métodos y opciones disponibles.
|
|
922
|
+
|
|
923
|
+
### 2. Guía de Instalación
|
|
924
|
+
|
|
925
|
+
Explicar cómo instalar y configurar la extensión.
|
|
926
|
+
|
|
927
|
+
### 3. Ejemplos Prácticos
|
|
928
|
+
|
|
929
|
+
Mostrar casos de uso reales y escenarios comunes.
|
|
930
|
+
|
|
931
|
+
### 4. Pruebas y Validación
|
|
932
|
+
|
|
933
|
+
Incluir pruebas que demuestren la funcionalidad y rendimiento.
|
|
934
|
+
|
|
935
|
+
## Consideraciones de Seguridad
|
|
936
|
+
|
|
937
|
+
Al crear extensiones, especialmente para almacenamiento de datos sensibles como tokens:
|
|
938
|
+
|
|
939
|
+
1. **Validación de Entrada**: Validar todos los datos antes de almacenarlos
|
|
940
|
+
2. **Escapado de Consultas**: Usar consultas preparadas para prevenir inyección SQL
|
|
941
|
+
3. **Cifrado**: Considerar cifrar datos sensibles si es necesario
|
|
942
|
+
4. **Auditoría**: Registrar operaciones importantes para fines de auditoría
|
|
943
|
+
5. **Límites**: Implementar límites para prevenir abusos
|
|
944
|
+
|
|
945
|
+
## Buenas Prácticas
|
|
946
|
+
|
|
947
|
+
1. **Seguir Convenciones**: Mantener consistencia con el resto del framework
|
|
948
|
+
2. **Manejo de Errores**: Proporcionar mensajes de error claros y útiles
|
|
949
|
+
3. **Rendimiento**: Optimizar operaciones para minimizar impacto en el rendimiento
|
|
950
|
+
4. **Documentación**: Documentar claramente la API y casos de uso
|
|
951
|
+
5. **Pruebas**: Incluir pruebas unitarias e integración
|
|
952
|
+
6. **Compatibilidad**: Mantener compatibilidad hacia atrás cuando sea posible
|
|
953
|
+
|
|
954
|
+
## Conclusión
|
|
955
|
+
|
|
956
|
+
La extensibilidad es una característica poderosa del API SDK Framework que permite adaptarlo a necesidades específicas. Al seguir los patrones y prácticas descritos en este manual, puedes crear extensiones robustas, seguras y fáciles de mantener que se integran perfectamente con el framework existente.
|
|
957
|
+
|
|
958
|
+
El ejemplo de SQLite demuestra cómo crear una extensión completa que sigue todos los principios de diseño del framework, manteniendo la consistencia de la API y proporcionando funcionalidad adicional de manera segura y eficiente.
|