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,140 @@
1
+ /**
2
+ * Generador de documentación OpenAPI para el framework API SDK
3
+ * Componente: lib/utils/openapiGenerator.js
4
+ */
5
+
6
+ class OpenApiGenerator {
7
+ /**
8
+ * Constructor del generador de OpenAPI
9
+ * @param {Object} options - Opciones de configuración
10
+ * @param {string} options.title - Título de la API
11
+ * @param {string} options.description - Descripción de la API
12
+ * @param {string} options.version - Versión de la API
13
+ * @param {Array} options.servers - Servidores donde se aloja la API
14
+ */
15
+ constructor(options = {}) {
16
+ this.spec = {
17
+ openapi: '3.0.0',
18
+ info: {
19
+ title: options.title || 'API Documentation',
20
+ description: options.description || 'Generated by API SDK Framework',
21
+ version: options.version || '1.0.0'
22
+ },
23
+ servers: options.servers || [{ url: 'http://localhost:3000' }],
24
+ paths: {},
25
+ components: {
26
+ schemas: {},
27
+ securitySchemes: {}
28
+ }
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Agrega una ruta a la documentación OpenAPI
34
+ * @param {Object} route - Definición de la ruta
35
+ * @param {string} route.path - Ruta del endpoint
36
+ * @param {string} route.method - Método HTTP
37
+ * @param {Object} route.config - Configuración del endpoint
38
+ */
39
+ addRoute(route) {
40
+ const { path, method, config = {} } = route;
41
+
42
+ if (!this.spec.paths[path]) {
43
+ this.spec.paths[path] = {};
44
+ }
45
+
46
+ this.spec.paths[path][method.toLowerCase()] = {
47
+ summary: config.summary || '',
48
+ description: config.description || '',
49
+ parameters: config.parameters || [],
50
+ requestBody: config.requestBody,
51
+ responses: config.responses || {
52
+ '200': {
53
+ description: 'Operación exitosa'
54
+ }
55
+ },
56
+ security: config.security || []
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Agrega un esquema a la documentación
62
+ * @param {string} name - Nombre del esquema
63
+ * @param {Object} schema - Definición del esquema
64
+ */
65
+ addSchema(name, schema) {
66
+ this.spec.components.schemas[name] = schema;
67
+ }
68
+
69
+ /**
70
+ * Agrega un esquema de seguridad
71
+ * @param {string} name - Nombre del esquema de seguridad
72
+ * @param {Object} scheme - Definición del esquema de seguridad
73
+ */
74
+ addSecurityScheme(name, scheme) {
75
+ this.spec.components.securitySchemes[name] = scheme;
76
+ }
77
+
78
+ /**
79
+ * Agrega un requisito de seguridad global
80
+ * @param {Array} securityRequirement - Requisitos de seguridad
81
+ */
82
+ addGlobalSecurity(securityRequirement) {
83
+ if (!this.spec.security) {
84
+ this.spec.security = [];
85
+ }
86
+ this.spec.security.push(securityRequirement);
87
+ }
88
+
89
+ /**
90
+ * Genera la especificación OpenAPI completa
91
+ * @returns {Object} - Especificación OpenAPI
92
+ */
93
+ generateSpec() {
94
+ return this.spec;
95
+ }
96
+
97
+ /**
98
+ * Agrega rutas de documentación OpenAPI al servidor
99
+ * @param {Object} server - Instancia del servidor
100
+ */
101
+ addDocumentationRoute(server) {
102
+ // Ruta para obtener la especificación OpenAPI en formato JSON
103
+ server.addRoute('GET', '/openapi.json', (req, res) => {
104
+ const spec = this.generateSpec();
105
+ res.writeHead(200, { 'Content-Type': 'application/json' });
106
+ res.end(JSON.stringify(spec, null, 2));
107
+ });
108
+
109
+ // Ruta para la documentación interactiva con Swagger UI
110
+ server.addRoute('GET', '/docs', (req, res) => {
111
+ const swaggerHtml = `
112
+ <!DOCTYPE html>
113
+ <html>
114
+ <head>
115
+ <title>API Documentation</title>
116
+ <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@latest/swagger-ui.css" />
117
+ </head>
118
+ <body>
119
+ <div id="swagger-ui"></div>
120
+ <script src="https://unpkg.com/swagger-ui-dist@latest/swagger-ui-bundle.js"></script>
121
+ <script>
122
+ SwaggerUIBundle({
123
+ url: '/openapi.json',
124
+ dom_id: '#swagger-ui',
125
+ presets: [
126
+ SwaggerUIBundle.presets.apis,
127
+ SwaggerUIBundle.presets.standalone
128
+ ]
129
+ });
130
+ </script>
131
+ </body>
132
+ </html>`;
133
+
134
+ res.writeHead(200, { 'Content-Type': 'text/html' });
135
+ res.end(swaggerHtml);
136
+ });
137
+ }
138
+ }
139
+
140
+ module.exports = OpenApiGenerator;
@@ -0,0 +1,224 @@
1
+ // lib/utils/sqliteTokenAdapter.js
2
+ const sqlite3 = require('sqlite3').verbose();
3
+ const path = require('path');
4
+
5
+ class SQLiteTokenAdapter {
6
+ /**
7
+ * Constructor del adaptador
8
+ * @param {Object} config - Configuración de conexión
9
+ * @param {string} config.dbPath - Ruta a la base de datos SQLite
10
+ * @param {string} config.tableName - Nombre de la tabla de tokens
11
+ */
12
+ constructor(config) {
13
+ this.dbPath = config.dbPath || './tokens.sqlite';
14
+ this.tableName = config.tableName || 'tokens';
15
+ this.db = null;
16
+ }
17
+
18
+ /**
19
+ * Inicializa la conexión y la tabla de tokens
20
+ */
21
+ async initialize() {
22
+ return new Promise((resolve, reject) => {
23
+ this.db = new sqlite3.Database(this.dbPath, (err) => {
24
+ if (err) {
25
+ console.error('Error conectando a SQLite:', err.message);
26
+ reject(err);
27
+ return;
28
+ }
29
+
30
+ console.log(`✅ Conexión a SQLite establecida: ${this.dbPath}`);
31
+
32
+ // Crear tabla de tokens si no existe
33
+ this.initializeTable()
34
+ .then(() => {
35
+ console.log(`✅ Tabla ${this.tableName} inicializada correctamente`);
36
+ resolve();
37
+ })
38
+ .catch(reject);
39
+ });
40
+ });
41
+ }
42
+
43
+ /**
44
+ * Inicializa la tabla de tokens en la base de datos
45
+ */
46
+ async initializeTable() {
47
+ const createTableQuery = `
48
+ CREATE TABLE IF NOT EXISTS ${this.tableName} (
49
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
50
+ token TEXT UNIQUE NOT NULL,
51
+ user_id INTEGER NOT NULL,
52
+ token_type TEXT DEFAULT 'access',
53
+ expires_at DATETIME NOT NULL,
54
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
55
+ revoked BOOLEAN DEFAULT 0
56
+ );
57
+
58
+ CREATE INDEX IF NOT EXISTS idx_user_id ON ${this.tableName} (user_id);
59
+ CREATE INDEX IF NOT EXISTS idx_token ON ${this.tableName} (token);
60
+ CREATE INDEX IF NOT EXISTS idx_expires_at ON ${this.tableName} (expires_at);
61
+ `;
62
+
63
+ return new Promise((resolve, reject) => {
64
+ this.db.run(createTableQuery, (err) => {
65
+ if (err) {
66
+ console.error('Error inicializando tabla de tokens:', err.message);
67
+ reject(err);
68
+ } else {
69
+ resolve();
70
+ }
71
+ });
72
+ });
73
+ }
74
+
75
+ /**
76
+ * Guarda un token en la base de datos
77
+ * @param {string} token - Token a guardar
78
+ * @param {Object} userData - Datos del usuario
79
+ * @param {string} tokenType - Tipo de token ('access' o 'refresh')
80
+ * @param {Date} expiresAt - Fecha de expiración
81
+ */
82
+ async saveToken(token, userData, tokenType = 'access', expiresAt) {
83
+ const insertQuery = `
84
+ INSERT INTO ${this.tableName} (token, user_id, token_type, expires_at)
85
+ VALUES (?, ?, ?, ?)
86
+ `;
87
+
88
+ return new Promise((resolve, reject) => {
89
+ this.db.run(insertQuery, [
90
+ token,
91
+ userData.userId || userData.id,
92
+ tokenType,
93
+ expiresAt.toISOString()
94
+ ], function(err) {
95
+ if (err) {
96
+ console.error('Error guardando token:', err.message);
97
+ reject(err);
98
+ } else {
99
+ console.log(`✅ Token ${tokenType} guardado para usuario ${userData.userId || userData.id}`);
100
+ resolve();
101
+ }
102
+ });
103
+ });
104
+ }
105
+
106
+ /**
107
+ * Verifica si un token existe y es válido
108
+ * @param {string} token - Token a verificar
109
+ * @returns {Object|null} - Datos del token o null si no es válido
110
+ */
111
+ async validateToken(token) {
112
+ const selectQuery = `
113
+ SELECT * FROM ${this.tableName}
114
+ WHERE token = ? AND revoked = 0 AND expires_at > datetime('now')
115
+ `;
116
+
117
+ return new Promise((resolve, reject) => {
118
+ this.db.get(selectQuery, [token], (err, row) => {
119
+ if (err) {
120
+ console.error('Error validando token:', err.message);
121
+ reject(err);
122
+ } else {
123
+ resolve(row || null);
124
+ }
125
+ });
126
+ });
127
+ }
128
+
129
+ /**
130
+ * Revoca un token
131
+ * @param {string} token - Token a revocar
132
+ * @returns {boolean} - True si se revocó exitosamente
133
+ */
134
+ async revokeToken(token) {
135
+ const updateQuery = `
136
+ UPDATE ${this.tableName}
137
+ SET revoked = 1
138
+ WHERE token = ?
139
+ `;
140
+
141
+ return new Promise((resolve, reject) => {
142
+ this.db.run(updateQuery, [token], function(err) {
143
+ if (err) {
144
+ console.error('Error revocando token:', err.message);
145
+ reject(err);
146
+ } else {
147
+ const revoked = this.changes > 0;
148
+ if (revoked) {
149
+ console.log(`✅ Token revocado: ${token.substring(0, 20)}...`);
150
+ } else {
151
+ console.log(`⚠️ Token no encontrado o ya revocado: ${token.substring(0, 20)}...`);
152
+ }
153
+ resolve(revoked);
154
+ }
155
+ });
156
+ });
157
+ }
158
+
159
+ /**
160
+ * Obtiene tokens de un usuario
161
+ * @param {number} userId - ID del usuario
162
+ * @returns {Array} - Array de tokens del usuario
163
+ */
164
+ async getUserTokens(userId) {
165
+ const selectQuery = `
166
+ SELECT * FROM ${this.tableName}
167
+ WHERE user_id = ? AND revoked = 0 AND expires_at > datetime('now')
168
+ `;
169
+
170
+ return new Promise((resolve, reject) => {
171
+ this.db.all(selectQuery, [userId], (err, rows) => {
172
+ if (err) {
173
+ console.error('Error obteniendo tokens de usuario:', err.message);
174
+ reject(err);
175
+ } else {
176
+ resolve(rows || []);
177
+ }
178
+ });
179
+ });
180
+ }
181
+
182
+ /**
183
+ * Limpia tokens expirados
184
+ */
185
+ async cleanupExpiredTokens() {
186
+ const deleteQuery = `
187
+ DELETE FROM ${this.tableName}
188
+ WHERE expires_at <= datetime('now')
189
+ `;
190
+
191
+ return new Promise((resolve, reject) => {
192
+ this.db.run(deleteQuery, function(err) {
193
+ if (err) {
194
+ console.error('Error limpiando tokens expirados:', err.message);
195
+ reject(err);
196
+ } else {
197
+ console.log(`✅ Eliminados ${this.changes} tokens expirados`);
198
+ resolve(this.changes);
199
+ }
200
+ });
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Cierra la conexión a la base de datos
206
+ */
207
+ async close() {
208
+ if (this.db) {
209
+ return new Promise((resolve, reject) => {
210
+ this.db.close((err) => {
211
+ if (err) {
212
+ console.error('Error cerrando conexión a SQLite:', err.message);
213
+ reject(err);
214
+ } else {
215
+ console.log('🔒 Conexión a SQLite cerrada');
216
+ resolve();
217
+ }
218
+ });
219
+ });
220
+ }
221
+ }
222
+ }
223
+
224
+ module.exports = SQLiteTokenAdapter;
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Sistema de gestión de tokens para el framework API SDK
3
+ * Implementación del componente utils/tokenManager.js
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const jwt = require('jsonwebtoken');
8
+
9
+ class TokenManager {
10
+ /**
11
+ * Constructor del gestor de tokens
12
+ * @param {Object} options - Opciones de configuración
13
+ * @param {string} options.storage - Tipo de almacenamiento ('memory', 'json', 'database')
14
+ * @param {string} options.tokenFile - Ruta al archivo JSON para almacenamiento
15
+ * @param {Object} options.dbConfig - Configuración para base de datos (si aplica)
16
+ */
17
+ constructor(options = {}) {
18
+ this.storage = options.storage || 'memory';
19
+ this.tokenFile = options.tokenFile || './tokens.json';
20
+ this.dbConfig = options.dbConfig;
21
+
22
+ // Inicializar almacenamiento
23
+ this.tokens = new Map();
24
+
25
+ if (this.storage === 'json') {
26
+ this.initJsonStorage();
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Inicializa el almacenamiento JSON
32
+ */
33
+ initJsonStorage() {
34
+ try {
35
+ if (fs.existsSync(this.tokenFile)) {
36
+ const data = fs.readFileSync(this.tokenFile, 'utf8');
37
+ const jsonData = JSON.parse(data);
38
+
39
+ // Convertir objeto a Map
40
+ for (const [key, value] of Object.entries(jsonData)) {
41
+ this.tokens.set(key, value);
42
+ }
43
+ } else {
44
+ // Crear archivo con estructura vacía
45
+ fs.writeFileSync(this.tokenFile, JSON.stringify({}, null, 2));
46
+ }
47
+ } catch (error) {
48
+ console.error('Error inicializando almacenamiento JSON:', error.message);
49
+ // Inicializar con estructura vacía
50
+ this.tokens = new Map();
51
+ fs.writeFileSync(this.tokenFile, JSON.stringify({}, null, 2));
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Guarda tokens en almacenamiento JSON
57
+ */
58
+ saveToJson() {
59
+ if (this.storage === 'json') {
60
+ const jsonObj = {};
61
+ for (const [key, value] of this.tokens) {
62
+ jsonObj[key] = value;
63
+ }
64
+ fs.writeFileSync(this.tokenFile, JSON.stringify(jsonObj, null, 2));
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Genera un nuevo token JWT
70
+ * @param {Object} payload - Payload del token
71
+ * @param {string} secret - Secreto para firmar el token
72
+ * @param {string|number} expiresIn - Tiempo de expiración
73
+ * @returns {string} - Token generado
74
+ */
75
+ generateToken(payload, secret, expiresIn = '1h') {
76
+ return jwt.sign(payload, secret, { expiresIn });
77
+ }
78
+
79
+ /**
80
+ * Genera un par de tokens (access y refresh)
81
+ * @param {Object} userData - Datos del usuario
82
+ * @param {Object} options - Opciones de generación
83
+ * @returns {Object} - Objeto con ambos tokens
84
+ */
85
+ generateTokenPair(userData, options = {}) {
86
+ const {
87
+ jwtSecret,
88
+ refreshSecret,
89
+ accessExpiresIn = '15m',
90
+ refreshExpiresIn = '7d'
91
+ } = options;
92
+
93
+ // Validar que los secrets estén definidos
94
+ if (!jwtSecret) {
95
+ throw new Error('Se requiere jwtSecret para generar el token de acceso');
96
+ }
97
+ if (!refreshSecret) {
98
+ throw new Error('Se requiere refreshSecret para generar el token de refresco');
99
+ }
100
+
101
+ const accessToken = this.generateToken(
102
+ { ...userData, tokenType: 'access' },
103
+ jwtSecret,
104
+ accessExpiresIn
105
+ );
106
+
107
+ const refreshToken = this.generateToken(
108
+ { ...userData, tokenType: 'refresh' },
109
+ refreshSecret,
110
+ refreshExpiresIn
111
+ );
112
+
113
+ // Almacenar refresh token si es necesario para revocación
114
+ if (this.storage !== 'memory') {
115
+ this.tokens.set(refreshToken, {
116
+ userId: userData.userId || userData.id,
117
+ createdAt: new Date().toISOString(),
118
+ expiresAt: new Date(Date.now() + this.parseTimeToMs(refreshExpiresIn)).toISOString(),
119
+ revoked: false
120
+ });
121
+
122
+ if (this.storage === 'json') {
123
+ this.saveToJson();
124
+ }
125
+ }
126
+
127
+ return {
128
+ accessToken,
129
+ refreshToken
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Valida un token JWT
135
+ * @param {string} token - Token a validar
136
+ * @param {string} secret - Secreto para verificar el token
137
+ * @returns {Object|null} - Payload decodificado o null si inválido
138
+ */
139
+ validateToken(token, secret) {
140
+ try {
141
+ return jwt.verify(token, secret);
142
+ } catch (error) {
143
+ return null;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Renueva un token de acceso usando un refresh token
149
+ * @param {string} refreshToken - Refresh token
150
+ * @param {string} jwtSecret - Secreto JWT
151
+ * @param {string} refreshSecret - Secreto refresh
152
+ * @param {string} accessExpiresIn - Tiempo expiración access token
153
+ * @returns {Object|null} - Nuevo par de tokens o null si inválido
154
+ */
155
+ refreshToken(refreshToken, jwtSecret, refreshSecret, accessExpiresIn = '15m') {
156
+ // Primero verificar si el refresh token es válido
157
+ const decoded = this.validateToken(refreshToken, refreshSecret);
158
+
159
+ if (!decoded) {
160
+ return null;
161
+ }
162
+
163
+ // Si usamos almacenamiento persistente, verificar si el token está revocado
164
+ if (this.storage !== 'memory') {
165
+ const storedToken = this.tokens.get(refreshToken);
166
+ if (!storedToken || storedToken.revoked) {
167
+ return null;
168
+ }
169
+ }
170
+
171
+ // Crear un nuevo payload sin las propiedades de expiración del refresh token
172
+ const newPayload = { ...decoded };
173
+ delete newPayload.exp; // Eliminar expiración del refresh token
174
+ delete newPayload.iat; // Eliminar emisión del refresh token
175
+ delete newPayload.tokenType; // Asegurarse de que sea un token de acceso
176
+
177
+ // Generar nuevo token de acceso con los datos del usuario
178
+ const newAccessToken = this.generateToken(
179
+ { ...newPayload, tokenType: 'access' },
180
+ jwtSecret,
181
+ accessExpiresIn
182
+ );
183
+
184
+ return {
185
+ accessToken: newAccessToken,
186
+ refreshToken // Devolver el mismo refresh token (o generar uno nuevo si se implementa rotación)
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Revoca un refresh token
192
+ * @param {string} refreshToken - Refresh token a revocar
193
+ * @returns {boolean} - True si se revocó exitosamente
194
+ */
195
+ revokeToken(refreshToken) {
196
+ if (this.storage !== 'memory') {
197
+ const storedToken = this.tokens.get(refreshToken);
198
+ if (storedToken) {
199
+ storedToken.revoked = true;
200
+ storedToken.revokedAt = new Date().toISOString();
201
+
202
+ if (this.storage === 'json') {
203
+ this.saveToJson();
204
+ }
205
+
206
+ return true;
207
+ }
208
+ }
209
+ return false;
210
+ }
211
+
212
+ /**
213
+ * Parsea una duración de tiempo a milisegundos
214
+ * @param {string|number} time - Tiempo en formato legible (ej: '1h', '7d', '30m')
215
+ * @returns {number} - Milisegundos
216
+ */
217
+ parseTimeToMs(time) {
218
+ if (typeof time === 'number') return time;
219
+
220
+ const timeStr = time.toString();
221
+ const num = parseInt(timeStr.match(/\d+/)[0]);
222
+ const unit = timeStr.match(/[a-z]+/i)[0].toLowerCase();
223
+
224
+ switch (unit) {
225
+ case 's': return num * 1000;
226
+ case 'm': return num * 60 * 1000;
227
+ case 'h': return num * 60 * 60 * 1000;
228
+ case 'd': return num * 24 * 60 * 60 * 1000;
229
+ default: return num * 1000; // Por defecto segundos
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Obtiene tokens válidos para un usuario
235
+ * @param {string|number} userId - ID del usuario
236
+ * @returns {Array} - Array de tokens asociados al usuario
237
+ */
238
+ getUserTokens(userId) {
239
+ const userTokens = [];
240
+
241
+ for (const [token, data] of this.tokens) {
242
+ if (data.userId == userId && !data.revoked) {
243
+ userTokens.push({
244
+ token,
245
+ ...data
246
+ });
247
+ }
248
+ }
249
+
250
+ return userTokens;
251
+ }
252
+ }
253
+
254
+ module.exports = TokenManager;
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "jerkjs",
3
+ "version": "2.0.0",
4
+ "description": "JERK Framework v2.0 - A comprehensive framework for building secure and scalable APIs",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [
10
+ "api",
11
+ "sdk",
12
+ "framework",
13
+ "server",
14
+ "router",
15
+ "middleware",
16
+ "security",
17
+ "authentication",
18
+ "cors",
19
+ "rate-limiter",
20
+ "firewall",
21
+ "waf"
22
+ ],
23
+ "author": "JERK Team",
24
+ "license": "Apache-2.0",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://gitlab.com/bytedogssyndicate1/jerk.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://gitlab.com/bytedogssyndicate1/jerk/issues"
31
+ },
32
+ "homepage": "https://jerk.page.gd/",
33
+ "dependencies": {
34
+ "bcrypt": "^6.0.0",
35
+ "jsonwebtoken": "^9.0.3",
36
+ "mariadb": "^3.0.0",
37
+ "sqlite3": "^5.1.7"
38
+ },
39
+ "engines": {
40
+ "node": ">=14.0.0"
41
+ },
42
+ "directories": {
43
+ "doc": "doc",
44
+ "example": "examples",
45
+ "lib": "lib"
46
+ }
47
+ }