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,565 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware de autenticación para el framework API SDK
|
|
3
|
+
* Implementación extendida del componente middleware/authenticator.js
|
|
4
|
+
* Incluye soporte para OAuth2 y OpenID Connect
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class Authenticator {
|
|
8
|
+
/**
|
|
9
|
+
* Constructor del autenticador
|
|
10
|
+
* @param {Object} options - Opciones de configuración
|
|
11
|
+
* @param {Object} options.logger - Instancia de logger para auditoría de seguridad
|
|
12
|
+
*/
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.strategies = new Map();
|
|
15
|
+
this.logger = options.logger || console;
|
|
16
|
+
this.failedAttempts = new Map(); // Para seguimiento de intentos fallidos
|
|
17
|
+
this.blockThreshold = options.blockThreshold || 5; // Número de intentos fallidos antes de bloquear
|
|
18
|
+
this.blockDuration = options.blockDuration || 900000; // Duración del bloqueo en ms (15 min por defecto)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Método para registrar una estrategia de autenticación
|
|
23
|
+
* @param {string} name - Nombre de la estrategia
|
|
24
|
+
* @param {Function} strategy - Función de estrategia de autenticación
|
|
25
|
+
*/
|
|
26
|
+
use(name, strategy) {
|
|
27
|
+
this.strategies.set(name, strategy);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Método para crear middleware de autenticación por nombre
|
|
32
|
+
* @param {string} strategyName - Nombre de la estrategia a usar
|
|
33
|
+
* @param {Object} options - Opciones para la estrategia
|
|
34
|
+
* @returns {Function} - Middleware de autenticación
|
|
35
|
+
*/
|
|
36
|
+
authenticate(strategyName, options = {}) {
|
|
37
|
+
const strategy = this.strategies.get(strategyName);
|
|
38
|
+
|
|
39
|
+
if (!strategy) {
|
|
40
|
+
throw new Error(`Estrategia de autenticación '${strategyName}' no encontrada`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return async (req, res, next) => {
|
|
44
|
+
// Obtener IP del cliente para seguimiento
|
|
45
|
+
const clientIP = req.headers['x-forwarded-for'] ||
|
|
46
|
+
req.connection.remoteAddress ||
|
|
47
|
+
req.socket.remoteAddress ||
|
|
48
|
+
(req.connection.socket ? req.connection.socket.remoteAddress : null);
|
|
49
|
+
|
|
50
|
+
// Verificar si la IP está bloqueada
|
|
51
|
+
if (this.isBlocked(clientIP)) {
|
|
52
|
+
this.logger.warn(`Intento de autenticación bloqueado desde IP: ${clientIP}`);
|
|
53
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
54
|
+
res.end(JSON.stringify({ error: 'Acceso bloqueado temporalmente por múltiples intentos fallidos' }));
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const isAuthenticated = await strategy(req, options);
|
|
60
|
+
|
|
61
|
+
if (isAuthenticated) {
|
|
62
|
+
// Registrar evento de autenticación exitosa
|
|
63
|
+
this.logger.info(`Autenticación exitosa para IP: ${clientIP}, estrategia: ${strategyName}`);
|
|
64
|
+
|
|
65
|
+
// Reiniciar contador de intentos fallidos para esta IP
|
|
66
|
+
this.resetFailedAttempts(clientIP);
|
|
67
|
+
|
|
68
|
+
if (next) {
|
|
69
|
+
next();
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
} else {
|
|
73
|
+
// Incrementar contador de intentos fallidos
|
|
74
|
+
this.incrementFailedAttempts(clientIP);
|
|
75
|
+
|
|
76
|
+
// Registrar evento de autenticación fallida
|
|
77
|
+
this.logger.warn(`Autenticación fallida para IP: ${clientIP}, estrategia: ${strategyName}`);
|
|
78
|
+
|
|
79
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
80
|
+
res.end(JSON.stringify({ error: 'No autorizado' }));
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// Incrementar contador de intentos fallidos
|
|
85
|
+
this.incrementFailedAttempts(clientIP);
|
|
86
|
+
|
|
87
|
+
// Registrar evento de error en autenticación
|
|
88
|
+
this.logger.error(`Error en autenticación para IP: ${clientIP}, estrategia: ${strategyName}`, error.message);
|
|
89
|
+
|
|
90
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
91
|
+
res.end(JSON.stringify({ error: 'Error en la autenticación' }));
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Estrategia de autenticación por API Key
|
|
99
|
+
* @param {string} headerName - Nombre del header que contiene la API Key
|
|
100
|
+
* @param {Array} validKeys - Array de claves válidas
|
|
101
|
+
* @param {Object} tenantConfig - Configuración por tenant
|
|
102
|
+
* @returns {Function} - Estrategia de autenticación por API Key
|
|
103
|
+
*/
|
|
104
|
+
apiKeyStrategy(headerName = 'X-API-Key', validKeys = [], tenantConfig = {}) {
|
|
105
|
+
return (req, options = {}) => {
|
|
106
|
+
const providedKey = req.headers[headerName.toLowerCase()];
|
|
107
|
+
const tenantId = req.headers['x-tenant-id'] || req.headers['tenant']; // Identificador del tenant
|
|
108
|
+
|
|
109
|
+
// Si se proporciona un tenantId y existe configuración específica para ese tenant
|
|
110
|
+
if (tenantId && tenantConfig[tenantId]) {
|
|
111
|
+
const tenantKeys = tenantConfig[tenantId].keys || [];
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
resolve(tenantKeys.includes(providedKey));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const keys = options.validKeys || validKeys;
|
|
118
|
+
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
resolve(keys.includes(providedKey));
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Estrategia de autenticación por JWT
|
|
127
|
+
* @param {string} secret - Secreto para verificar el token
|
|
128
|
+
* @param {Object} options - Opciones adicionales
|
|
129
|
+
* @param {string} options.refreshSecret - Secreto para verificar refresh tokens
|
|
130
|
+
* @param {string} options.storage - Tipo de almacenamiento para tokens ('memory', 'json')
|
|
131
|
+
* @param {string} options.tokenFile - Ruta al archivo JSON para almacenamiento
|
|
132
|
+
* @returns {Function} - Estrategia de autenticación por JWT
|
|
133
|
+
*/
|
|
134
|
+
jwtStrategy(secret, options = {}) {
|
|
135
|
+
const refreshSecret = options.refreshSecret || secret; // Usar mismo secreto si no se proporciona uno diferente
|
|
136
|
+
const tokenManager = new (require('../utils/tokenManager'))({
|
|
137
|
+
storage: options.storage || 'memory',
|
|
138
|
+
tokenFile: options.tokenFile
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Verificar que se haya proporcionado un secreto
|
|
142
|
+
if (!secret) {
|
|
143
|
+
throw new Error('Se requiere un secreto para la estrategia JWT');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Importar jsonwebtoken si está disponible
|
|
147
|
+
const jwt = require('jsonwebtoken');
|
|
148
|
+
|
|
149
|
+
return async (req, options = {}) => {
|
|
150
|
+
const authHeader = req.headers.authorization;
|
|
151
|
+
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
|
152
|
+
const jwtSecret = options.secret || secret;
|
|
153
|
+
|
|
154
|
+
if (!token) {
|
|
155
|
+
// Verificar si hay un refresh token para renovar el access token
|
|
156
|
+
const refreshToken = req.headers['x-refresh-token'];
|
|
157
|
+
if (refreshToken) {
|
|
158
|
+
try {
|
|
159
|
+
// Usar el token manager para renovar el token
|
|
160
|
+
const renewedTokens = tokenManager.refreshToken(
|
|
161
|
+
refreshToken,
|
|
162
|
+
jwtSecret,
|
|
163
|
+
refreshSecret,
|
|
164
|
+
options.accessTokenExpiration || '15m'
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
if (renewedTokens) {
|
|
168
|
+
// Agregar nuevo token a la respuesta para que el cliente lo actualice
|
|
169
|
+
if (req.res) {
|
|
170
|
+
req.res.setHeader('X-New-Access-Token', renewedTokens.accessToken);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Decodificar el nuevo token para obtener la información del usuario
|
|
174
|
+
const decoded = jwt.verify(renewedTokens.accessToken, jwtSecret);
|
|
175
|
+
req.user = decoded;
|
|
176
|
+
return Promise.resolve(true);
|
|
177
|
+
}
|
|
178
|
+
} catch (refreshError) {
|
|
179
|
+
return Promise.resolve(false);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return Promise.resolve(false);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return new Promise((resolve) => {
|
|
187
|
+
jwt.verify(token, jwtSecret, (err, decoded) => {
|
|
188
|
+
if (err) {
|
|
189
|
+
// Si el token ha expirado, intentar usar refresh token
|
|
190
|
+
if (err.name === 'TokenExpiredError') {
|
|
191
|
+
const refreshToken = req.headers['x-refresh-token'];
|
|
192
|
+
if (refreshToken) {
|
|
193
|
+
const renewedTokens = tokenManager.refreshToken(
|
|
194
|
+
refreshToken,
|
|
195
|
+
jwtSecret,
|
|
196
|
+
refreshSecret,
|
|
197
|
+
options.accessTokenExpiration || '15m'
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
if (renewedTokens) {
|
|
201
|
+
// Agregar nuevo token a la respuesta para que el cliente lo actualice
|
|
202
|
+
if (req.res) {
|
|
203
|
+
req.res.setHeader('X-New-Access-Token', renewedTokens.accessToken);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Decodificar el nuevo token para obtener la información del usuario
|
|
207
|
+
const freshDecoded = jwt.verify(renewedTokens.accessToken, jwtSecret);
|
|
208
|
+
req.user = freshDecoded;
|
|
209
|
+
resolve(true);
|
|
210
|
+
} else {
|
|
211
|
+
resolve(false);
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
resolve(false);
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
resolve(false);
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
// Agregar el payload decodificado a la solicitud
|
|
221
|
+
req.user = decoded;
|
|
222
|
+
resolve(true);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Estrategia de autenticación básica
|
|
231
|
+
* @param {Object} credentials - Credenciales válidas { username: password }
|
|
232
|
+
* @returns {Function} - Estrategia de autenticación básica
|
|
233
|
+
*/
|
|
234
|
+
basicStrategy(credentials = {}) {
|
|
235
|
+
return (req, options = {}) => {
|
|
236
|
+
const authHeader = req.headers.authorization;
|
|
237
|
+
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
|
238
|
+
return Promise.resolve(false);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const base64Credentials = authHeader.split(' ')[1];
|
|
242
|
+
const credentialsString = Buffer.from(base64Credentials, 'base64').toString('ascii');
|
|
243
|
+
const [username, password] = credentialsString.split(':');
|
|
244
|
+
|
|
245
|
+
const validCredentials = options.credentials || credentials;
|
|
246
|
+
|
|
247
|
+
return new Promise((resolve) => {
|
|
248
|
+
if (validCredentials[username] && validCredentials[username] === password) {
|
|
249
|
+
req.user = { username }; // Agregar usuario a la solicitud
|
|
250
|
+
resolve(true);
|
|
251
|
+
} else {
|
|
252
|
+
resolve(false);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Verifica si una IP está bloqueada por exceso de intentos fallidos
|
|
260
|
+
* @param {string} ip - Dirección IP a verificar
|
|
261
|
+
* @returns {boolean} - Verdadero si la IP está bloqueada
|
|
262
|
+
*/
|
|
263
|
+
isBlocked(ip) {
|
|
264
|
+
const record = this.failedAttempts.get(ip);
|
|
265
|
+
if (!record) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Verificar si aún está dentro del período de bloqueo
|
|
270
|
+
if (Date.now() - record.firstAttemptTime < this.blockDuration) {
|
|
271
|
+
return record.count >= this.blockThreshold;
|
|
272
|
+
} else {
|
|
273
|
+
// El período de bloqueo ha expirado, eliminar el registro
|
|
274
|
+
this.failedAttempts.delete(ip);
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Incrementa el contador de intentos fallidos para una IP
|
|
281
|
+
* @param {string} ip - Dirección IP
|
|
282
|
+
*/
|
|
283
|
+
incrementFailedAttempts(ip) {
|
|
284
|
+
const record = this.failedAttempts.get(ip);
|
|
285
|
+
if (record) {
|
|
286
|
+
record.count++;
|
|
287
|
+
record.lastAttemptTime = Date.now();
|
|
288
|
+
} else {
|
|
289
|
+
this.failedAttempts.set(ip, {
|
|
290
|
+
count: 1,
|
|
291
|
+
firstAttemptTime: Date.now(),
|
|
292
|
+
lastAttemptTime: Date.now()
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Reinicia el contador de intentos fallidos para una IP
|
|
299
|
+
* @param {string} ip - Dirección IP
|
|
300
|
+
*/
|
|
301
|
+
resetFailedAttempts(ip) {
|
|
302
|
+
this.failedAttempts.delete(ip);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Obtiene estadísticas de seguridad para una IP
|
|
307
|
+
* @param {string} ip - Dirección IP
|
|
308
|
+
* @returns {Object} - Estadísticas de intentos de autenticación
|
|
309
|
+
*/
|
|
310
|
+
getSecurityStats(ip) {
|
|
311
|
+
return this.failedAttempts.get(ip) || { count: 0, blocked: false };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Estrategia de autenticación OAuth2
|
|
316
|
+
* @param {Object} options - Opciones para la estrategia OAuth2
|
|
317
|
+
* @param {string} options.clientId - ID del cliente OAuth2
|
|
318
|
+
* @param {string} options.clientSecret - Secreto del cliente OAuth2
|
|
319
|
+
* @param {string} options.callbackURL - URL de callback para OAuth2
|
|
320
|
+
* @param {string} options.authorizationURL - URL de autorización
|
|
321
|
+
* @param {string} options.tokenURL - URL para obtener token
|
|
322
|
+
* @returns {Function} - Estrategia de autenticación OAuth2
|
|
323
|
+
*/
|
|
324
|
+
oauth2Strategy(options = {}) {
|
|
325
|
+
const { clientId, clientSecret, callbackURL, authorizationURL, tokenURL } = options;
|
|
326
|
+
|
|
327
|
+
if (!clientId || !clientSecret || !callbackURL || !authorizationURL || !tokenURL) {
|
|
328
|
+
throw new Error('Opciones requeridas para OAuth2: clientId, clientSecret, callbackURL, authorizationURL, tokenURL');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return async (req, options = {}) => {
|
|
332
|
+
// Verificar si req.session existe (requiere middleware de sesión externo)
|
|
333
|
+
if (!req.session) {
|
|
334
|
+
// Si no hay sesión, verificar si se proporciona token en header
|
|
335
|
+
const authHeader = req.headers.authorization;
|
|
336
|
+
if (authHeader && authHeader.startsWith('Bearer ')) {
|
|
337
|
+
const token = authHeader.substring(7);
|
|
338
|
+
|
|
339
|
+
// En una implementación real, aquí verificaríamos el token con el proveedor OAuth2
|
|
340
|
+
// Por ahora, simplemente simulamos la verificación
|
|
341
|
+
try {
|
|
342
|
+
// Simular verificación del token con el proveedor OAuth2
|
|
343
|
+
// En una implementación real, haríamos una solicitud HTTP al proveedor
|
|
344
|
+
// para verificar la validez del token
|
|
345
|
+
req.oauth2User = { token: token, verified: true };
|
|
346
|
+
return Promise.resolve(true);
|
|
347
|
+
} catch (error) {
|
|
348
|
+
return Promise.resolve(false);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Si no hay sesión ni token en header, la autenticación falla
|
|
353
|
+
return Promise.resolve(false);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Si hay sesión, verificar si ya tenemos un token de acceso
|
|
357
|
+
const accessToken = req.session.accessToken;
|
|
358
|
+
|
|
359
|
+
if (accessToken) {
|
|
360
|
+
// Verificar validez del token si es posible
|
|
361
|
+
try {
|
|
362
|
+
// Aquí normalmente haríamos una llamada para verificar el token
|
|
363
|
+
// Por simplicidad en este ejemplo, asumiremos que es válido si existe
|
|
364
|
+
return Promise.resolve(true);
|
|
365
|
+
} catch (error) {
|
|
366
|
+
return Promise.resolve(false);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Si no hay token, verificar si estamos en el proceso de callback
|
|
371
|
+
const parsedUrl = require('url').parse(req.url, true);
|
|
372
|
+
const { query } = parsedUrl;
|
|
373
|
+
|
|
374
|
+
if (query.code) {
|
|
375
|
+
// Este es el callback con el código de autorización
|
|
376
|
+
try {
|
|
377
|
+
// Intercambiar el código por un token de acceso
|
|
378
|
+
const tokenResponse = await this.exchangeCodeForToken(
|
|
379
|
+
query.code,
|
|
380
|
+
clientId,
|
|
381
|
+
clientSecret,
|
|
382
|
+
callbackURL,
|
|
383
|
+
tokenURL
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
// Almacenar el token en la sesión si existe
|
|
387
|
+
if (req.session) {
|
|
388
|
+
req.session.accessToken = tokenResponse.access_token;
|
|
389
|
+
req.session.refreshToken = tokenResponse.refresh_token;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Agregar información del usuario a la solicitud
|
|
393
|
+
req.oauth2User = {
|
|
394
|
+
accessToken: tokenResponse.access_token,
|
|
395
|
+
tokenType: tokenResponse.token_type
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
return Promise.resolve(true);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
console.error('Error en el proceso de OAuth2:', error);
|
|
401
|
+
return Promise.resolve(false);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Si no hay token ni código de autorización, la autenticación falla
|
|
406
|
+
return Promise.resolve(false);
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Método auxiliar para intercambiar código por token
|
|
412
|
+
* @private
|
|
413
|
+
*/
|
|
414
|
+
async exchangeCodeForToken(code, clientId, clientSecret, callbackURL, tokenURL) {
|
|
415
|
+
const https = require('https');
|
|
416
|
+
const querystring = require('querystring');
|
|
417
|
+
|
|
418
|
+
const postData = querystring.stringify({
|
|
419
|
+
grant_type: 'authorization_code',
|
|
420
|
+
client_id: clientId,
|
|
421
|
+
client_secret: clientSecret,
|
|
422
|
+
redirect_uri: callbackURL,
|
|
423
|
+
code: code
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const options = {
|
|
427
|
+
method: 'POST',
|
|
428
|
+
headers: {
|
|
429
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
430
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
return new Promise((resolve, reject) => {
|
|
435
|
+
const req = https.request(tokenURL, options, (res) => {
|
|
436
|
+
let data = '';
|
|
437
|
+
|
|
438
|
+
res.on('data', (chunk) => {
|
|
439
|
+
data += chunk;
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
res.on('end', () => {
|
|
443
|
+
try {
|
|
444
|
+
const tokenResponse = JSON.parse(data);
|
|
445
|
+
resolve(tokenResponse);
|
|
446
|
+
} catch (error) {
|
|
447
|
+
reject(error);
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
req.on('error', (error) => {
|
|
453
|
+
reject(error);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
req.write(postData);
|
|
457
|
+
req.end();
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Estrategia de autenticación OpenID Connect
|
|
463
|
+
* @param {Object} options - Opciones para la estrategia OIDC
|
|
464
|
+
* @param {string} options.issuer - URL del proveedor OIDC
|
|
465
|
+
* @param {string} options.clientId - ID del cliente OIDC
|
|
466
|
+
* @param {string} options.clientSecret - Secreto del cliente OIDC
|
|
467
|
+
* @param {string} options.callbackURL - URL de callback para OIDC
|
|
468
|
+
* @returns {Function} - Estrategia de autenticación OIDC
|
|
469
|
+
*/
|
|
470
|
+
oidcStrategy(options = {}) {
|
|
471
|
+
const { issuer, clientId, clientSecret, callbackURL } = options;
|
|
472
|
+
|
|
473
|
+
if (!issuer || !clientId || !clientSecret || !callbackURL) {
|
|
474
|
+
throw new Error('Opciones requeridas para OIDC: issuer, clientId, clientSecret, callbackURL');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return async (req, options = {}) => {
|
|
478
|
+
// Verificar si ya tenemos un token de ID
|
|
479
|
+
const idToken = req.session ? req.session.idToken : null;
|
|
480
|
+
|
|
481
|
+
if (idToken) {
|
|
482
|
+
try {
|
|
483
|
+
// Verificar el token de ID con la biblioteca adecuada
|
|
484
|
+
// Para esta implementación, necesitaríamos jsonwebtoken y jwks-rsa
|
|
485
|
+
const jwt = require('jsonwebtoken');
|
|
486
|
+
const jwksClient = require('jwks-rsa');
|
|
487
|
+
|
|
488
|
+
// Obtener la clave pública del proveedor OIDC
|
|
489
|
+
const client = jwksClient({
|
|
490
|
+
jwksUri: `${issuer}/.well-known/jwks.json`
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const getKey = (header, callback) => {
|
|
494
|
+
client.getSigningKey(header.kid, (err, key) => {
|
|
495
|
+
const signingKey = key.publicKey || key.rsaPublicKey;
|
|
496
|
+
callback(null, signingKey);
|
|
497
|
+
});
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
return new Promise((resolve) => {
|
|
501
|
+
jwt.verify(idToken, getKey, {
|
|
502
|
+
audience: clientId,
|
|
503
|
+
issuer: issuer
|
|
504
|
+
}, (err, decoded) => {
|
|
505
|
+
if (err) {
|
|
506
|
+
console.error('Error verificando token OIDC:', err);
|
|
507
|
+
resolve(false);
|
|
508
|
+
} else {
|
|
509
|
+
// Agregar información del usuario a la solicitud
|
|
510
|
+
req.oidcUser = decoded;
|
|
511
|
+
resolve(true);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
} catch (error) {
|
|
516
|
+
console.error('Error en la verificación OIDC:', error);
|
|
517
|
+
return Promise.resolve(false);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Si no hay token, verificar si estamos en el proceso de callback
|
|
522
|
+
const parsedUrl = require('url').parse(req.url, true);
|
|
523
|
+
const { query } = parsedUrl;
|
|
524
|
+
|
|
525
|
+
if (query.code) {
|
|
526
|
+
// Este es el callback con el código de autorización
|
|
527
|
+
try {
|
|
528
|
+
// Intercambiar el código por tokens
|
|
529
|
+
const tokenResponse = await this.exchangeCodeForToken(
|
|
530
|
+
query.code,
|
|
531
|
+
clientId,
|
|
532
|
+
clientSecret,
|
|
533
|
+
callbackURL,
|
|
534
|
+
`${issuer}/token`
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
// Verificar y decodificar el ID token
|
|
538
|
+
const idToken = tokenResponse.id_token;
|
|
539
|
+
|
|
540
|
+
if (idToken) {
|
|
541
|
+
// Almacenar el token en la sesión si existe
|
|
542
|
+
if (req.session) {
|
|
543
|
+
req.session.idToken = idToken;
|
|
544
|
+
req.session.accessToken = tokenResponse.access_token;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Agregar información del usuario a la solicitud
|
|
548
|
+
const jwt = require('jsonwebtoken');
|
|
549
|
+
req.oidcUser = jwt.decode(idToken);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return Promise.resolve(true);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
console.error('Error en el proceso de OIDC:', error);
|
|
555
|
+
return Promise.resolve(false);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Si no hay token ni código de autorización, la autenticación falla
|
|
560
|
+
return Promise.resolve(false);
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
module.exports = Authenticator;
|