jerkjs 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/LICENSE +200 -0
  2. package/README.md +171 -0
  3. package/doc/EXTENSION_MANUAL.md +958 -0
  4. package/doc/FIREWALL_MANUAL.md +419 -0
  5. package/doc/HOOKS_REFERENCE_IMPROVED.md +599 -0
  6. package/doc/MANUAL_API_SDK.md +539 -0
  7. package/doc/MANUAL_MVC.md +397 -0
  8. package/doc/MARIADB_TOKENS_IMPLEMENTATION.md +113 -0
  9. package/doc/MIDDLEWARE_MANUAL.md +521 -0
  10. package/doc/OAUTH2_GOOGLE_MANUAL.md +408 -0
  11. package/doc/frontend-and-sessions.md +356 -0
  12. package/examples/advanced/controllers/productController.js +64 -0
  13. package/examples/advanced/controllers/userController.js +85 -0
  14. package/examples/advanced/routes.json +51 -0
  15. package/examples/advanced_example.js +93 -0
  16. package/examples/basic/controllers/userController.js +85 -0
  17. package/examples/basic_example.js +72 -0
  18. package/examples/frontend/README.md +71 -0
  19. package/examples/frontend/app.js +71 -0
  20. package/examples/frontend/controllers/apiController.js +39 -0
  21. package/examples/frontend/controllers/authController.js +220 -0
  22. package/examples/frontend/controllers/formController.js +47 -0
  23. package/examples/frontend/controllers/messageController.js +96 -0
  24. package/examples/frontend/controllers/pageController.js +178 -0
  25. package/examples/frontend/controllers/staticController.js +167 -0
  26. package/examples/frontend/routes.json +90 -0
  27. package/examples/mvc_example/app.js +138 -0
  28. package/examples/mvc_example/views/home/index.html +26 -0
  29. package/examples/mvc_example/views/home/simple.html +3 -0
  30. package/examples/mvc_example/views/layout.html +23 -0
  31. package/examples/mvc_example/views/test.html +3 -0
  32. package/examples/mvc_example/views/user/invalid.html +6 -0
  33. package/examples/mvc_example/views/user/list.html +36 -0
  34. package/examples/mvc_example/views/user/notfound.html +6 -0
  35. package/examples/mvc_example/views/user/profile.html +11 -0
  36. package/examples/mvc_routes_example/app.js +34 -0
  37. package/examples/mvc_routes_example/controllers/mainController.js +27 -0
  38. package/examples/mvc_routes_example/controllers/productController.js +47 -0
  39. package/examples/mvc_routes_example/controllers/userController.js +76 -0
  40. package/examples/mvc_routes_example/routes.json +30 -0
  41. package/examples/mvc_routes_example/views/layout.html +31 -0
  42. package/examples/mvc_routes_example/views/main/index.html +11 -0
  43. package/examples/mvc_routes_example/views/product/catalog.html +24 -0
  44. package/examples/mvc_routes_example/views/user/invalid.html +6 -0
  45. package/examples/mvc_routes_example/views/user/list.html +40 -0
  46. package/examples/mvc_routes_example/views/user/notfound.html +6 -0
  47. package/examples/mvc_routes_example/views/user/profile.html +18 -0
  48. package/examples/public/README.md +92 -0
  49. package/examples/public/app.js +72 -0
  50. package/examples/public/controllers/healthController.js +20 -0
  51. package/examples/public/controllers/mainController.js +22 -0
  52. package/examples/public/controllers/userController.js +139 -0
  53. package/examples/public/routes.json +51 -0
  54. package/examples/v2/README.md +72 -0
  55. package/examples/v2/app.js +74 -0
  56. package/examples/v2/app_fixed.js +74 -0
  57. package/examples/v2/controllers/authController.js +64 -0
  58. package/examples/v2/controllers/mainController.js +24 -0
  59. package/examples/v2/controllers/protectedController.js +12 -0
  60. package/examples/v2/controllers/userController.js +16 -0
  61. package/examples/v2/package.json +27 -0
  62. package/examples/v2/routes.json +30 -0
  63. package/examples/v2/test_api.sh +47 -0
  64. package/examples/v2/tokens_example.sqlite +0 -0
  65. package/examples/v2.1_firewall_demo/README.md +113 -0
  66. package/examples/v2.1_firewall_demo/app.js +182 -0
  67. package/examples/v2.1_firewall_demo/package.json +27 -0
  68. package/examples/v2.1_hooks_demo/README.md +85 -0
  69. package/examples/v2.1_hooks_demo/app.js +101 -0
  70. package/examples/v2.1_hooks_demo/controllers/hooksController.js +29 -0
  71. package/examples/v2.1_hooks_demo/controllers/mainController.js +18 -0
  72. package/examples/v2.1_hooks_demo/package.json +27 -0
  73. package/examples/v2.1_hooks_demo/routes.json +16 -0
  74. package/examples/v2.1_openapi_demo/README.md +82 -0
  75. package/examples/v2.1_openapi_demo/app.js +296 -0
  76. package/examples/v2.1_openapi_demo/package.json +26 -0
  77. package/examples/v2_cors/README.md +82 -0
  78. package/examples/v2_cors/app.js +108 -0
  79. package/examples/v2_cors/package.json +23 -0
  80. package/examples/v2_json_auth/README.md +83 -0
  81. package/examples/v2_json_auth/app.js +72 -0
  82. package/examples/v2_json_auth/controllers/authController.js +67 -0
  83. package/examples/v2_json_auth/controllers/mainController.js +16 -0
  84. package/examples/v2_json_auth/controllers/protectedController.js +12 -0
  85. package/examples/v2_json_auth/controllers/tokenController.js +28 -0
  86. package/examples/v2_json_auth/controllers/userController.js +15 -0
  87. package/examples/v2_json_auth/package.json +26 -0
  88. package/examples/v2_json_auth/routes.json +37 -0
  89. package/examples/v2_json_auth/tokens.json +20 -0
  90. package/examples/v2_mariadb_auth/README.md +94 -0
  91. package/examples/v2_mariadb_auth/app.js +81 -0
  92. package/examples/v2_mariadb_auth/controllers/authController.js +95 -0
  93. package/examples/v2_mariadb_auth/controllers/mainController.js +31 -0
  94. package/examples/v2_mariadb_auth/controllers/protectedController.js +12 -0
  95. package/examples/v2_mariadb_auth/controllers/userController.js +17 -0
  96. package/examples/v2_mariadb_auth/package.json +27 -0
  97. package/examples/v2_mariadb_auth/routes.json +37 -0
  98. package/examples/v2_no_auth/README.md +75 -0
  99. package/examples/v2_no_auth/app.js +72 -0
  100. package/examples/v2_no_auth/controllers/healthController.js +14 -0
  101. package/examples/v2_no_auth/controllers/mainController.js +19 -0
  102. package/examples/v2_no_auth/controllers/productController.js +31 -0
  103. package/examples/v2_no_auth/controllers/publicController.js +16 -0
  104. package/examples/v2_no_auth/package.json +22 -0
  105. package/examples/v2_no_auth/routes.json +37 -0
  106. package/examples/v2_oauth/README.md +70 -0
  107. package/examples/v2_oauth/app.js +90 -0
  108. package/examples/v2_oauth/controllers/mainController.js +45 -0
  109. package/examples/v2_oauth/controllers/oauthController.js +247 -0
  110. package/examples/v2_oauth/controllers/protectedController.js +13 -0
  111. package/examples/v2_oauth/controllers/userController.js +17 -0
  112. package/examples/v2_oauth/package.json +26 -0
  113. package/examples/v2_oauth/routes.json +44 -0
  114. package/examples/v2_openapi/README.md +77 -0
  115. package/examples/v2_openapi/app.js +222 -0
  116. package/examples/v2_openapi/controllers/authController.js +52 -0
  117. package/examples/v2_openapi/controllers/mainController.js +26 -0
  118. package/examples/v2_openapi/controllers/productController.js +17 -0
  119. package/examples/v2_openapi/controllers/userController.js +27 -0
  120. package/examples/v2_openapi/package.json +26 -0
  121. package/examples/v2_openapi/routes.json +37 -0
  122. package/generate_token.js +10 -0
  123. package/index.js +85 -0
  124. package/jerk.jpg +0 -0
  125. package/lib/core/handler.js +86 -0
  126. package/lib/core/hooks.js +224 -0
  127. package/lib/core/router.js +204 -0
  128. package/lib/core/securityEnhancedServer.js +752 -0
  129. package/lib/core/server.js +369 -0
  130. package/lib/loader/controllerLoader.js +175 -0
  131. package/lib/loader/routeLoader.js +341 -0
  132. package/lib/middleware/auditLogger.js +208 -0
  133. package/lib/middleware/authenticator.js +565 -0
  134. package/lib/middleware/compressor.js +218 -0
  135. package/lib/middleware/cors.js +135 -0
  136. package/lib/middleware/firewall.js +443 -0
  137. package/lib/middleware/rateLimiter.js +210 -0
  138. package/lib/middleware/session.js +301 -0
  139. package/lib/middleware/validator.js +193 -0
  140. package/lib/mvc/controllerBase.js +207 -0
  141. package/lib/mvc/viewEngine.js +752 -0
  142. package/lib/utils/configParser.js +223 -0
  143. package/lib/utils/logger.js +145 -0
  144. package/lib/utils/mariadbTokenAdapter.js +226 -0
  145. package/lib/utils/openapiGenerator.js +140 -0
  146. package/lib/utils/sqliteTokenAdapter.js +224 -0
  147. package/lib/utils/tokenManager.js +254 -0
  148. package/package.json +47 -0
  149. package/v2examplle/v2_json_auth/README.md +83 -0
  150. package/v2examplle/v2_json_auth/app.js +72 -0
  151. package/v2examplle/v2_json_auth/controllers/authController.js +67 -0
  152. package/v2examplle/v2_json_auth/controllers/mainController.js +16 -0
  153. package/v2examplle/v2_json_auth/controllers/protectedController.js +12 -0
  154. package/v2examplle/v2_json_auth/controllers/tokenController.js +28 -0
  155. package/v2examplle/v2_json_auth/controllers/userController.js +15 -0
  156. package/v2examplle/v2_json_auth/package.json +26 -0
  157. package/v2examplle/v2_json_auth/routes.json +37 -0
  158. package/v2examplle/v2_json_auth/tokens.json +20 -0
  159. package/v2examplle/v2_mariadb_auth/README.md +94 -0
  160. package/v2examplle/v2_mariadb_auth/app.js +81 -0
  161. package/v2examplle/v2_mariadb_auth/controllers/authController.js +95 -0
  162. package/v2examplle/v2_mariadb_auth/controllers/mainController.js +31 -0
  163. package/v2examplle/v2_mariadb_auth/controllers/protectedController.js +12 -0
  164. package/v2examplle/v2_mariadb_auth/controllers/userController.js +17 -0
  165. package/v2examplle/v2_mariadb_auth/package.json +27 -0
  166. package/v2examplle/v2_mariadb_auth/routes.json +37 -0
  167. package/v2examplle/v2_sqlite_auth/README.md +72 -0
  168. package/v2examplle/v2_sqlite_auth/app.js +74 -0
  169. package/v2examplle/v2_sqlite_auth/app_fixed.js +74 -0
  170. package/v2examplle/v2_sqlite_auth/controllers/authController.js +64 -0
  171. package/v2examplle/v2_sqlite_auth/controllers/mainController.js +24 -0
  172. package/v2examplle/v2_sqlite_auth/controllers/protectedController.js +12 -0
  173. package/v2examplle/v2_sqlite_auth/controllers/userController.js +16 -0
  174. package/v2examplle/v2_sqlite_auth/package.json +27 -0
  175. package/v2examplle/v2_sqlite_auth/routes.json +30 -0
  176. package/v2examplle/v2_sqlite_auth/test_api.sh +47 -0
  177. package/v2examplle/v2_sqlite_auth/tokens_example.sqlite +0 -0
@@ -0,0 +1,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.