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,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware de sesión para el framework API SDK
|
|
3
|
+
* Implementación del componente middleware/session.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
|
|
8
|
+
class SessionManager {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.sessions = new Map(); // Almacenamiento en memoria de sesiones
|
|
11
|
+
this.cookieName = options.cookieName || 'sessionId';
|
|
12
|
+
this.secret = options.secret || 'default-session-secret-change-me';
|
|
13
|
+
this.timeout = options.timeout || 3600000; // 1 hora por defecto
|
|
14
|
+
|
|
15
|
+
// Obtener instancia de hooks del framework
|
|
16
|
+
try {
|
|
17
|
+
const framework = require('../../index.js');
|
|
18
|
+
this.hooks = framework.hooks;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
// Si no está disponible, crear instancia local
|
|
21
|
+
const HookSystem = require('../core/hooks');
|
|
22
|
+
this.hooks = new HookSystem();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Genera un ID de sesión seguro
|
|
28
|
+
* @returns {string} - ID de sesión generado
|
|
29
|
+
*/
|
|
30
|
+
generateSessionId() {
|
|
31
|
+
return crypto.randomBytes(32).toString('hex');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Crea una nueva sesión
|
|
36
|
+
* @param {Object} userData - Datos del usuario para almacenar en la sesión
|
|
37
|
+
* @returns {string} - ID de la sesión creada
|
|
38
|
+
*/
|
|
39
|
+
createSession(userData) {
|
|
40
|
+
// Permitir que otros módulos modifiquen los datos de la sesión antes de crearla
|
|
41
|
+
const processedUserData = this.hooks.applyFilters('session_create_data', userData);
|
|
42
|
+
|
|
43
|
+
const sessionId = this.generateSessionId();
|
|
44
|
+
const sessionData = {
|
|
45
|
+
id: sessionId,
|
|
46
|
+
data: processedUserData,
|
|
47
|
+
createdAt: Date.now(),
|
|
48
|
+
lastAccessed: Date.now()
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
this.sessions.set(sessionId, sessionData);
|
|
52
|
+
|
|
53
|
+
// Disparar hook después de crear la sesión
|
|
54
|
+
this.hooks.doAction('session_created', sessionId, sessionData);
|
|
55
|
+
|
|
56
|
+
return sessionId;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Obtiene los datos de una sesión
|
|
61
|
+
* @param {string} sessionId - ID de la sesión
|
|
62
|
+
* @returns {Object|null} - Datos de la sesión o null si no existe
|
|
63
|
+
*/
|
|
64
|
+
getSession(sessionId) {
|
|
65
|
+
if (!sessionId) {
|
|
66
|
+
// Disparar hook cuando se intenta obtener una sesión sin ID
|
|
67
|
+
this.hooks.doAction('session_get_no_id');
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Permitir que otros módulos modifiquen el ID de sesión antes de buscarla
|
|
72
|
+
const processedSessionId = this.hooks.applyFilters('session_get_id', sessionId);
|
|
73
|
+
|
|
74
|
+
const session = this.sessions.get(processedSessionId);
|
|
75
|
+
if (!session) {
|
|
76
|
+
// Disparar hook cuando no se encuentra la sesión
|
|
77
|
+
this.hooks.doAction('session_not_found', processedSessionId);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Verificar si la sesión ha expirado
|
|
82
|
+
if (Date.now() - session.lastAccessed > this.timeout) {
|
|
83
|
+
this.destroySession(processedSessionId);
|
|
84
|
+
// Disparar hook cuando la sesión ha expirado
|
|
85
|
+
this.hooks.doAction('session_expired', processedSessionId);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Actualizar último acceso
|
|
90
|
+
session.lastAccessed = Date.now();
|
|
91
|
+
this.sessions.set(processedSessionId, session);
|
|
92
|
+
|
|
93
|
+
// Disparar hook después de obtener la sesión
|
|
94
|
+
this.hooks.doAction('session_retrieved', processedSessionId, session.data);
|
|
95
|
+
|
|
96
|
+
return session.data;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Actualiza los datos de una sesión
|
|
101
|
+
* @param {string} sessionId - ID de la sesión
|
|
102
|
+
* @param {Object} newData - Nuevos datos para la sesión
|
|
103
|
+
* @returns {boolean} - True si la actualización fue exitosa
|
|
104
|
+
*/
|
|
105
|
+
updateSession(sessionId, newData) {
|
|
106
|
+
// Permitir que otros módulos modifiquen los nuevos datos antes de actualizar
|
|
107
|
+
const processedNewData = this.hooks.applyFilters('session_update_data', newData, sessionId);
|
|
108
|
+
|
|
109
|
+
const session = this.sessions.get(sessionId);
|
|
110
|
+
if (!session) {
|
|
111
|
+
// Disparar hook cuando se intenta actualizar una sesión inexistente
|
|
112
|
+
this.hooks.doAction('session_update_failed', sessionId, processedNewData);
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
session.data = { ...session.data, ...processedNewData };
|
|
117
|
+
session.lastAccessed = Date.now();
|
|
118
|
+
this.sessions.set(sessionId, session);
|
|
119
|
+
|
|
120
|
+
// Disparar hook después de actualizar la sesión
|
|
121
|
+
this.hooks.doAction('session_updated', sessionId, session.data);
|
|
122
|
+
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Destruye una sesión
|
|
128
|
+
* @param {string} sessionId - ID de la sesión a destruir
|
|
129
|
+
* @returns {boolean} - True si la destrucción fue exitosa
|
|
130
|
+
*/
|
|
131
|
+
destroySession(sessionId) {
|
|
132
|
+
const session = this.sessions.get(sessionId);
|
|
133
|
+
if (!session) {
|
|
134
|
+
// Disparar hook cuando se intenta destruir una sesión inexistente
|
|
135
|
+
this.hooks.doAction('session_destroy_failed', sessionId);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const result = this.sessions.delete(sessionId);
|
|
140
|
+
|
|
141
|
+
if (result) {
|
|
142
|
+
// Disparar hook después de destruir la sesión
|
|
143
|
+
this.hooks.doAction('session_destroyed', sessionId, session.data);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Middleware de sesión
|
|
151
|
+
* @returns {Function} - Middleware de sesión
|
|
152
|
+
*/
|
|
153
|
+
middleware() {
|
|
154
|
+
return (req, res, next) => {
|
|
155
|
+
// Disparar hook antes de procesar la sesión
|
|
156
|
+
this.hooks.doAction('session_middleware_before', req, res);
|
|
157
|
+
|
|
158
|
+
// Obtener ID de sesión de la cookie
|
|
159
|
+
const cookies = this.parseCookies(req.headers.cookie || '');
|
|
160
|
+
const sessionId = cookies[this.cookieName];
|
|
161
|
+
|
|
162
|
+
// Agregar métodos de sesión a la solicitud
|
|
163
|
+
req.session = {
|
|
164
|
+
id: sessionId,
|
|
165
|
+
data: sessionId ? this.getSession(sessionId) : null,
|
|
166
|
+
create: (userData) => {
|
|
167
|
+
// Permitir que otros módulos modifiquen los datos antes de crear la sesión
|
|
168
|
+
const processedUserData = this.hooks.applyFilters('session_create_user_data', userData, req);
|
|
169
|
+
|
|
170
|
+
const newSessionId = this.createSession(processedUserData);
|
|
171
|
+
req.session.id = newSessionId;
|
|
172
|
+
req.session.data = processedUserData;
|
|
173
|
+
|
|
174
|
+
// Establecer cookie con el ID de sesión
|
|
175
|
+
res.setHeader('Set-Cookie', `${this.cookieName}=${newSessionId}; HttpOnly; Path=/; Max-Age=${this.timeout / 1000}`);
|
|
176
|
+
|
|
177
|
+
// Disparar hook después de crear la sesión
|
|
178
|
+
this.hooks.doAction('session_created_response', req, res, newSessionId);
|
|
179
|
+
|
|
180
|
+
return newSessionId;
|
|
181
|
+
},
|
|
182
|
+
update: (newData) => {
|
|
183
|
+
if (!req.session.id) return false;
|
|
184
|
+
|
|
185
|
+
// Permitir que otros módulos modifiquen los datos antes de actualizar
|
|
186
|
+
const processedNewData = this.hooks.applyFilters('session_update_user_data', newData, req, req.session.id);
|
|
187
|
+
|
|
188
|
+
const result = this.updateSession(req.session.id, processedNewData);
|
|
189
|
+
|
|
190
|
+
// Disparar hook después de actualizar la sesión
|
|
191
|
+
if (result) {
|
|
192
|
+
this.hooks.doAction('session_updated_response', req, res, req.session.id);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return result;
|
|
196
|
+
},
|
|
197
|
+
destroy: () => {
|
|
198
|
+
if (!req.session.id) return false;
|
|
199
|
+
|
|
200
|
+
// Disparar hook antes de destruir la sesión
|
|
201
|
+
this.hooks.doAction('session_destroy_before', req, res, req.session.id);
|
|
202
|
+
|
|
203
|
+
// Borrar cookie
|
|
204
|
+
res.setHeader('Set-Cookie', `${this.cookieName}=; HttpOnly; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:01 GMT`);
|
|
205
|
+
|
|
206
|
+
const result = this.destroySession(req.session.id);
|
|
207
|
+
req.session.id = null;
|
|
208
|
+
req.session.data = null;
|
|
209
|
+
|
|
210
|
+
// Disparar hook después de destruir la sesión
|
|
211
|
+
if (result) {
|
|
212
|
+
this.hooks.doAction('session_destroyed_response', req, res);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return result;
|
|
216
|
+
},
|
|
217
|
+
regenerate: (userData) => {
|
|
218
|
+
if (req.session.id) {
|
|
219
|
+
this.destroySession(req.session.id);
|
|
220
|
+
}
|
|
221
|
+
return req.session.create(userData);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// Disparar hook después de procesar la sesión
|
|
226
|
+
this.hooks.doAction('session_middleware_after', req, res);
|
|
227
|
+
|
|
228
|
+
// Continuar con el siguiente middleware
|
|
229
|
+
if (next) {
|
|
230
|
+
next();
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Parsea las cookies de la cabecera
|
|
237
|
+
* @param {string} cookieHeader - Cabecera de cookies
|
|
238
|
+
* @returns {Object} - Objeto con las cookies
|
|
239
|
+
*/
|
|
240
|
+
parseCookies(cookieHeader) {
|
|
241
|
+
const cookies = {};
|
|
242
|
+
if (cookieHeader) {
|
|
243
|
+
cookieHeader.split(';').forEach(cookie => {
|
|
244
|
+
const parts = cookie.trim().split('=');
|
|
245
|
+
if (parts.length === 2) {
|
|
246
|
+
cookies[parts[0]] = parts[1];
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return cookies;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Middleware de autenticación basado en sesión
|
|
256
|
+
* @param {SessionManager} sessionManager - Instancia del administrador de sesiones
|
|
257
|
+
* @param {Object} options - Opciones de autenticación
|
|
258
|
+
* @returns {Function} - Middleware de autenticación de sesión
|
|
259
|
+
*/
|
|
260
|
+
function sessionAuth(sessionManager, options = {}) {
|
|
261
|
+
const redirectTo = options.redirectTo || '/login';
|
|
262
|
+
const failureMessage = options.failureMessage || 'Acceso no autorizado. Por favor inicie sesión.';
|
|
263
|
+
|
|
264
|
+
return (req, res, next) => {
|
|
265
|
+
// Disparar hook antes de verificar la autenticación
|
|
266
|
+
sessionManager.hooks.doAction('session_auth_check_before', req, res);
|
|
267
|
+
|
|
268
|
+
// Verificar si hay una sesión activa
|
|
269
|
+
if (!req.session || !req.session.data || !req.session.data.authenticated) {
|
|
270
|
+
// Disparar hook cuando la autenticación falla
|
|
271
|
+
sessionManager.hooks.doAction('session_auth_failed', req, res, redirectTo);
|
|
272
|
+
|
|
273
|
+
// Si es una solicitud AJAX o API, devolver error JSON
|
|
274
|
+
if (req.headers['x-requested-with'] === 'XMLHttpRequest' ||
|
|
275
|
+
req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
|
|
276
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
277
|
+
res.end(JSON.stringify({
|
|
278
|
+
success: false,
|
|
279
|
+
message: failureMessage,
|
|
280
|
+
redirect: redirectTo
|
|
281
|
+
}));
|
|
282
|
+
return;
|
|
283
|
+
} else {
|
|
284
|
+
// Para solicitudes normales, redirigir al login
|
|
285
|
+
res.writeHead(302, { 'Location': redirectTo });
|
|
286
|
+
res.end();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Disparar hook cuando la autenticación es exitosa
|
|
292
|
+
sessionManager.hooks.doAction('session_auth_success', req, res);
|
|
293
|
+
|
|
294
|
+
// Si hay sesión activa, continuar
|
|
295
|
+
if (next) {
|
|
296
|
+
next();
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
module.exports = { SessionManager, sessionAuth };
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware de validación para el framework API SDK
|
|
3
|
+
* Implementación del componente middleware/validator.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class Validator {
|
|
7
|
+
/**
|
|
8
|
+
* Constructor del validador
|
|
9
|
+
*/
|
|
10
|
+
constructor() {
|
|
11
|
+
this.rules = new Map();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Método para crear un middleware de validación
|
|
16
|
+
* @param {Object} schema - Esquema de validación
|
|
17
|
+
* @returns {Function} - Middleware de validación
|
|
18
|
+
*/
|
|
19
|
+
validate(schema) {
|
|
20
|
+
return (req, res, next) => {
|
|
21
|
+
const errors = this.validateSchema(req, schema);
|
|
22
|
+
|
|
23
|
+
if (errors.length > 0) {
|
|
24
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
25
|
+
res.end(JSON.stringify({
|
|
26
|
+
error: 'Validación fallida',
|
|
27
|
+
details: errors
|
|
28
|
+
}));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (next) {
|
|
33
|
+
next();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Método para validar un esquema contra una solicitud
|
|
40
|
+
* @param {Object} req - Objeto de solicitud HTTP
|
|
41
|
+
* @param {Object} schema - Esquema de validación
|
|
42
|
+
* @returns {Array} - Array de errores de validación
|
|
43
|
+
*/
|
|
44
|
+
validateSchema(req, schema) {
|
|
45
|
+
const errors = [];
|
|
46
|
+
|
|
47
|
+
// Validar campos en query
|
|
48
|
+
if (schema.query) {
|
|
49
|
+
for (const [field, rules] of Object.entries(schema.query)) {
|
|
50
|
+
const value = req.query[field];
|
|
51
|
+
const fieldErrors = this.validateField(value, rules, `query.${field}`);
|
|
52
|
+
errors.push(...fieldErrors);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Validar campos en params
|
|
57
|
+
if (schema.params) {
|
|
58
|
+
for (const [field, rules] of Object.entries(schema.params)) {
|
|
59
|
+
const value = req.params[field];
|
|
60
|
+
const fieldErrors = this.validateField(value, rules, `params.${field}`);
|
|
61
|
+
errors.push(...fieldErrors);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Validar campos en body
|
|
66
|
+
if (schema.body) {
|
|
67
|
+
for (const [field, rules] of Object.entries(schema.body)) {
|
|
68
|
+
const value = req.body[field];
|
|
69
|
+
const fieldErrors = this.validateField(value, rules, `body.${field}`);
|
|
70
|
+
errors.push(...fieldErrors);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return errors;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Método para validar un campo individual
|
|
79
|
+
* @param {*} value - Valor a validar
|
|
80
|
+
* @param {Object|Array} rules - Reglas de validación
|
|
81
|
+
* @param {string} fieldName - Nombre del campo para mensajes de error
|
|
82
|
+
* @returns {Array} - Array de errores de validación para este campo
|
|
83
|
+
*/
|
|
84
|
+
validateField(value, rules, fieldName) {
|
|
85
|
+
const errors = [];
|
|
86
|
+
|
|
87
|
+
// Si las reglas son un array, procesar cada regla
|
|
88
|
+
const ruleList = Array.isArray(rules) ? rules : [rules];
|
|
89
|
+
|
|
90
|
+
for (const rule of ruleList) {
|
|
91
|
+
// Si la regla es una cadena, interpretar como regla predefinida
|
|
92
|
+
if (typeof rule === 'string') {
|
|
93
|
+
const [ruleName, ...params] = rule.split(':');
|
|
94
|
+
const validationFn = this.getValidationRule(ruleName);
|
|
95
|
+
|
|
96
|
+
if (validationFn) {
|
|
97
|
+
const isValid = validationFn(value, ...params);
|
|
98
|
+
if (!isValid) {
|
|
99
|
+
errors.push(`${fieldName} no cumple con la regla: ${ruleName}`);
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
errors.push(`Regla de validación desconocida: ${ruleName}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Si la regla es una función, ejecutarla directamente
|
|
106
|
+
else if (typeof rule === 'function') {
|
|
107
|
+
try {
|
|
108
|
+
const isValid = rule(value);
|
|
109
|
+
if (!isValid) {
|
|
110
|
+
errors.push(`${fieldName} no pasó la validación personalizada`);
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
errors.push(`${fieldName} causó un error en la validación: ${error.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Si la regla es un objeto, asumir que es un esquema anidado
|
|
117
|
+
else if (typeof rule === 'object') {
|
|
118
|
+
if (typeof value === 'object' && value !== null) {
|
|
119
|
+
const nestedErrors = this.validateSchema({ body: value }, { body: rule });
|
|
120
|
+
errors.push(...nestedErrors.map(error => `${fieldName}.${error}`));
|
|
121
|
+
} else {
|
|
122
|
+
errors.push(`${fieldName} debe ser un objeto`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return errors;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Método para obtener una regla de validación por nombre
|
|
132
|
+
* @param {string} ruleName - Nombre de la regla de validación
|
|
133
|
+
* @returns {Function|null} - Función de validación o null si no existe
|
|
134
|
+
*/
|
|
135
|
+
getValidationRule(ruleName) {
|
|
136
|
+
switch (ruleName) {
|
|
137
|
+
case 'required':
|
|
138
|
+
return (value) => value !== undefined && value !== null && value !== '';
|
|
139
|
+
case 'string':
|
|
140
|
+
return (value) => typeof value === 'string';
|
|
141
|
+
case 'number':
|
|
142
|
+
return (value) => typeof value === 'number' && !isNaN(value);
|
|
143
|
+
case 'boolean':
|
|
144
|
+
return (value) => typeof value === 'boolean';
|
|
145
|
+
case 'email':
|
|
146
|
+
return (value) => typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
147
|
+
case 'minLength':
|
|
148
|
+
return (value, minLength) => typeof value === 'string' && value.length >= parseInt(minLength);
|
|
149
|
+
case 'maxLength':
|
|
150
|
+
return (value, maxLength) => typeof value === 'string' && value.length <= parseInt(maxLength);
|
|
151
|
+
case 'min':
|
|
152
|
+
return (value, min) => typeof value === 'number' && value >= parseFloat(min);
|
|
153
|
+
case 'max':
|
|
154
|
+
return (value, max) => typeof value === 'number' && value <= parseFloat(max);
|
|
155
|
+
case 'regex':
|
|
156
|
+
return (value, pattern) => {
|
|
157
|
+
try {
|
|
158
|
+
const regex = new RegExp(pattern);
|
|
159
|
+
return typeof value === 'string' && regex.test(value);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
case 'array':
|
|
165
|
+
return (value) => Array.isArray(value);
|
|
166
|
+
case 'object':
|
|
167
|
+
return (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
168
|
+
default:
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Método para registrar una regla de validación personalizada
|
|
175
|
+
* @param {string} name - Nombre de la regla
|
|
176
|
+
* @param {Function} validationFn - Función de validación
|
|
177
|
+
*/
|
|
178
|
+
addRule(name, validationFn) {
|
|
179
|
+
this.rules.set(name, validationFn);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Método para validar un valor con reglas específicas
|
|
184
|
+
* @param {*} value - Valor a validar
|
|
185
|
+
* @param {Array} rules - Array de reglas de validación
|
|
186
|
+
* @returns {Array} - Array de errores de validación
|
|
187
|
+
*/
|
|
188
|
+
validateValue(value, rules) {
|
|
189
|
+
return this.validateField(value, rules, 'value');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = Validator;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Controlador base para el framework API SDK
|
|
3
|
+
* Implementación del componente MVC controllerBase.js
|
|
4
|
+
* Similar al sistema de controladores de CodeIgniter
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const ViewEngine = require('./viewEngine');
|
|
8
|
+
|
|
9
|
+
class ControllerBase {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
// Inicializar el motor de vistas
|
|
12
|
+
this.viewEngine = new ViewEngine({
|
|
13
|
+
viewsPath: options.viewsPath || './views',
|
|
14
|
+
defaultExtension: options.defaultExtension || '.html',
|
|
15
|
+
cacheEnabled: options.cacheEnabled
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Inicializar variables de datos para la vista
|
|
19
|
+
this.viewData = {};
|
|
20
|
+
|
|
21
|
+
// Referencia al objeto de solicitud y respuesta (cuando esté disponible)
|
|
22
|
+
this.req = null;
|
|
23
|
+
this.res = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Establece variables para pasar a la vista
|
|
28
|
+
* @param {string|Object} key - Nombre de la variable o objeto con múltiples variables
|
|
29
|
+
* @param {*} value - Valor de la variable (si key es string)
|
|
30
|
+
*/
|
|
31
|
+
set(key, value) {
|
|
32
|
+
if (typeof key === 'object') {
|
|
33
|
+
// Si se pasa un objeto, fusionar con viewData
|
|
34
|
+
this.viewData = { ...this.viewData, ...key };
|
|
35
|
+
} else {
|
|
36
|
+
// Si se pasa una clave y valor, asignar individualmente
|
|
37
|
+
this.viewData[key] = value;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Renderiza una vista con las variables actuales
|
|
43
|
+
* @param {string} viewName - Nombre de la vista a renderizar
|
|
44
|
+
* @param {Object} additionalData - Datos adicionales para pasar a la vista
|
|
45
|
+
* @param {Object} options - Opciones adicionales
|
|
46
|
+
*/
|
|
47
|
+
view(viewName, additionalData = {}, options = {}) {
|
|
48
|
+
// Fusionar datos de la vista con datos adicionales
|
|
49
|
+
const data = { ...this.viewData, ...additionalData };
|
|
50
|
+
|
|
51
|
+
// Renderizar la vista
|
|
52
|
+
const renderedView = this.viewEngine.render(viewName, data, options);
|
|
53
|
+
|
|
54
|
+
return renderedView;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Renderiza una vista y la envía como respuesta HTTP
|
|
59
|
+
* @param {Object} res - Objeto de respuesta HTTP
|
|
60
|
+
* @param {string} viewName - Nombre de la vista a renderizar
|
|
61
|
+
* @param {Object} additionalData - Datos adicionales para pasar a la vista
|
|
62
|
+
* @param {Object} options - Opciones adicionales
|
|
63
|
+
*/
|
|
64
|
+
render(res, viewName, additionalData = {}, options = {}) {
|
|
65
|
+
try {
|
|
66
|
+
// Renderizar la vista
|
|
67
|
+
const renderedView = this.view(viewName, additionalData, options);
|
|
68
|
+
|
|
69
|
+
// Enviar la vista renderizada como respuesta
|
|
70
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
71
|
+
res.end(renderedView);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('Error renderizando vista:', error);
|
|
74
|
+
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
75
|
+
res.end('Error interno del servidor');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Renderiza una vista parcial (sin layout)
|
|
81
|
+
* @param {string} viewName - Nombre de la vista parcial
|
|
82
|
+
* @param {Object} additionalData - Datos adicionales para pasar a la vista
|
|
83
|
+
* @returns {string} - Vista parcial renderizada
|
|
84
|
+
*/
|
|
85
|
+
partial(viewName, additionalData = {}) {
|
|
86
|
+
// Fusionar datos de la vista con datos adicionales
|
|
87
|
+
const data = { ...this.viewData, ...additionalData };
|
|
88
|
+
|
|
89
|
+
// Renderizar la vista parcial
|
|
90
|
+
return this.viewEngine.render(viewName, data);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Redirecciona a otra URL
|
|
95
|
+
* @param {Object} res - Objeto de respuesta HTTP
|
|
96
|
+
* @param {string} url - URL a la que redireccionar
|
|
97
|
+
*/
|
|
98
|
+
redirect(res, url) {
|
|
99
|
+
res.writeHead(302, { 'Location': url });
|
|
100
|
+
res.end();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Devuelve una respuesta JSON
|
|
105
|
+
* @param {Object} res - Objeto de respuesta HTTP
|
|
106
|
+
* @param {Object} data - Datos a enviar como JSON
|
|
107
|
+
* @param {number} statusCode - Código de estado HTTP
|
|
108
|
+
*/
|
|
109
|
+
json(res, data, statusCode = 200) {
|
|
110
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
111
|
+
res.end(JSON.stringify(data));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Obtiene un valor de la solicitud (query, body o params)
|
|
116
|
+
* @param {string} key - Clave del valor a obtener
|
|
117
|
+
* @param {*} defaultValue - Valor por defecto si no se encuentra
|
|
118
|
+
* @returns {*} - Valor obtenido o valor por defecto
|
|
119
|
+
*/
|
|
120
|
+
input(key, defaultValue = null) {
|
|
121
|
+
if (!this.req) {
|
|
122
|
+
return defaultValue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Buscar en body, query y params
|
|
126
|
+
if (this.req.body && typeof this.req.body === 'object' && key in this.req.body) {
|
|
127
|
+
return this.req.body[key];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (this.req.query && typeof this.req.query === 'object' && key in this.req.query) {
|
|
131
|
+
return this.req.query[key];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (this.req.params && typeof this.req.params === 'object' && key in this.req.params) {
|
|
135
|
+
return this.req.params[key];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return defaultValue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Obtiene todos los valores de entrada
|
|
143
|
+
* @returns {Object} - Todos los valores de entrada combinados
|
|
144
|
+
*/
|
|
145
|
+
allInput() {
|
|
146
|
+
if (!this.req) {
|
|
147
|
+
return {};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
...this.getBody(),
|
|
152
|
+
...this.getQuery(),
|
|
153
|
+
...this.getParams()
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Obtiene los parámetros de consulta (query)
|
|
159
|
+
* @returns {Object} - Parámetros de consulta
|
|
160
|
+
*/
|
|
161
|
+
getQuery() {
|
|
162
|
+
return this.req?.query || {};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Obtiene el cuerpo de la solicitud (body)
|
|
167
|
+
* @returns {Object} - Cuerpo de la solicitud
|
|
168
|
+
*/
|
|
169
|
+
getBody() {
|
|
170
|
+
return this.req?.body || {};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Obtiene los parámetros de ruta (params)
|
|
175
|
+
* @returns {Object} - Parámetros de ruta
|
|
176
|
+
*/
|
|
177
|
+
getParams() {
|
|
178
|
+
return this.req?.params || {};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Establece la solicitud y respuesta actuales
|
|
183
|
+
* @param {Object} req - Objeto de solicitud HTTP
|
|
184
|
+
* @param {Object} res - Objeto de respuesta HTTP
|
|
185
|
+
*/
|
|
186
|
+
setRequestResponse(req, res) {
|
|
187
|
+
this.req = req;
|
|
188
|
+
this.res = res;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Obtiene la instancia del motor de vistas
|
|
193
|
+
* @returns {ViewEngine} - Instancia del motor de vistas
|
|
194
|
+
*/
|
|
195
|
+
getViewEngine() {
|
|
196
|
+
return this.viewEngine;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Limpia las variables de vista
|
|
201
|
+
*/
|
|
202
|
+
clearViewData() {
|
|
203
|
+
this.viewData = {};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = ControllerBase;
|