@hemia/core 0.0.3 → 0.0.5
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/dist/hemia-core.esm.js +147 -10
- package/dist/hemia-core.js +144 -7
- package/dist/types/guards/api-key.guard.d.ts +11 -0
- package/dist/types/guards/auth.guard.d.ts +4 -0
- package/dist/types/guards/index.d.ts +2 -0
- package/dist/types/guards/jwt.guard.d.ts +13 -0
- package/dist/types/hemia-factory.d.ts +3 -0
- package/dist/types/router/routes-registry.d.ts +1 -1
- package/package.json +11 -4
package/dist/hemia-core.esm.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import express, { Router } from 'express';
|
|
3
|
-
import { METADATA_KEYS, ParamType,
|
|
3
|
+
import { METADATA_KEYS, ParamType, ApiResponse, isRedirectResponse } from '@hemia/common';
|
|
4
4
|
import { TRACE_METADATA_KEY } from '@hemia/trace-manager';
|
|
5
5
|
import { traceMiddleware } from '@hemia/app-context';
|
|
6
6
|
import { plainToInstance } from 'class-transformer';
|
|
7
|
-
import
|
|
7
|
+
import multer from 'multer';
|
|
8
|
+
import { injectable, inject } from 'inversify';
|
|
9
|
+
import { AUTH_SERVICE_ID, AuthService } from '@hemia/auth-sdk';
|
|
8
10
|
|
|
9
11
|
class GuardsConsumer {
|
|
10
12
|
/**
|
|
@@ -79,7 +81,7 @@ class ResponseSerializer {
|
|
|
79
81
|
* @param container Instancia del Container de Inversify
|
|
80
82
|
* @param controllerIdentifiers Array de Symbols (o Clases si usas self-binding)
|
|
81
83
|
*/
|
|
82
|
-
async function registerRoutes(app, container, controllerIdentifiers, onTraceFinishCallback) {
|
|
84
|
+
async function registerRoutes(app, container, controllerIdentifiers, onTraceFinishCallback, multerOptions) {
|
|
83
85
|
for (const identifier of controllerIdentifiers) {
|
|
84
86
|
const instance = await container.getAsync(identifier);
|
|
85
87
|
const prototype = Object.getPrototypeOf(instance);
|
|
@@ -101,6 +103,14 @@ async function registerRoutes(app, container, controllerIdentifiers, onTraceFini
|
|
|
101
103
|
// Leer metadata de headers y redirect
|
|
102
104
|
const headersMetadata = Reflect.getMetadata(METADATA_KEYS.HEADERS, ControllerClass, route.methodName) || [];
|
|
103
105
|
const redirectMetadata = Reflect.getMetadata(METADATA_KEYS.REDIRECT, ControllerClass, route.methodName);
|
|
106
|
+
const needsFile = paramsMetadata.some(p => p.type === ParamType.FILE);
|
|
107
|
+
const needsFiles = paramsMetadata.some(p => p.type === ParamType.FILES);
|
|
108
|
+
if (needsFiles) {
|
|
109
|
+
handlers.push(multer(multerOptions).array('files'));
|
|
110
|
+
}
|
|
111
|
+
else if (needsFile) {
|
|
112
|
+
handlers.push(multer(multerOptions).single('file'));
|
|
113
|
+
}
|
|
104
114
|
const mainHandler = async (req, res, next) => {
|
|
105
115
|
try {
|
|
106
116
|
const args = [];
|
|
@@ -137,11 +147,59 @@ async function registerRoutes(app, container, controllerIdentifiers, onTraceFini
|
|
|
137
147
|
case ParamType.SESSION:
|
|
138
148
|
args[param.index] = req.session;
|
|
139
149
|
break;
|
|
150
|
+
case ParamType.COOKIES:
|
|
151
|
+
args[param.index] = param.data ? req.cookies?.[param.data] : req.cookies;
|
|
152
|
+
break;
|
|
153
|
+
case ParamType.FILE:
|
|
154
|
+
args[param.index] = req.file;
|
|
155
|
+
break;
|
|
156
|
+
case ParamType.FILES:
|
|
157
|
+
args[param.index] = req.files;
|
|
158
|
+
break;
|
|
159
|
+
case ParamType.USER:
|
|
160
|
+
args[param.index] = req.user;
|
|
161
|
+
break;
|
|
162
|
+
case ParamType.IP:
|
|
163
|
+
args[param.index] = req.ip;
|
|
164
|
+
break;
|
|
165
|
+
case ParamType.LOCALE:
|
|
166
|
+
args[param.index] = req.acceptsLanguages()[0];
|
|
167
|
+
break;
|
|
168
|
+
case ParamType.HOST:
|
|
169
|
+
args[param.index] = req.hostname;
|
|
170
|
+
break;
|
|
171
|
+
case ParamType.PERMISSIONS:
|
|
172
|
+
args[param.index] = req.permissions;
|
|
173
|
+
break;
|
|
174
|
+
case ParamType.CONTEXT:
|
|
175
|
+
args[param.index] = req.context;
|
|
176
|
+
break;
|
|
140
177
|
default: args[param.index] = undefined;
|
|
141
178
|
}
|
|
142
179
|
});
|
|
143
180
|
}
|
|
181
|
+
// CUSTOM PARAMETERS
|
|
182
|
+
const customs = Reflect.getMetadata(METADATA_KEYS.CUSTOMS, instance, route.methodName) || [];
|
|
183
|
+
for (const { index, key } of customs) {
|
|
184
|
+
args[index] = req.customData?.[key];
|
|
185
|
+
}
|
|
186
|
+
// TRANSFORMACIÓN DE PARÁMETROS
|
|
187
|
+
const transformers = Reflect.getMetadata(METADATA_KEYS.TRANSFORMERS, instance, route.methodName) || [];
|
|
188
|
+
for (const { index, transformer } of transformers) {
|
|
189
|
+
args[index] = transformer(args[index]);
|
|
190
|
+
}
|
|
191
|
+
// VALIDACIÓN DE PARÁMETROS
|
|
192
|
+
const validators = Reflect.getMetadata(METADATA_KEYS.VALIDATORS, instance, route.methodName) || [];
|
|
193
|
+
for (const { index, validator } of validators) {
|
|
194
|
+
const value = args[index];
|
|
195
|
+
const result = validator(value);
|
|
196
|
+
if (result === false) {
|
|
197
|
+
return res.status(400).json(ApiResponse.error('Validation failed', undefined, 400));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// EJECUCIÓN DEL CONTROLLER
|
|
144
201
|
const result = await instance[route.methodName](...args);
|
|
202
|
+
// SERIALIZACIÓN DE RESULTADO
|
|
145
203
|
const serializeDto = Reflect.getMetadata(METADATA_KEYS.SERIALIZE, methodHandler) || Reflect.getMetadata(METADATA_KEYS.SERIALIZE, ControllerClass);
|
|
146
204
|
let finalResult = result;
|
|
147
205
|
if (serializeDto) {
|
|
@@ -206,6 +264,10 @@ function __decorate(decorators, target, key, desc) {
|
|
|
206
264
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
207
265
|
}
|
|
208
266
|
|
|
267
|
+
function __param(paramIndex, decorator) {
|
|
268
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
269
|
+
}
|
|
270
|
+
|
|
209
271
|
function __metadata(metadataKey, metadataValue) {
|
|
210
272
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
|
|
211
273
|
}
|
|
@@ -259,8 +321,8 @@ class HemiaFactory {
|
|
|
259
321
|
*/
|
|
260
322
|
static async create(container, controllers, options = {}) {
|
|
261
323
|
const app = express();
|
|
262
|
-
app.use(express.json());
|
|
263
|
-
app.use(express.urlencoded({ extended: true }));
|
|
324
|
+
app.use(express.json(options.jsonOptions));
|
|
325
|
+
app.use(express.urlencoded({ extended: true, ...options.urlencodedOptions }));
|
|
264
326
|
if (options.middlewares && Array.isArray(options.middlewares)) {
|
|
265
327
|
options.middlewares.forEach(middleware => {
|
|
266
328
|
app.use(middleware);
|
|
@@ -291,7 +353,14 @@ class HemiaFactory {
|
|
|
291
353
|
container.bind(controllerClass).toSelf();
|
|
292
354
|
}
|
|
293
355
|
});
|
|
294
|
-
await registerRoutes(app, container, controllers, options.onTraceFinish || (() => { }));
|
|
356
|
+
await registerRoutes(app, container, controllers, options.onTraceFinish || (() => { }), options.multerOptions);
|
|
357
|
+
app.use((err, req, res, next) => {
|
|
358
|
+
console.error('[Hemia] Unhandled error:', err);
|
|
359
|
+
res.status(err.status || 500).json({
|
|
360
|
+
error: err.message || 'Internal Server Error',
|
|
361
|
+
details: err.details || undefined,
|
|
362
|
+
});
|
|
363
|
+
});
|
|
295
364
|
if (options.logger !== false) {
|
|
296
365
|
console.log(`[Hemia] Application initialized with ${controllers.length} controllers.`);
|
|
297
366
|
}
|
|
@@ -299,6 +368,10 @@ class HemiaFactory {
|
|
|
299
368
|
}
|
|
300
369
|
}
|
|
301
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Guardia de autorización que verifica roles y permisos definidos en los controladores y métodos.
|
|
373
|
+
* Utiliza los metadatos definidos con los decoradores @Roles y @Permissions.
|
|
374
|
+
*/
|
|
302
375
|
let AuthGuard = class AuthGuard {
|
|
303
376
|
constructor(reflector) {
|
|
304
377
|
this.reflector = reflector;
|
|
@@ -315,17 +388,19 @@ let AuthGuard = class AuthGuard {
|
|
|
315
388
|
}
|
|
316
389
|
const request = context.switchToHttp().getRequest();
|
|
317
390
|
const user = request.user;
|
|
318
|
-
|
|
391
|
+
const permissions = request.permissions;
|
|
392
|
+
const contextData = request.context;
|
|
393
|
+
if (!user) {
|
|
319
394
|
return false;
|
|
320
395
|
}
|
|
321
396
|
if (requiredRoles) {
|
|
322
|
-
const userRoleNames =
|
|
397
|
+
const userRoleNames = Array.isArray(contextData.roles) ? contextData.roles : [];
|
|
323
398
|
const hasRole = requiredRoles.some((role) => userRoleNames.includes(role));
|
|
324
399
|
if (!hasRole)
|
|
325
400
|
return false;
|
|
326
401
|
}
|
|
327
402
|
if (requiredPermissions) {
|
|
328
|
-
const userPermissions =
|
|
403
|
+
const userPermissions = Array.isArray(permissions) ? permissions : [];
|
|
329
404
|
const hasPermission = requiredPermissions.some((perm) => userPermissions.includes(perm));
|
|
330
405
|
if (!hasPermission)
|
|
331
406
|
return false;
|
|
@@ -338,4 +413,66 @@ AuthGuard = __decorate([
|
|
|
338
413
|
__metadata("design:paramtypes", [Reflector])
|
|
339
414
|
], AuthGuard);
|
|
340
415
|
|
|
341
|
-
|
|
416
|
+
/**
|
|
417
|
+
* Guardia para validar la presencia y validez de una API Key en las solicitudes entrantes.
|
|
418
|
+
* La API Key se espera en el encabezado 'x-api-key' o como parámetro de consulta 'api_key'.
|
|
419
|
+
* Las claves válidas se configuran mediante la variable de entorno 'API_KEY' y pueden ser múltiples, separadas por comas.
|
|
420
|
+
*/
|
|
421
|
+
let ApiKeyGuard = class ApiKeyGuard {
|
|
422
|
+
constructor() {
|
|
423
|
+
this.validApiKeys = (process.env.API_KEY || "")
|
|
424
|
+
.split(",")
|
|
425
|
+
.map(key => key.trim())
|
|
426
|
+
.filter(key => !!key);
|
|
427
|
+
}
|
|
428
|
+
canActivate(context) {
|
|
429
|
+
const request = context.switchToHttp().getRequest();
|
|
430
|
+
const apiKey = request.headers['x-api-key'] || request.query['api_key'];
|
|
431
|
+
if (!apiKey)
|
|
432
|
+
return false;
|
|
433
|
+
return this.validApiKeys.includes(apiKey);
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
ApiKeyGuard = __decorate([
|
|
437
|
+
injectable(),
|
|
438
|
+
__metadata("design:paramtypes", [])
|
|
439
|
+
], ApiKeyGuard);
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Guardia para validar y procesar tokens JWT en las solicitudes entrantes.
|
|
443
|
+
* Extrae el token de sesión del encabezado 'x-session' o de las cookies,
|
|
444
|
+
* y utiliza el AuthService para validar el token y obtener los datos del usuario.
|
|
445
|
+
* Si el token es válido, adjunta la información del usuario, permisos y contexto a la solicitud.
|
|
446
|
+
*/
|
|
447
|
+
let JWTGuard = class JWTGuard {
|
|
448
|
+
constructor(authService) {
|
|
449
|
+
this.authService = authService;
|
|
450
|
+
}
|
|
451
|
+
async canActivate(context) {
|
|
452
|
+
const request = context.switchToHttp().getRequest();
|
|
453
|
+
const sessionId = request.headers['x-session'] || request.cookies['x-session'];
|
|
454
|
+
if (!sessionId) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
const accessTokenData = await this.authService.getSessionAccess(sessionId);
|
|
459
|
+
if (!accessTokenData) {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
request.user = accessTokenData.user;
|
|
463
|
+
request.permissions = accessTokenData.permissions;
|
|
464
|
+
request.context = accessTokenData.context;
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
JWTGuard = __decorate([
|
|
473
|
+
injectable(),
|
|
474
|
+
__param(0, inject(AUTH_SERVICE_ID)),
|
|
475
|
+
__metadata("design:paramtypes", [AuthService])
|
|
476
|
+
], JWTGuard);
|
|
477
|
+
|
|
478
|
+
export { ApiKeyGuard, AuthGuard, GuardsConsumer, HemiaExecutionContext, HemiaFactory, JWTGuard, Reflector, ResponseSerializer, registerRoutes };
|
package/dist/hemia-core.js
CHANGED
|
@@ -6,7 +6,9 @@ var common = require('@hemia/common');
|
|
|
6
6
|
var traceManager = require('@hemia/trace-manager');
|
|
7
7
|
var appContext = require('@hemia/app-context');
|
|
8
8
|
var classTransformer = require('class-transformer');
|
|
9
|
+
var multer = require('multer');
|
|
9
10
|
var inversify = require('inversify');
|
|
11
|
+
var authSdk = require('@hemia/auth-sdk');
|
|
10
12
|
|
|
11
13
|
class GuardsConsumer {
|
|
12
14
|
/**
|
|
@@ -81,7 +83,7 @@ class ResponseSerializer {
|
|
|
81
83
|
* @param container Instancia del Container de Inversify
|
|
82
84
|
* @param controllerIdentifiers Array de Symbols (o Clases si usas self-binding)
|
|
83
85
|
*/
|
|
84
|
-
async function registerRoutes(app, container, controllerIdentifiers, onTraceFinishCallback) {
|
|
86
|
+
async function registerRoutes(app, container, controllerIdentifiers, onTraceFinishCallback, multerOptions) {
|
|
85
87
|
for (const identifier of controllerIdentifiers) {
|
|
86
88
|
const instance = await container.getAsync(identifier);
|
|
87
89
|
const prototype = Object.getPrototypeOf(instance);
|
|
@@ -103,6 +105,14 @@ async function registerRoutes(app, container, controllerIdentifiers, onTraceFini
|
|
|
103
105
|
// Leer metadata de headers y redirect
|
|
104
106
|
const headersMetadata = Reflect.getMetadata(common.METADATA_KEYS.HEADERS, ControllerClass, route.methodName) || [];
|
|
105
107
|
const redirectMetadata = Reflect.getMetadata(common.METADATA_KEYS.REDIRECT, ControllerClass, route.methodName);
|
|
108
|
+
const needsFile = paramsMetadata.some(p => p.type === common.ParamType.FILE);
|
|
109
|
+
const needsFiles = paramsMetadata.some(p => p.type === common.ParamType.FILES);
|
|
110
|
+
if (needsFiles) {
|
|
111
|
+
handlers.push(multer(multerOptions).array('files'));
|
|
112
|
+
}
|
|
113
|
+
else if (needsFile) {
|
|
114
|
+
handlers.push(multer(multerOptions).single('file'));
|
|
115
|
+
}
|
|
106
116
|
const mainHandler = async (req, res, next) => {
|
|
107
117
|
try {
|
|
108
118
|
const args = [];
|
|
@@ -139,11 +149,59 @@ async function registerRoutes(app, container, controllerIdentifiers, onTraceFini
|
|
|
139
149
|
case common.ParamType.SESSION:
|
|
140
150
|
args[param.index] = req.session;
|
|
141
151
|
break;
|
|
152
|
+
case common.ParamType.COOKIES:
|
|
153
|
+
args[param.index] = param.data ? req.cookies?.[param.data] : req.cookies;
|
|
154
|
+
break;
|
|
155
|
+
case common.ParamType.FILE:
|
|
156
|
+
args[param.index] = req.file;
|
|
157
|
+
break;
|
|
158
|
+
case common.ParamType.FILES:
|
|
159
|
+
args[param.index] = req.files;
|
|
160
|
+
break;
|
|
161
|
+
case common.ParamType.USER:
|
|
162
|
+
args[param.index] = req.user;
|
|
163
|
+
break;
|
|
164
|
+
case common.ParamType.IP:
|
|
165
|
+
args[param.index] = req.ip;
|
|
166
|
+
break;
|
|
167
|
+
case common.ParamType.LOCALE:
|
|
168
|
+
args[param.index] = req.acceptsLanguages()[0];
|
|
169
|
+
break;
|
|
170
|
+
case common.ParamType.HOST:
|
|
171
|
+
args[param.index] = req.hostname;
|
|
172
|
+
break;
|
|
173
|
+
case common.ParamType.PERMISSIONS:
|
|
174
|
+
args[param.index] = req.permissions;
|
|
175
|
+
break;
|
|
176
|
+
case common.ParamType.CONTEXT:
|
|
177
|
+
args[param.index] = req.context;
|
|
178
|
+
break;
|
|
142
179
|
default: args[param.index] = undefined;
|
|
143
180
|
}
|
|
144
181
|
});
|
|
145
182
|
}
|
|
183
|
+
// CUSTOM PARAMETERS
|
|
184
|
+
const customs = Reflect.getMetadata(common.METADATA_KEYS.CUSTOMS, instance, route.methodName) || [];
|
|
185
|
+
for (const { index, key } of customs) {
|
|
186
|
+
args[index] = req.customData?.[key];
|
|
187
|
+
}
|
|
188
|
+
// TRANSFORMACIÓN DE PARÁMETROS
|
|
189
|
+
const transformers = Reflect.getMetadata(common.METADATA_KEYS.TRANSFORMERS, instance, route.methodName) || [];
|
|
190
|
+
for (const { index, transformer } of transformers) {
|
|
191
|
+
args[index] = transformer(args[index]);
|
|
192
|
+
}
|
|
193
|
+
// VALIDACIÓN DE PARÁMETROS
|
|
194
|
+
const validators = Reflect.getMetadata(common.METADATA_KEYS.VALIDATORS, instance, route.methodName) || [];
|
|
195
|
+
for (const { index, validator } of validators) {
|
|
196
|
+
const value = args[index];
|
|
197
|
+
const result = validator(value);
|
|
198
|
+
if (result === false) {
|
|
199
|
+
return res.status(400).json(common.ApiResponse.error('Validation failed', undefined, 400));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// EJECUCIÓN DEL CONTROLLER
|
|
146
203
|
const result = await instance[route.methodName](...args);
|
|
204
|
+
// SERIALIZACIÓN DE RESULTADO
|
|
147
205
|
const serializeDto = Reflect.getMetadata(common.METADATA_KEYS.SERIALIZE, methodHandler) || Reflect.getMetadata(common.METADATA_KEYS.SERIALIZE, ControllerClass);
|
|
148
206
|
let finalResult = result;
|
|
149
207
|
if (serializeDto) {
|
|
@@ -208,6 +266,10 @@ function __decorate(decorators, target, key, desc) {
|
|
|
208
266
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
209
267
|
}
|
|
210
268
|
|
|
269
|
+
function __param(paramIndex, decorator) {
|
|
270
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
271
|
+
}
|
|
272
|
+
|
|
211
273
|
function __metadata(metadataKey, metadataValue) {
|
|
212
274
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
|
|
213
275
|
}
|
|
@@ -261,8 +323,8 @@ class HemiaFactory {
|
|
|
261
323
|
*/
|
|
262
324
|
static async create(container, controllers, options = {}) {
|
|
263
325
|
const app = express();
|
|
264
|
-
app.use(express.json());
|
|
265
|
-
app.use(express.urlencoded({ extended: true }));
|
|
326
|
+
app.use(express.json(options.jsonOptions));
|
|
327
|
+
app.use(express.urlencoded({ extended: true, ...options.urlencodedOptions }));
|
|
266
328
|
if (options.middlewares && Array.isArray(options.middlewares)) {
|
|
267
329
|
options.middlewares.forEach(middleware => {
|
|
268
330
|
app.use(middleware);
|
|
@@ -293,7 +355,14 @@ class HemiaFactory {
|
|
|
293
355
|
container.bind(controllerClass).toSelf();
|
|
294
356
|
}
|
|
295
357
|
});
|
|
296
|
-
await registerRoutes(app, container, controllers, options.onTraceFinish || (() => { }));
|
|
358
|
+
await registerRoutes(app, container, controllers, options.onTraceFinish || (() => { }), options.multerOptions);
|
|
359
|
+
app.use((err, req, res, next) => {
|
|
360
|
+
console.error('[Hemia] Unhandled error:', err);
|
|
361
|
+
res.status(err.status || 500).json({
|
|
362
|
+
error: err.message || 'Internal Server Error',
|
|
363
|
+
details: err.details || undefined,
|
|
364
|
+
});
|
|
365
|
+
});
|
|
297
366
|
if (options.logger !== false) {
|
|
298
367
|
console.log(`[Hemia] Application initialized with ${controllers.length} controllers.`);
|
|
299
368
|
}
|
|
@@ -301,6 +370,10 @@ class HemiaFactory {
|
|
|
301
370
|
}
|
|
302
371
|
}
|
|
303
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Guardia de autorización que verifica roles y permisos definidos en los controladores y métodos.
|
|
375
|
+
* Utiliza los metadatos definidos con los decoradores @Roles y @Permissions.
|
|
376
|
+
*/
|
|
304
377
|
exports.AuthGuard = class AuthGuard {
|
|
305
378
|
constructor(reflector) {
|
|
306
379
|
this.reflector = reflector;
|
|
@@ -317,17 +390,19 @@ exports.AuthGuard = class AuthGuard {
|
|
|
317
390
|
}
|
|
318
391
|
const request = context.switchToHttp().getRequest();
|
|
319
392
|
const user = request.user;
|
|
320
|
-
|
|
393
|
+
const permissions = request.permissions;
|
|
394
|
+
const contextData = request.context;
|
|
395
|
+
if (!user) {
|
|
321
396
|
return false;
|
|
322
397
|
}
|
|
323
398
|
if (requiredRoles) {
|
|
324
|
-
const userRoleNames =
|
|
399
|
+
const userRoleNames = Array.isArray(contextData.roles) ? contextData.roles : [];
|
|
325
400
|
const hasRole = requiredRoles.some((role) => userRoleNames.includes(role));
|
|
326
401
|
if (!hasRole)
|
|
327
402
|
return false;
|
|
328
403
|
}
|
|
329
404
|
if (requiredPermissions) {
|
|
330
|
-
const userPermissions =
|
|
405
|
+
const userPermissions = Array.isArray(permissions) ? permissions : [];
|
|
331
406
|
const hasPermission = requiredPermissions.some((perm) => userPermissions.includes(perm));
|
|
332
407
|
if (!hasPermission)
|
|
333
408
|
return false;
|
|
@@ -340,6 +415,68 @@ exports.AuthGuard = __decorate([
|
|
|
340
415
|
__metadata("design:paramtypes", [exports.Reflector])
|
|
341
416
|
], exports.AuthGuard);
|
|
342
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Guardia para validar la presencia y validez de una API Key en las solicitudes entrantes.
|
|
420
|
+
* La API Key se espera en el encabezado 'x-api-key' o como parámetro de consulta 'api_key'.
|
|
421
|
+
* Las claves válidas se configuran mediante la variable de entorno 'API_KEY' y pueden ser múltiples, separadas por comas.
|
|
422
|
+
*/
|
|
423
|
+
exports.ApiKeyGuard = class ApiKeyGuard {
|
|
424
|
+
constructor() {
|
|
425
|
+
this.validApiKeys = (process.env.API_KEY || "")
|
|
426
|
+
.split(",")
|
|
427
|
+
.map(key => key.trim())
|
|
428
|
+
.filter(key => !!key);
|
|
429
|
+
}
|
|
430
|
+
canActivate(context) {
|
|
431
|
+
const request = context.switchToHttp().getRequest();
|
|
432
|
+
const apiKey = request.headers['x-api-key'] || request.query['api_key'];
|
|
433
|
+
if (!apiKey)
|
|
434
|
+
return false;
|
|
435
|
+
return this.validApiKeys.includes(apiKey);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
exports.ApiKeyGuard = __decorate([
|
|
439
|
+
inversify.injectable(),
|
|
440
|
+
__metadata("design:paramtypes", [])
|
|
441
|
+
], exports.ApiKeyGuard);
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Guardia para validar y procesar tokens JWT en las solicitudes entrantes.
|
|
445
|
+
* Extrae el token de sesión del encabezado 'x-session' o de las cookies,
|
|
446
|
+
* y utiliza el AuthService para validar el token y obtener los datos del usuario.
|
|
447
|
+
* Si el token es válido, adjunta la información del usuario, permisos y contexto a la solicitud.
|
|
448
|
+
*/
|
|
449
|
+
exports.JWTGuard = class JWTGuard {
|
|
450
|
+
constructor(authService) {
|
|
451
|
+
this.authService = authService;
|
|
452
|
+
}
|
|
453
|
+
async canActivate(context) {
|
|
454
|
+
const request = context.switchToHttp().getRequest();
|
|
455
|
+
const sessionId = request.headers['x-session'] || request.cookies['x-session'];
|
|
456
|
+
if (!sessionId) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
const accessTokenData = await this.authService.getSessionAccess(sessionId);
|
|
461
|
+
if (!accessTokenData) {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
request.user = accessTokenData.user;
|
|
465
|
+
request.permissions = accessTokenData.permissions;
|
|
466
|
+
request.context = accessTokenData.context;
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
exports.JWTGuard = __decorate([
|
|
475
|
+
inversify.injectable(),
|
|
476
|
+
__param(0, inversify.inject(authSdk.AUTH_SERVICE_ID)),
|
|
477
|
+
__metadata("design:paramtypes", [authSdk.AuthService])
|
|
478
|
+
], exports.JWTGuard);
|
|
479
|
+
|
|
343
480
|
exports.GuardsConsumer = GuardsConsumer;
|
|
344
481
|
exports.HemiaExecutionContext = HemiaExecutionContext;
|
|
345
482
|
exports.HemiaFactory = HemiaFactory;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CanActivate, ExecutionContext } from "@hemia/common";
|
|
2
|
+
/**
|
|
3
|
+
* Guardia para validar la presencia y validez de una API Key en las solicitudes entrantes.
|
|
4
|
+
* La API Key se espera en el encabezado 'x-api-key' o como parámetro de consulta 'api_key'.
|
|
5
|
+
* Las claves válidas se configuran mediante la variable de entorno 'API_KEY' y pueden ser múltiples, separadas por comas.
|
|
6
|
+
*/
|
|
7
|
+
export declare class ApiKeyGuard implements CanActivate {
|
|
8
|
+
private readonly validApiKeys;
|
|
9
|
+
constructor();
|
|
10
|
+
canActivate(context: ExecutionContext): boolean;
|
|
11
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { CanActivate, ExecutionContext } from '@hemia/common';
|
|
2
2
|
import { Reflector } from '../services';
|
|
3
|
+
/**
|
|
4
|
+
* Guardia de autorización que verifica roles y permisos definidos en los controladores y métodos.
|
|
5
|
+
* Utiliza los metadatos definidos con los decoradores @Roles y @Permissions.
|
|
6
|
+
*/
|
|
3
7
|
export declare class AuthGuard implements CanActivate {
|
|
4
8
|
private reflector;
|
|
5
9
|
constructor(reflector: Reflector);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CanActivate, ExecutionContext } from "@hemia/common";
|
|
2
|
+
import { AuthService } from "@hemia/auth-sdk";
|
|
3
|
+
/**
|
|
4
|
+
* Guardia para validar y procesar tokens JWT en las solicitudes entrantes.
|
|
5
|
+
* Extrae el token de sesión del encabezado 'x-session' o de las cookies,
|
|
6
|
+
* y utiliza el AuthService para validar el token y obtener los datos del usuario.
|
|
7
|
+
* Si el token es válido, adjunta la información del usuario, permisos y contexto a la solicitud.
|
|
8
|
+
*/
|
|
9
|
+
export declare class JWTGuard implements CanActivate {
|
|
10
|
+
private readonly authService;
|
|
11
|
+
constructor(authService: AuthService);
|
|
12
|
+
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
13
|
+
}
|
|
@@ -7,4 +7,4 @@ export type TraceFinishCallback = (payloads: any, context: any, res: Response) =
|
|
|
7
7
|
* @param container Instancia del Container de Inversify
|
|
8
8
|
* @param controllerIdentifiers Array de Symbols (o Clases si usas self-binding)
|
|
9
9
|
*/
|
|
10
|
-
export declare function registerRoutes(app: Express, container: Container, controllerIdentifiers: any[], onTraceFinishCallback: TraceFinishCallback): Promise<void>;
|
|
10
|
+
export declare function registerRoutes(app: Express, container: Container, controllerIdentifiers: any[], onTraceFinishCallback: TraceFinishCallback, multerOptions?: any): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hemia/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Core utilities for Hemia projects",
|
|
5
5
|
"main": "dist/hemia-core.js",
|
|
6
6
|
"module": "dist/hemia-core.esm.js",
|
|
@@ -17,15 +17,17 @@
|
|
|
17
17
|
"@rollup/plugin-commonjs": "^26.0.1",
|
|
18
18
|
"@rollup/plugin-json": "^6.1.0",
|
|
19
19
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
20
|
-
"@hemia/common": "^0.0.
|
|
20
|
+
"@hemia/common": "^0.0.7",
|
|
21
21
|
"@hemia/app-context": "^0.0.6",
|
|
22
22
|
"@hemia/trace-manager": "^0.0.9",
|
|
23
|
+
"@hemia/auth-sdk": "^0.0.9",
|
|
23
24
|
"@types/express": "^5.0.5",
|
|
24
25
|
"express": "^5.1.0",
|
|
25
26
|
"inversify": "^7.10.4",
|
|
26
27
|
"@types/jest": "^29.5.14",
|
|
27
28
|
"@types/node": "^22.3.0",
|
|
28
29
|
"@typescript-eslint/eslint-plugin": "^8.5.0",
|
|
30
|
+
"@types/multer": "^2.0.0",
|
|
29
31
|
"class-transformer": "^0.5.1",
|
|
30
32
|
"events": "^3.3.0",
|
|
31
33
|
"jest": "^29.7.0",
|
|
@@ -34,7 +36,8 @@
|
|
|
34
36
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
35
37
|
"ts-jest": "^29.2.5",
|
|
36
38
|
"ts-node": "^8.9.0",
|
|
37
|
-
"typescript": "^5.5.4"
|
|
39
|
+
"typescript": "^5.5.4",
|
|
40
|
+
"multer": "^2.0.2"
|
|
38
41
|
},
|
|
39
42
|
"author": "",
|
|
40
43
|
"license": "ISC",
|
|
@@ -45,6 +48,10 @@
|
|
|
45
48
|
"class-transformer": "^0.5.1",
|
|
46
49
|
"express": "^5.0.0",
|
|
47
50
|
"inversify": "^7.0.0",
|
|
48
|
-
"reflect-metadata": "^0.
|
|
51
|
+
"reflect-metadata": "^0.2.2",
|
|
52
|
+
"multer": "^2.0.0"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
|
|
49
56
|
}
|
|
50
57
|
}
|