blackcoffee2 2.1.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/CHANGELOG.md +664 -0
- package/LICENSE +201 -0
- package/NOTICE +25 -0
- package/README.md +246 -0
- package/apps.zip +0 -0
- package/bin/adminclient +105 -0
- package/bin/blackcoffee +133 -0
- package/cli/admin-users.js +282 -0
- package/cli/commands/app.js +561 -0
- package/cli/commands/config.js +182 -0
- package/cli/commands/db.js +257 -0
- package/cli/commands/server.js +200 -0
- package/config/applications.json +5 -0
- package/config/database.json +28 -0
- package/config/database.json.example +23 -0
- package/config/server.json +32 -0
- package/controllers/admin/AdminController.js +529 -0
- package/controllers/admin/AdminViewController.js +90 -0
- package/controllers/admin/AuthController.js +293 -0
- package/controllers/admin/DatabaseAdminController.js +218 -0
- package/core/SQLiteAdapter.js +333 -0
- package/core/appLoader.js +385 -0
- package/core/databasePoolManager.js +431 -0
- package/core/hotReload.js +363 -0
- package/data/ADMIN-README.md +145 -0
- package/data/CHANGELOG.md +48 -0
- package/data/GTK3-NODE-PROPOSALS.md +410 -0
- package/data/admin-db.js +150 -0
- package/data/admin-gui.js +452 -0
- package/data/blackcoffee_admin.db-shm +0 -0
- package/data/blackcoffee_admin.db-wal +0 -0
- package/data/migrations/001_create_admin_users.sql +33 -0
- package/docs/APP_HOOKS_HANDLER.md +432 -0
- package/docs/APP_HOOKS_REQUIREMENTS.md +588 -0
- package/docs/ARCHITECTURE.md +435 -0
- package/docs/CREAR_APP_Y_USAR_POOLS.md +1595 -0
- package/docs/EVENTS_APP_MANUAL.md +289 -0
- package/docs/INSITU_BINARY_UPLOAD_PROPOSAL.md +186 -0
- package/docs/INSITU_FIREWALL_EXCEPTION.md +187 -0
- package/docs/ROADMAP.md +242 -0
- package/docs/ROADMAP.md.backup +243 -0
- package/includes/404-hooks.js +423 -0
- package/includes/adminAuth.js +214 -0
- package/includes/adminExtension.js +53 -0
- package/includes/appHooks.js +302 -0
- package/includes/initAdminDb.js +115 -0
- package/includes/routeLoader.js +67 -0
- package/includes/sessions.js +223 -0
- package/issues/001-duplicate-module-loading.md +92 -0
- package/manuales/ADMIN_EXTENSION_COMMANDS_MANUAL.md +261 -0
- package/manuales/ADMIN_EXTENSION_HOOK_EXAMPLE.md +28 -0
- package/manuales/ADMIN_EXTENSION_INTEGRATION_MANUAL.md +232 -0
- package/manuales/CACHE_REGEX_COMMANDS.md +136 -0
- package/manuales/CACHE_SYSTEM_MAP.md +206 -0
- package/manuales/CREACION_DE_CONTROLADORES_INSITU.md +383 -0
- package/manuales/QUEUE_CLI_MODULE_MANUAL.md +289 -0
- package/manuales/QUEUE_SYSTEM_MANUAL.md +320 -0
- package/manuales/ROUTE_CACHE_MODULE_MANUAL.md +205 -0
- package/manuales/SESSION_MANAGER_GUIDE.md +529 -0
- package/manuales/SESSION_SECURITY_FLAGS.md +174 -0
- package/manuales/WAF_MODULE_MANUAL.md +229 -0
- package/manuales/after_route_handler_filter_example.md +116 -0
- package/manuales/after_route_handler_usage.md +130 -0
- package/manuales/an/303/241lisis-completo-insitu-framework.md +213 -0
- package/manuales/async_hooks_promises_guide.md +325 -0
- package/manuales/before_route_handler_filter_example.md +97 -0
- package/manuales/before_route_handler_usage.md +122 -0
- package/manuales/hooks_chaining_conditions_guide.md +261 -0
- package/manuales/hooks_filters_documentation.md +493 -0
- package/manuales/hooks_filters_documentation_en.md +493 -0
- package/manuales/hooks_vs_middlewares_comparison.md +87 -0
- package/manuales/manual-mvc-completo.md +934 -0
- package/manuales/modulos_administracion.md +89 -0
- package/manuales/router_execution_points.md +74 -0
- package/manuales/static_file_hooks_usage.md +222 -0
- package/models/AdminUserModel.js +132 -0
- package/package.json +45 -0
- package/programatically/PRoutes.js +89 -0
- package/programatically/initFlow.js +211 -0
- package/public/admin/css/db-pools.css +336 -0
- package/public/admin/css/styles.css +310 -0
- package/public/admin/database.html +312 -0
- package/public/admin/index.html +116 -0
- package/public/admin/js/app.js +470 -0
- package/public/admin/js/db-pools.js +253 -0
- package/public/admin/login.html +278 -0
- package/public/assets/css/styles.css +477 -0
- package/public/assets/js/main.js +89 -0
- package/public/index.html +136 -0
- package/public/templates/404.html +158 -0
- package/routes/admin-views.json +20 -0
- package/routes/admin.json +38 -0
- package/routes/auth.json +32 -0
- package/routes/static.json +18 -0
- package/server.js +299 -0
- package/test-aplicacion.con-logisession/BlackCoffee.js +226 -0
- package/test-aplicacion.con-logisession/SSL_SETUP.md +53 -0
- package/test-aplicacion.con-logisession/certs/ca-certificate.pem +32 -0
- package/test-aplicacion.con-logisession/certs/ca-private-key.pem +52 -0
- package/test-aplicacion.con-logisession/certs/certificate-2048.pem +22 -0
- package/test-aplicacion.con-logisession/certs/certificate.pem +32 -0
- package/test-aplicacion.con-logisession/certs/private-key-2048.pem +28 -0
- package/test-aplicacion.con-logisession/certs/private-key.pem +52 -0
- package/test-aplicacion.con-logisession/config/iaQueueSetup.js +84 -0
- package/test-aplicacion.con-logisession/config/qwen-rules.json +39 -0
- package/test-aplicacion.con-logisession/controllers/analyticsController.js +117 -0
- package/test-aplicacion.con-logisession/controllers/auth/AdminAuthController.js +142 -0
- package/test-aplicacion.con-logisession/controllers/auth/AuthController.js +439 -0
- package/test-aplicacion.con-logisession/controllers/auth/AuthViewController.js +223 -0
- package/test-aplicacion.con-logisession/controllers/endpointController.js +66 -0
- package/test-aplicacion.con-logisession/controllers/example.js +183 -0
- package/test-aplicacion.con-logisession/controllers/iaQueueController.js +367 -0
- package/test-aplicacion.con-logisession/controllers/queueController.js +206 -0
- package/test-aplicacion.con-logisession/controllers/qwenQueueController.js +197 -0
- package/test-aplicacion.con-logisession/controllers/test.js +0 -0
- package/test-aplicacion.con-logisession/controllers/tracking/EventsNoFinishController.js +78 -0
- package/test-aplicacion.con-logisession/controllers/tracking/TrackingController.js +412 -0
- package/test-aplicacion.con-logisession/controllers/tracking/TrackingControllerWithLoadModel.js +437 -0
- package/test-aplicacion.con-logisession/hooks/admin-hooks.js +20 -0
- package/test-aplicacion.con-logisession/hooks/general-hooks.js +97 -0
- package/test-aplicacion.con-logisession/hooks/queue-hooks.js +64 -0
- package/test-aplicacion.con-logisession/hooks/route-directory-hooks.js +38 -0
- package/test-aplicacion.con-logisession/hooks/security-hooks.js +24 -0
- package/test-aplicacion.con-logisession/insitu-admin-client/README.md +69 -0
- package/test-aplicacion.con-logisession/insitu-admin-client/package.json +23 -0
- package/test-aplicacion.con-logisession/insitu-admin-client.js +257 -0
- package/test-aplicacion.con-logisession/models/ExampleModel.js +88 -0
- package/test-aplicacion.con-logisession/models/QueueJobModel.js +263 -0
- package/test-aplicacion.con-logisession/models/TokenModel.js +207 -0
- package/test-aplicacion.con-logisession/models/auth/AuthModel.js +66 -0
- package/test-aplicacion.con-logisession/models/auth/UserModel.js +189 -0
- package/test-aplicacion.con-logisession/models/tracking/CompletedCartModel.js +213 -0
- package/test-aplicacion.con-logisession/models/tracking/EventModel.js +366 -0
- package/test-aplicacion.con-logisession/models/tracking/EventsNoFinishModel.js +131 -0
- package/test-aplicacion.con-logisession/models/tracking/SessionModel.js +360 -0
- package/test-aplicacion.con-logisession/models/tracking/SiteFlowModel.js +286 -0
- package/test-aplicacion.con-logisession/models/tracking/TokenModel.js +207 -0
- package/test-aplicacion.con-logisession/package-lock.json +3313 -0
- package/test-aplicacion.con-logisession/package.json +32 -0
- package/test-aplicacion.con-logisession/public/blackcoffee-welcome/index.html +1339 -0
- package/test-aplicacion.con-logisession/public/css/style.css +64 -0
- package/test-aplicacion.con-logisession/public/ejemplo-estatica/index.html +18 -0
- package/test-aplicacion.con-logisession/public/ejemplo-estatica/script.js +16 -0
- package/test-aplicacion.con-logisession/public/ejemplo-estatica/styles.css +43 -0
- package/test-aplicacion.con-logisession/public/images/logo.svg +7 -0
- package/test-aplicacion.con-logisession/public/js/main.js +67 -0
- package/test-aplicacion.con-logisession/routes/analytics-routes.json +8 -0
- package/test-aplicacion.con-logisession/routes/auth-routes.json +98 -0
- package/test-aplicacion.con-logisession/routes/blackcoffee-welcome-routes.json +20 -0
- package/test-aplicacion.con-logisession/routes/duplicate-test-routes.json.disabled +16 -0
- package/test-aplicacion.con-logisession/routes/ejemplo-estatica-routes.json +11 -0
- package/test-aplicacion.con-logisession/routes/endpoints-routes.json +8 -0
- package/test-aplicacion.con-logisession/routes/ia-queue-routes.json +26 -0
- package/test-aplicacion.con-logisession/routes/product-routes.json.disabled +20 -0
- package/test-aplicacion.con-logisession/routes/queue-routes.json +32 -0
- package/test-aplicacion.con-logisession/routes/qwen-routes.json +14 -0
- package/test-aplicacion.con-logisession/routes/static-routes.json +29 -0
- package/test-aplicacion.con-logisession/routes/tracking-routes.json +58 -0
- package/test-aplicacion.con-logisession/routes/tracking-with-loadmodel-routes.json +51 -0
- package/test-aplicacion.con-logisession/utils/dbAdapter.js +88 -0
- package/test-aplicacion.con-logisession/utils/qbWrapper.js +4 -0
- package/test-aplicacion.con-logisession/utils/queueProcessor.js +305 -0
- package/test-aplicacion.con-logisession/utils/qwenRulesService.js +131 -0
- package/test-aplicacion.con-logisession/utils/tokenHelper.js +22 -0
- package/test-aplicacion.con-logisession/views/auth/dashboard.html +443 -0
- package/test-aplicacion.con-logisession/views/auth/forgot-password.html +200 -0
- package/test-aplicacion.con-logisession/views/auth/login.html +213 -0
- package/test-aplicacion.con-logisession/views/auth/register.html +294 -0
- package/test-aplicacion.con-logisession/views/contact/form.html +47 -0
- package/test-aplicacion.con-logisession/views/products/index.html +39 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hooks para manejo de errores 404 en BlackCoffee
|
|
3
|
+
* Utiliza el sistema de hooks de Insitu Framework
|
|
4
|
+
*
|
|
5
|
+
* Maneja dos tipos de errores 404:
|
|
6
|
+
* 1. Ruta no encontrada (endpoints)
|
|
7
|
+
* 2. Archivo estático no encontrado
|
|
8
|
+
*
|
|
9
|
+
* Sirve un template HTML personalizado desde public/templates/404.html
|
|
10
|
+
*
|
|
11
|
+
* Uso:
|
|
12
|
+
* const hooks404 = require('./includes/404-hooks.js');
|
|
13
|
+
* hooks404.register(server.hooks);
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
// Cache del template 404
|
|
20
|
+
let cached404Template = null;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Carga el template 404 desde el archivo
|
|
24
|
+
* @returns {string} - Contenido del template HTML
|
|
25
|
+
*/
|
|
26
|
+
function load404Template() {
|
|
27
|
+
if (cached404Template) {
|
|
28
|
+
return cached404Template;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const templatePath = path.join(__dirname, '../public/templates/404.html');
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
cached404Template = fs.readFileSync(templatePath, 'utf8');
|
|
35
|
+
console.log('✅ Template 404 cargado en caché');
|
|
36
|
+
return cached404Template;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.warn('⚠️ No se pudo cargar el template 404:', error.message);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Registra los hooks para manejo de errores 404
|
|
45
|
+
* @param {Object} hooks - Sistema de hooks de Insitu
|
|
46
|
+
*/
|
|
47
|
+
function register(hooks) {
|
|
48
|
+
if (!hooks) {
|
|
49
|
+
console.warn('⚠️ No se proporcionó el sistema de hooks');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log('🔗 Registrando hooks para manejo 404...');
|
|
54
|
+
|
|
55
|
+
// Cargar template 404
|
|
56
|
+
load404Template();
|
|
57
|
+
|
|
58
|
+
// ============================================
|
|
59
|
+
// FILTROS (Filters) - Permiten modificar datos
|
|
60
|
+
// ============================================
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Filtro: custom_404_message
|
|
64
|
+
* Permite personalizar el mensaje de error 404
|
|
65
|
+
* Este es el filtro principal para modificar la respuesta
|
|
66
|
+
*/
|
|
67
|
+
hooks.addFilter('custom_404_message', (errorData, req, res, type) => {
|
|
68
|
+
if (type === 'file') {
|
|
69
|
+
errorData.message = 'Archivo no encontrado';
|
|
70
|
+
errorData.hint = 'Verifica la ruta del archivo estático';
|
|
71
|
+
} else {
|
|
72
|
+
errorData.message = 'Endpoint no encontrado';
|
|
73
|
+
errorData.hint = 'Verifica la URL e intenta nuevamente';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
errorData.timestamp = new Date().toISOString();
|
|
77
|
+
errorData.blackcoffee = 'BlackCoffee v1.1.0';
|
|
78
|
+
|
|
79
|
+
return errorData;
|
|
80
|
+
}, 10);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Filtro: pre_route_not_found
|
|
84
|
+
* Se ejecuta antes de devolver un 404 de ruta
|
|
85
|
+
*/
|
|
86
|
+
hooks.addFilter('pre_route_not_found', (data, req, res) => {
|
|
87
|
+
return data;
|
|
88
|
+
}, 10);
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Filtro: customize_404_response
|
|
92
|
+
* Filtro PRINCIPAL para servir template HTML 404
|
|
93
|
+
* Si shouldSendDefaultResponse = false, evita la respuesta JSON por defecto
|
|
94
|
+
* y sirve el template HTML personalizado
|
|
95
|
+
*/
|
|
96
|
+
hooks.addFilter('customize_404_response', (data, pathname, req, res) => {
|
|
97
|
+
const template = load404Template();
|
|
98
|
+
|
|
99
|
+
if (template && req && res) {
|
|
100
|
+
// Preparar datos del error
|
|
101
|
+
const errorData = {
|
|
102
|
+
type: 'route',
|
|
103
|
+
path: pathname,
|
|
104
|
+
method: req.method,
|
|
105
|
+
timestamp: new Date().toISOString()
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Reemplazar placeholders en el template
|
|
109
|
+
let html = template;
|
|
110
|
+
html = html.replace(
|
|
111
|
+
"window.errorData = {};",
|
|
112
|
+
`window.errorData = ${JSON.stringify(errorData)};`
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Enviar respuesta HTML
|
|
116
|
+
res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
117
|
+
res.end(html);
|
|
118
|
+
|
|
119
|
+
// Evitar respuesta por defecto
|
|
120
|
+
data.shouldSendDefaultResponse = false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return data;
|
|
124
|
+
}, 5); // Prioridad alta para que se ejecute primero
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Filtro: customize_static_404_response
|
|
128
|
+
* Filtro para servir template HTML 404 para archivos estáticos
|
|
129
|
+
* Si shouldSendDefaultResponse = false, evita la respuesta JSON por defecto
|
|
130
|
+
* y sirve el template HTML personalizado
|
|
131
|
+
*/
|
|
132
|
+
hooks.addFilter('customize_static_404_response', (data, filePath, req, res) => {
|
|
133
|
+
const template = load404Template();
|
|
134
|
+
|
|
135
|
+
// Usar filePath de data si el directo es undefined
|
|
136
|
+
const actualFilePath = filePath || data?.filePath;
|
|
137
|
+
const actualReq = req || data?.req;
|
|
138
|
+
const actualRes = res || data?.res;
|
|
139
|
+
|
|
140
|
+
if (template && actualReq && actualRes && actualFilePath) {
|
|
141
|
+
// Preparar datos del error para archivo estático
|
|
142
|
+
const errorData = {
|
|
143
|
+
type: 'file',
|
|
144
|
+
path: actualFilePath,
|
|
145
|
+
method: actualReq.method || 'GET',
|
|
146
|
+
url: actualReq.url || actualFilePath,
|
|
147
|
+
timestamp: new Date().toISOString()
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Reemplazar placeholders en el template
|
|
151
|
+
let html = template;
|
|
152
|
+
html = html.replace(
|
|
153
|
+
"window.errorData = {};",
|
|
154
|
+
`window.errorData = ${JSON.stringify(errorData)};`
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Enviar respuesta HTML
|
|
158
|
+
actualRes.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
159
|
+
actualRes.end(html);
|
|
160
|
+
|
|
161
|
+
// Evitar respuesta por defecto
|
|
162
|
+
data.shouldSendDefaultResponse = false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return data;
|
|
166
|
+
}, 5); // Prioridad alta para que se ejecute primero
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Filtro: route_not_found_response
|
|
170
|
+
* Permite modificar la respuesta 404 antes de enviarla
|
|
171
|
+
*/
|
|
172
|
+
hooks.addFilter('route_not_found_response', (response, req, res, path) => {
|
|
173
|
+
const customResponse = hooks.applyFilters(
|
|
174
|
+
'custom_404_message',
|
|
175
|
+
response,
|
|
176
|
+
req,
|
|
177
|
+
res,
|
|
178
|
+
'route'
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
return customResponse;
|
|
182
|
+
}, 10);
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Filtro: static_file_not_found_response
|
|
186
|
+
* Permite modificar la respuesta 404 para archivos estáticos
|
|
187
|
+
* NOTA: Este hook se ejecuta DESPUÉS de sendNotFoundResponse,
|
|
188
|
+
* por lo que no podemos evitar la respuesta JSON.
|
|
189
|
+
* Se usa solo para logging/métricas.
|
|
190
|
+
*/
|
|
191
|
+
hooks.addFilter('static_file_not_found_response', (response, req, res, filePath) => {
|
|
192
|
+
const customResponse = hooks.applyFilters(
|
|
193
|
+
'custom_404_message',
|
|
194
|
+
response,
|
|
195
|
+
req,
|
|
196
|
+
res,
|
|
197
|
+
'file'
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
customResponse.requestedFile = filePath;
|
|
201
|
+
|
|
202
|
+
return customResponse;
|
|
203
|
+
}, 10);
|
|
204
|
+
|
|
205
|
+
// ============================================
|
|
206
|
+
// FUNCIONES AUXILIARES
|
|
207
|
+
// ============================================
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Obtiene la IP del cliente de forma segura
|
|
211
|
+
*/
|
|
212
|
+
function getIP(req) {
|
|
213
|
+
if (!req) return 'N/A';
|
|
214
|
+
return req.connection?.remoteAddress ||
|
|
215
|
+
req.socket?.remoteAddress ||
|
|
216
|
+
req.headers?.['x-forwarded-for'] ||
|
|
217
|
+
'N/A';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ============================================
|
|
221
|
+
// ACCIONES (Actions) - Efectos secundarios
|
|
222
|
+
// ============================================
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Acción: route_not_found
|
|
226
|
+
* Se ejecuta cuando no se encuentra una ruta (endpoint)
|
|
227
|
+
* Muestra mensaje por stdout
|
|
228
|
+
* Firma del hook: (pathname, req, res)
|
|
229
|
+
*/
|
|
230
|
+
hooks.addAction('route_not_found', (pathname, req, res) => {
|
|
231
|
+
const ip = getIP(req);
|
|
232
|
+
const method = req?.method || 'N/A';
|
|
233
|
+
const userAgent = req?.headers?.['user-agent'] || 'unknown';
|
|
234
|
+
|
|
235
|
+
console.log(`\n❌ [404 - ROUTE] Ruta no encontrada:`);
|
|
236
|
+
console.log(` Método: ${method}`);
|
|
237
|
+
console.log(` Path: ${pathname}`);
|
|
238
|
+
console.log(` IP: ${ip}`);
|
|
239
|
+
console.log(` User-Agent: ${userAgent}`);
|
|
240
|
+
console.log(` Timestamp: ${new Date().toISOString()}\n`);
|
|
241
|
+
|
|
242
|
+
// Registrar en métricas
|
|
243
|
+
trackPath(pathname, 'route');
|
|
244
|
+
}, 10);
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Acción: post_route_not_found
|
|
248
|
+
* Se ejecuta después de enviar la respuesta 404 de ruta
|
|
249
|
+
*/
|
|
250
|
+
hooks.addAction('post_route_not_found', (pathname, req, res) => {
|
|
251
|
+
console.log(`📤 [404 - ROUTE] Respuesta enviada para: ${pathname}`);
|
|
252
|
+
}, 10);
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Acción: static_file_not_found
|
|
256
|
+
* Se ejecuta cuando no se encuentra un archivo estático
|
|
257
|
+
* Muestra mensaje por stdout
|
|
258
|
+
* Firma del hook: (filePath, req, res)
|
|
259
|
+
*/
|
|
260
|
+
hooks.addAction('static_file_not_found', (filePath, req, res) => {
|
|
261
|
+
const ip = getIP(req);
|
|
262
|
+
const method = req?.method || 'N/A';
|
|
263
|
+
const url = req?.url || 'N/A';
|
|
264
|
+
|
|
265
|
+
console.log(`\n❌ [404 - FILE] Archivo no encontrado:`);
|
|
266
|
+
console.log(` Método: ${method}`);
|
|
267
|
+
console.log(` Path solicitado: ${url}`);
|
|
268
|
+
console.log(` Archivo físico: ${filePath}`);
|
|
269
|
+
console.log(` IP: ${ip}`);
|
|
270
|
+
console.log(` Timestamp: ${new Date().toISOString()}\n`);
|
|
271
|
+
|
|
272
|
+
// Registrar en métricas
|
|
273
|
+
trackPath(filePath, 'file');
|
|
274
|
+
}, 10);
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Acción: static_file_access_denied
|
|
278
|
+
* Se ejecuta cuando se deniega acceso a un archivo
|
|
279
|
+
* Firma del hook: (req, res, normalizedPath)
|
|
280
|
+
*/
|
|
281
|
+
hooks.addAction('static_file_access_denied', (req, res, normalizedPath) => {
|
|
282
|
+
console.log(`\n❌ [403 - FILE] Acceso denegado:`);
|
|
283
|
+
console.log(` Archivo: ${normalizedPath}`);
|
|
284
|
+
console.log(` Razón: Path traversal detectado`);
|
|
285
|
+
console.log(` IP: ${req?.connection?.remoteAddress || req?.socket?.remoteAddress || 'N/A'}`);
|
|
286
|
+
console.log(` Timestamp: ${new Date().toISOString()}\n`);
|
|
287
|
+
}, 10);
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Acción: post_static_file_not_found
|
|
291
|
+
* Se ejecuta después de enviar la respuesta 404 de archivo
|
|
292
|
+
*/
|
|
293
|
+
hooks.addAction('post_static_file_not_found', (filePath, req, res) => {
|
|
294
|
+
console.log(`📤 [404 - FILE] Respuesta enviada para: ${filePath}`);
|
|
295
|
+
}, 10);
|
|
296
|
+
|
|
297
|
+
console.log('✅ Hooks 404 registrados exitosamente');
|
|
298
|
+
console.log(' Filtros:');
|
|
299
|
+
console.log(' • custom_404_message (personalizar mensaje)');
|
|
300
|
+
console.log(' • customize_404_response (template HTML para rutas)');
|
|
301
|
+
console.log(' • customize_static_404_response (template HTML para archivos)');
|
|
302
|
+
console.log(' • pre_route_not_found');
|
|
303
|
+
console.log(' • route_not_found_response');
|
|
304
|
+
console.log(' • static_file_not_found_response');
|
|
305
|
+
console.log(' Acciones:');
|
|
306
|
+
console.log(' • route_not_found (stdout)');
|
|
307
|
+
console.log(' • post_route_not_found');
|
|
308
|
+
console.log(' • static_file_not_found (stdout)');
|
|
309
|
+
console.log(' • static_file_access_denied (stdout)');
|
|
310
|
+
console.log(' • post_static_file_not_found\n');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ============================================
|
|
314
|
+
// MÉTRICAS
|
|
315
|
+
// ============================================
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Inicializa el sistema de métricas 404
|
|
319
|
+
*/
|
|
320
|
+
function initMetrics() {
|
|
321
|
+
global.metrics404 = {
|
|
322
|
+
count: 0,
|
|
323
|
+
routes: 0,
|
|
324
|
+
files: 0,
|
|
325
|
+
lastPath: null,
|
|
326
|
+
lastType: null,
|
|
327
|
+
lastTime: null,
|
|
328
|
+
paths: new Map() // Contador por path
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
console.log('📊 Métricas 404 inicializadas');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Registra un path en las métricas
|
|
336
|
+
* @param {string} path - Ruta o archivo solicitado
|
|
337
|
+
* @param {string} type - Tipo: 'route' o 'file'
|
|
338
|
+
*/
|
|
339
|
+
function trackPath(path, type = 'route') {
|
|
340
|
+
if (!global.metrics404) {
|
|
341
|
+
initMetrics();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
global.metrics404.count++;
|
|
345
|
+
|
|
346
|
+
if (type === 'file') {
|
|
347
|
+
global.metrics404.files++;
|
|
348
|
+
} else {
|
|
349
|
+
global.metrics404.routes++;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
global.metrics404.lastPath = path;
|
|
353
|
+
global.metrics404.lastType = type;
|
|
354
|
+
global.metrics404.lastTime = new Date().toISOString();
|
|
355
|
+
|
|
356
|
+
const count = global.metrics404.paths.get(path) || 0;
|
|
357
|
+
global.metrics404.paths.set(path, count + 1);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Obtiene las métricas de errores 404
|
|
362
|
+
* @returns {Object} - Métricas de errores 404
|
|
363
|
+
*/
|
|
364
|
+
function getMetrics() {
|
|
365
|
+
return global.metrics404 || {
|
|
366
|
+
count: 0,
|
|
367
|
+
routes: 0,
|
|
368
|
+
files: 0,
|
|
369
|
+
lastPath: null,
|
|
370
|
+
lastType: null,
|
|
371
|
+
lastTime: null,
|
|
372
|
+
paths: new Map()
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Limpia las métricas de errores 404
|
|
378
|
+
*/
|
|
379
|
+
function clearMetrics() {
|
|
380
|
+
global.metrics404 = {
|
|
381
|
+
count: 0,
|
|
382
|
+
routes: 0,
|
|
383
|
+
files: 0,
|
|
384
|
+
lastPath: null,
|
|
385
|
+
lastType: null,
|
|
386
|
+
lastTime: null,
|
|
387
|
+
paths: new Map()
|
|
388
|
+
};
|
|
389
|
+
console.log('🧹 Métricas 404 limpiadas');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Imprime las métricas por stdout
|
|
394
|
+
*/
|
|
395
|
+
function printMetrics() {
|
|
396
|
+
const metrics = getMetrics();
|
|
397
|
+
console.log('\n📊 === Métricas 404 ===');
|
|
398
|
+
console.log(` Total: ${metrics.count}`);
|
|
399
|
+
console.log(` Rutas: ${metrics.routes}`);
|
|
400
|
+
console.log(` Archivos: ${metrics.files}`);
|
|
401
|
+
console.log(` Último: ${metrics.lastPath} (${metrics.lastType})`);
|
|
402
|
+
console.log(` Hora: ${metrics.lastTime || 'N/A'}`);
|
|
403
|
+
|
|
404
|
+
if (metrics.paths.size > 0) {
|
|
405
|
+
console.log(' Paths más solicitados:');
|
|
406
|
+
const sorted = Array.from(metrics.paths.entries())
|
|
407
|
+
.sort((a, b) => b[1] - a[1])
|
|
408
|
+
.slice(0, 5);
|
|
409
|
+
sorted.forEach(([path, count]) => {
|
|
410
|
+
console.log(` - ${path}: ${count}`);
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
console.log('========================\n');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
module.exports = {
|
|
417
|
+
register,
|
|
418
|
+
initMetrics,
|
|
419
|
+
getMetrics,
|
|
420
|
+
trackPath,
|
|
421
|
+
clearMetrics,
|
|
422
|
+
printMetrics
|
|
423
|
+
};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Módulo de Autenticación y Rutas de Administración para BlackCoffee
|
|
3
|
+
* Maneja SessionManager, hooks de autenticación y rutas del dashboard
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { SessionManager } = require('insitu-js');
|
|
9
|
+
const { hooks } = require('insitu-js');
|
|
10
|
+
const { initDatabase } = require('./initAdminDb');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Middleware de autenticación para proteger rutas admin
|
|
14
|
+
*/
|
|
15
|
+
function authMiddleware() {
|
|
16
|
+
return (req, res, next) => {
|
|
17
|
+
const requestPath = req.url?.split('?')[0];
|
|
18
|
+
|
|
19
|
+
if (!requestPath) {
|
|
20
|
+
return next();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Excepción: recursos estáticos del admin (CSS, JS) - NO requieren autenticación
|
|
24
|
+
if (requestPath.startsWith('/admin/css') || requestPath.startsWith('/admin/js')) {
|
|
25
|
+
return next();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Excepción: página de login web - NO requiere autenticación
|
|
29
|
+
if (requestPath === '/admin/login') {
|
|
30
|
+
return next();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Excepción: rutas de autenticación (API) - NO requieren autenticación
|
|
34
|
+
if (requestPath.startsWith('/api/admin/auth')) {
|
|
35
|
+
return next();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Rutas que requieren autenticación (API y Web)
|
|
39
|
+
const protectedPaths = [
|
|
40
|
+
'/api/admin/apps',
|
|
41
|
+
'/api/admin/stats',
|
|
42
|
+
'/api/admin/config',
|
|
43
|
+
'/api/admin/db',
|
|
44
|
+
'/api/admin/database',
|
|
45
|
+
'/admin', // Dashboard web (ahora es dinámica)
|
|
46
|
+
'/admin/database' // Database Pools web (ahora es dinámica)
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// Verificar si la ruta está protegida
|
|
50
|
+
let isProtected = false;
|
|
51
|
+
for (const p of protectedPaths) {
|
|
52
|
+
if (p === requestPath) {
|
|
53
|
+
isProtected = true;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
if (requestPath.startsWith(p + '/')) {
|
|
57
|
+
isProtected = true;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (isProtected) {
|
|
63
|
+
// Verificar sesión
|
|
64
|
+
const isAuthenticated = req.session && req.session.data && req.session.data.authenticated;
|
|
65
|
+
|
|
66
|
+
if (!isAuthenticated) {
|
|
67
|
+
// Para peticiones API, devolver JSON
|
|
68
|
+
if (requestPath.startsWith('/api/')) {
|
|
69
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
70
|
+
res.end(JSON.stringify({
|
|
71
|
+
success: false,
|
|
72
|
+
error: 'No autenticado',
|
|
73
|
+
redirect: '/admin/login'
|
|
74
|
+
}));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Para peticiones web, redirigir al login
|
|
79
|
+
res.writeHead(302, { 'Location': '/admin/login' });
|
|
80
|
+
res.end();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Usuario autenticado, añadir información a la request
|
|
85
|
+
req.currentUser = {
|
|
86
|
+
userId: req.session.data.userId,
|
|
87
|
+
username: req.session.data.username,
|
|
88
|
+
role: req.session.data.role
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
next();
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Inicializar el sistema de autenticación y rutas admin
|
|
98
|
+
* @param {Object} server - Instancia del servidor APIServer
|
|
99
|
+
*/
|
|
100
|
+
async function initAdminAuth(server) {
|
|
101
|
+
const config = {
|
|
102
|
+
cookieName: 'blackcoffee_admin_session',
|
|
103
|
+
secret: process.env.SESSION_SECRET || 'blackcoffee-session-secret-change-in-production',
|
|
104
|
+
timeout: 3600000, // 1 hora
|
|
105
|
+
secure: false, // Cambiar a true en producción con HTTPS
|
|
106
|
+
httpOnly: true,
|
|
107
|
+
sameSite: 'lax'
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// ============================================
|
|
111
|
+
// Session Manager para autenticación
|
|
112
|
+
// ============================================
|
|
113
|
+
const sessionManager = new SessionManager(config);
|
|
114
|
+
|
|
115
|
+
// Registrar middleware de sesión PRIMERO
|
|
116
|
+
server.use(sessionManager.middleware());
|
|
117
|
+
|
|
118
|
+
server.sessionManager = sessionManager;
|
|
119
|
+
console.log('🔐 Session Manager inicializado\n');
|
|
120
|
+
|
|
121
|
+
// ============================================
|
|
122
|
+
// Rutas estáticas para CSS y JS (ANTES del middleware de auth)
|
|
123
|
+
// ============================================
|
|
124
|
+
console.log('🎨 Cargando recursos estáticos del admin...\n');
|
|
125
|
+
|
|
126
|
+
server.addRoute('GET', '/admin/css', {
|
|
127
|
+
static: {
|
|
128
|
+
dir: path.join(__dirname, '..', 'public', 'admin', 'css'),
|
|
129
|
+
cacheControl: 'public, max-age=31536000'
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
server.addRoute('GET', '/admin/js', {
|
|
134
|
+
static: {
|
|
135
|
+
dir: path.join(__dirname, '..', 'public', 'admin', 'js'),
|
|
136
|
+
cacheControl: 'public, max-age=31536000'
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
console.log(' ✅ Recursos estáticos cargados\n');
|
|
141
|
+
|
|
142
|
+
// ============================================
|
|
143
|
+
// Middleware de autenticación (DESPUÉS de las estáticas)
|
|
144
|
+
// ============================================
|
|
145
|
+
server.use(authMiddleware());
|
|
146
|
+
console.log('🔒 Middleware de autenticación registrado\n');
|
|
147
|
+
|
|
148
|
+
// ============================================
|
|
149
|
+
// Inicializar base de datos de administración
|
|
150
|
+
// ============================================
|
|
151
|
+
try {
|
|
152
|
+
await initDatabase();
|
|
153
|
+
console.log('🗄️ Base de datos admin inicializada\n');
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error('❌ Error inicializando DB admin:', error.message);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ============================================
|
|
159
|
+
// Rutas de autenticación (API)
|
|
160
|
+
// ============================================
|
|
161
|
+
const authRoutesPath = path.join(__dirname, '..', 'routes', 'auth.json');
|
|
162
|
+
if (fs.existsSync(authRoutesPath)) {
|
|
163
|
+
const RouteLoader = require('insitu-js/lib/loader/routeLoader');
|
|
164
|
+
const routeLoader = new RouteLoader();
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const authRoutes = fs.readFileSync(authRoutesPath, 'utf8');
|
|
168
|
+
const routes = JSON.parse(authRoutes);
|
|
169
|
+
|
|
170
|
+
for (const route of routes) {
|
|
171
|
+
const controllerPath = path.resolve(__dirname, '..', route.controller);
|
|
172
|
+
const routeCopy = { ...route, controller: controllerPath };
|
|
173
|
+
await routeLoader.loadSingleRoute(server, routeCopy);
|
|
174
|
+
console.log(` ✅ ${route.method} ${route.path}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log(' ✅ Rutas de autenticación cargadas\n');
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error(` ❌ Error cargando rutas auth: ${error.message}\n`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ============================================
|
|
184
|
+
// Rutas de la UI de administración (Web) - DINÁMICAS con controlador
|
|
185
|
+
// ============================================
|
|
186
|
+
console.log('🎨 Cargando UI de administración...\n');
|
|
187
|
+
|
|
188
|
+
const adminViewsRoutesPath = path.join(__dirname, '..', 'routes', 'admin-views.json');
|
|
189
|
+
if (fs.existsSync(adminViewsRoutesPath)) {
|
|
190
|
+
const RouteLoader = require('insitu-js/lib/loader/routeLoader');
|
|
191
|
+
const routeLoader = new RouteLoader();
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const adminViewsRoutes = fs.readFileSync(adminViewsRoutesPath, 'utf8');
|
|
195
|
+
const routes = JSON.parse(adminViewsRoutes);
|
|
196
|
+
|
|
197
|
+
for (const route of routes) {
|
|
198
|
+
const controllerPath = path.resolve(__dirname, '..', route.controller);
|
|
199
|
+
const routeCopy = { ...route, controller: controllerPath };
|
|
200
|
+
await routeLoader.loadSingleRoute(server, routeCopy);
|
|
201
|
+
console.log(` ✅ ${route.method} ${route.path}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
console.log(' ✅ Rutas de administración cargadas\n');
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error(` ❌ Error cargando rutas admin views: ${error.message}\n`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Exponer sessionManager globalmente para acceso desde controladores
|
|
211
|
+
global.sessionManager = sessionManager;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = { initAdminAuth };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extensión administrativa de BlackCoffee
|
|
3
|
+
* Habilita la consola TCP de administración de Insitu
|
|
4
|
+
*
|
|
5
|
+
* Uso:
|
|
6
|
+
* const adminExtension = require('./includes/adminExtension');
|
|
7
|
+
* adminExtension.init(server, config);
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Inicializa la consola administrativa
|
|
12
|
+
* @param {Object} server - Instancia del servidor APIServer
|
|
13
|
+
* @param {Object} config - Configuración del servidor
|
|
14
|
+
*/
|
|
15
|
+
function init(server, config = {}) {
|
|
16
|
+
const adminPort = config.adminPort || parseInt(process.env.ADMIN_PORT, 10) || 9999;
|
|
17
|
+
const adminHost = config.adminHost || process.env.ADMIN_HOST || '127.0.0.1';
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
console.log(`🔧 Inicializando consola administrativa...`);
|
|
21
|
+
|
|
22
|
+
server.initializeAdminExtension({
|
|
23
|
+
port: adminPort,
|
|
24
|
+
host: adminHost
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log(`✅ Consola administrativa iniciada en ${adminHost}:${adminPort}`);
|
|
28
|
+
console.log(` Conéctate con: telnet ${adminHost} ${adminPort}`);
|
|
29
|
+
console.log(` O usa: nc ${adminHost} ${adminPort}\n`);
|
|
30
|
+
|
|
31
|
+
return true;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(`❌ Error inicializando consola administrativa: ${error.message}`);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Obtiene información sobre la consola administrativa
|
|
40
|
+
* @returns {Object} - Información de la consola
|
|
41
|
+
*/
|
|
42
|
+
function getInfo() {
|
|
43
|
+
return {
|
|
44
|
+
port: parseInt(process.env.ADMIN_PORT, 10) || 9999,
|
|
45
|
+
host: process.env.ADMIN_HOST || '127.0.0.1',
|
|
46
|
+
connectCommand: `telnet ${process.env.ADMIN_HOST || '127.0.0.1'} ${process.env.ADMIN_PORT || 9999}`
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
init,
|
|
52
|
+
getInfo
|
|
53
|
+
};
|