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,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
|
+
}
|