@fiado/gateway-adapter
v2.1.0
Published
Facilita la comunicación e integración entre AWS Lambda y API Gateway , sqs y Event Bridge
Readme
Fiado Gateway Adapter
Libreria Core para Enrutamiento de Eventos en Microservicios Lambda
Tabla de Contenidos
- Descripcion General
- Arquitectura
- Instalacion
- Configuracion
- GatewayAdapter - Router Principal
- API Gateway
- Colas SQS
- EventBridge
- Decoradores
- Sistema de Respuestas
- Encriptacion JWE
- Estructura del Proyecto
- Dependencias
Descripcion General
@fiado/gateway-adapter es la libreria core del ecosistema Fiado que actua como router central para todos los eventos que recibe una funcion Lambda. Proporciona:
- Enrutamiento automatico de eventos API Gateway, SQS y EventBridge
- Transformacion de requests/responses con tipado fuerte
- Sistema de autorizacion basado en decoradores y features
- Decodificacion de tokens JWT de Cognito
- Respuestas HTTP estandarizadas con CORS configurado
- Encriptacion JWE opcional para datos sensibles
- Registro de actividad de sesion automatico
Proposito
En el modelo de desarrollo Fiado, cada Lambda recibe multiples tipos de eventos:
- API Gateway: Peticiones HTTP (publicas, privadas, backoffice)
- SQS: Mensajes de colas para procesamiento asincrono
- EventBridge: Eventos entre servicios
Esta libreria unifica el manejo de todos estos eventos en un unico punto de entrada.
Arquitectura
LAMBDA HANDLER
index.ts
|
v
+---------------+
| GatewayAdapter | <-- Router Principal
+-------+-------+
|
+-------------------+-------------------+
v v v
+-----------------+ +-----------------+ +-----------------+
|ApiGatewayAdapter| |QueueGatewayAdapter| |EventBridgeAdapter|
+--------+--------+ +--------+--------+ +--------+--------+
| | |
v v v
+-----------------+ +-----------------+ +-----------------+
| Controllers | | SQS Subscriber | | EB Subscriber |
| Public/Private/ | |IMessageSubscriber| |IEventBridgeSub |
| Backoffice | | | | |
+-----------------+ +-----------------+ +-----------------+Flujo de Deteccion de Eventos
El GatewayAdapter detecta automaticamente el tipo de evento:
- API Gateway: Si
event.httpMethodexiste - SQS: Si
event.Records[0].eventSource === 'aws:sqs' - EventBridge: Si
event.source === 'EVENT_BRIDGE'
Instalacion
npm install @fiado/gateway-adapterPeer Dependencies
{
"@fiado/logger": "^1.0.2",
"@fiado/type-kit": "^2.1.35",
"inversify": "^6.2.2",
"reflect-metadata": "^0.2.2"
}Configuracion
Registro en el Contenedor IoC
import { Container } from "inversify";
import {
gatewayAdapterBindings,
TypeQueue,
TypeEventBridge,
IController,
IMessageSubscriber,
IEventBridgeMessageSubscriber
} from "@fiado/gateway-adapter";
const container = new Container();
// Cargar bindings del gateway adapter
container.load(gatewayAdapterBindings);
// Registrar controladores del proyecto
container.bind<IController>('PublicController').to(PublicController);
container.bind<IController>('PrivateController').to(PrivateController);
container.bind<IController>('BackofficeController').to(BackofficeController);
// Registrar subscriber de SQS (si aplica)
container.bind<IMessageSubscriber<any>>("SQSMessageSubscriber").to(MySqsSubscriber);
container.bind<() => IMessageSubscriber<any>>(TypeQueue.SQSMessageSubscriberFactory)
.toAutoFactory("SQSMessageSubscriber");
// Registrar subscriber de EventBridge (si aplica)
container.bind<IEventBridgeMessageSubscriber<any>>("EventBridgeMessageSubscriber")
.to(MyEventBridgeSubscriber);
container.bind<() => IEventBridgeMessageSubscriber<any>>(
TypeEventBridge.EventBridgeMessageSubscriberFactory
).toAutoFactory("EventBridgeMessageSubscriber");
export { container };Entry Point del Lambda
import 'reflect-metadata';
import warmer from "lambda-warmer";
import { container } from './container.config';
import { GatewayAdapter, TypeGateway } from '@fiado/gateway-adapter';
import { log } from "@fiado/logger";
export const handler = async (event: any, context: any) => {
// Soporte para lambda warming
if (await warmer(event)) {
return 'warmed';
}
log.info(`Event received: ${JSON.stringify(event)}`);
// Obtener el router y ejecutar
const gatewayAdapter = container.get<GatewayAdapter>(TypeGateway.GatewayAdapter);
return await gatewayAdapter.execute(event);
}GatewayAdapter - Router Principal
El GatewayAdapter es el componente central que detecta el tipo de evento y lo enruta al adaptador correspondiente.
Tipos de Eventos Soportados
| Tipo | Deteccion | Adaptador |
|------|-----------|-----------|
| API Gateway | event.httpMethod existe | ApiGatewayAdapter |
| SQS | event.Records[0].eventSource === 'aws:sqs' | QueueGatewayAdapter |
| EventBridge | event.source === 'EVENT_BRIDGE' | EventBridgeGatewayAdapter |
Uso
import { GatewayAdapter, TypeGateway } from '@fiado/gateway-adapter';
const gatewayAdapter = container.get<GatewayAdapter>(TypeGateway.GatewayAdapter);
// El adapter detecta automaticamente el tipo de evento
const result = await gatewayAdapter.execute(event);API Gateway
ApiGatewayRequest
Interface tipada que representa una peticion HTTP transformada:
interface ApiGatewayRequest<TBody> {
body: TBody | null; // Body parseado
headers: { [key: string]: string | undefined }; // Headers HTTP
method: string; // GET, POST, PUT, DELETE
pathParameter: { [key: string]: string | undefined }; // Path params (/users/:id)
queryStringParameters: { [key: string]: string }; // Query params (?key=value)
multiValueQueryStringParameters: { [key: string]: string[] }; // Arrays
token: TokenPayload | undefined | null; // Token JWT decodificado
operationName: string | null; // Nombre de la operacion
base64EncodedFile?: string; // Archivos en base64
requestContext: { identity?: { sourceIp?: string } }; // Contexto de request
}Controladores
Los controladores se identifican por prefijo en el operationName:
| Prefijo | Controlador | Uso |
|---------|-------------|-----|
| public* | PublicController | Endpoints publicos (internet) |
| private* | PrivateController | Endpoints privados (VPC) |
| backoffice* | BackofficeController | Endpoints administrativos |
| agent* / agents* | AgentController | Endpoints de agentes |
Ejemplo de Controlador
import { injectable, inject } from 'inversify';
import {
IController,
ApiGatewayRequest,
ApiResponse,
AuthorizedFeatures,
Feature,
Endpoint,
HttpMethod,
SecurityType
} from '@fiado/gateway-adapter';
@injectable()
export class PublicController implements IController {
constructor(
@inject("IUserManager") private userManager: IUserManager
) {}
@AuthorizedFeatures(Feature.ANONIMUS)
@Endpoint({
method: HttpMethod.GET,
path: '/users/:id',
summary: 'Obtener usuario por ID',
tags: ['PublicController'],
security: SecurityType.CognitoAppAuthorizer
})
async getUser(request: ApiGatewayRequest<void>): Promise<any> {
const userId = request.pathParameter?.id;
const user = await this.userManager.getById(userId);
return ApiResponse.success({ data: user });
}
@AuthorizedFeatures(Feature.ANONIMUS)
@Endpoint({
method: HttpMethod.POST,
path: '/users',
summary: 'Crear usuario',
tags: ['PublicController'],
security: SecurityType.CognitoAppAuthorizer
})
async createUser(request: ApiGatewayRequest<CreateUserDto>): Promise<any> {
const user = await this.userManager.create(request.body);
return ApiResponse.created({ data: user });
}
}TokenService
Servicio para decodificar tokens JWT de Cognito:
import { TokenService } from '@fiado/gateway-adapter';
const tokenService = new TokenService();
const payload = tokenService.decodeToken(bearerToken);
// payload contiene:
// - sub: ID del usuario
// - email: Email del usuario
// - permissions: Array de permisos/features
// - cognito:groups: Grupos de CognitoColas SQS
IMessageSubscriber
Interface para procesar mensajes de SQS:
export interface IMessageSubscriber<T> {
handle(messageBody: T, receiptHandle: string): Promise<void>;
deleteMessage(queueUrl: string, receiptHandle: string): Promise<void>;
}Implementacion de Subscriber
import { injectable } from 'inversify';
import { IMessageSubscriber } from '@fiado/gateway-adapter';
import { log } from '@fiado/logger';
@injectable()
export class OrderMessageSubscriber implements IMessageSubscriber<OrderMessage> {
async handle(messageBody: OrderMessage, receiptHandle: string): Promise<void> {
log.info('Processing order message', { orderId: messageBody.orderId });
await this.processOrder(messageBody);
}
async deleteMessage(queueUrl: string, receiptHandle: string): Promise<void> {
// Implementar si se necesita borrado manual
}
private async processOrder(order: OrderMessage): Promise<void> {
// Logica de procesamiento
}
}IMessagePublisher
Interface para publicar mensajes a SQS:
export interface IMessagePublisher<T> {
publish(messageBody: T, options?: PublishOptions): Promise<void>;
}EventBridge
IEventBridgeMessageSubscriber
Interface para procesar eventos de EventBridge:
import { EventBridgeMessageRequest } from "@fiado/type-kit";
export interface IEventBridgeMessageSubscriber<TPayload> {
handle(message: EventBridgeMessageRequest<TPayload>): Promise<void>;
}Implementacion de Subscriber
import { injectable } from 'inversify';
import { IEventBridgeMessageSubscriber } from '@fiado/gateway-adapter';
import { EventBridgeMessageRequest } from '@fiado/type-kit';
@injectable()
export class PaymentEventSubscriber implements IEventBridgeMessageSubscriber<PaymentEvent> {
async handle(message: EventBridgeMessageRequest<PaymentEvent>): Promise<void> {
const { payload, eventType } = message;
switch (eventType) {
case 'PAYMENT_COMPLETED':
await this.handlePaymentCompleted(payload);
break;
case 'PAYMENT_FAILED':
await this.handlePaymentFailed(payload);
break;
}
}
}Decoradores
@AuthorizedFeatures
Decorador para definir que features/permisos pueden acceder a un metodo:
import { AuthorizedFeatures, Feature } from '@fiado/gateway-adapter';
// Acceso anonimo (sin token)
@AuthorizedFeatures(Feature.ANONIMUS)
async publicEndpoint(request: ApiGatewayRequest<any>): Promise<any> { }
// Requiere feature especifico en el token
@AuthorizedFeatures(Feature.PAYROLL)
async payrollEndpoint(request: ApiGatewayRequest<any>): Promise<any> { }
// Multiples features (OR - cualquiera autoriza)
@AuthorizedFeatures(Feature.ADMIN, Feature.MANAGER)
async adminEndpoint(request: ApiGatewayRequest<any>): Promise<any> { }Feature Enum
export enum Feature {
ANONIMUS = "ANONIMUS", // Acceso publico sin autenticacion
TEST = "test",
FEATURE1 = "feature1",
PAYROLL = "PAYROLL"
}@Endpoint
Decorador para definir metadata del endpoint (usado para generacion OpenAPI):
import { Endpoint, HttpMethod, SecurityType } from '@fiado/gateway-adapter';
@Endpoint({
method: HttpMethod.POST,
path: '/payments',
summary: 'Procesar pago',
tags: ['PaymentController'],
security: SecurityType.CognitoAppAuthorizer,
queryParams: ['currency'],
pathParams: ['id']
})
async processPayment(request: ApiGatewayRequest<PaymentDto>): Promise<any> { }HttpMethod Enum
export enum HttpMethod {
GET = 'get',
POST = 'post',
PUT = 'put',
DELETE = 'delete'
}SecurityType Enum
export enum SecurityType {
CognitoAppAuthorizer = 'CognitoAppAuthorizer', // Usuarios de la app
CognitoBackofficeAuthorizer = 'CognitoBackofficeAuthorizer', // Administradores
CognitoCombinedAuthorizer = 'CognitoCombinedAuthorizer', // Ambos pools
CognitoAgentAuthorizer = 'CognitoAgentAuthorizer' // Agentes
}Sistema de Respuestas
ApiResponse
Clase estatica para construir respuestas HTTP estandarizadas:
import { ApiResponse } from '@fiado/gateway-adapter';
// 200 OK
return ApiResponse.success({ data: result, msg: 'Operacion exitosa' });
// 201 Created
return ApiResponse.created({ data: newResource });
// 400 Bad Request
return ApiResponse.badRequest({ code: 'VALIDATION_ERROR', msg: 'Campo requerido' });
// 401 Unauthorized
return ApiResponse.unauthorized({ msg: 'Token invalido' });
// 403 Forbidden
return ApiResponse.forbidden({ msg: 'Acceso denegado' });
// 404 Not Found
return ApiResponse.notFound({ msg: 'Recurso no encontrado' });
// 425 Too Early
return ApiResponse.tooEarly({ msg: 'Intente mas tarde' });
// 500 Internal Server Error
return ApiResponse.internalServerError({ msg: 'Error interno' });
// 302 Redirect
return ApiResponse.redirect({ data: { url: 'https://...' } });
// Redireccion real con header Location
return ApiResponse.redirectTo('https://example.com/callback', 302);Estructura de Respuesta
Todas las respuestas siguen el formato estandar:
{
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "*",
"Content-Type": "application/json"
},
body: {
code: "PROCESS_OK", // Codigo de respuesta
date: "2024-01-15T...", // Timestamp ISO
data: { ... }, // Datos de respuesta
description: null, // Descripcion opcional
msg: null, // Mensaje opcional
encrypted: false // Flag de encriptacion (opcional)
}
}ResponseOptions
interface ResponseOptions<T> {
data?: T; // Datos a retornar
code?: string; // Codigo personalizado
msg?: string; // Mensaje
description?: string; // Descripcion
encrypted?: boolean; // Flag de encriptacion
}Encriptacion JWE
JweEncryptionService
Servicio para encriptar respuestas sensibles usando JWE (JSON Web Encryption):
Configuracion
Variables de entorno para habilitar encriptacion:
Environment:
Variables:
# Encriptar TODOS los endpoints de app
JWE_ENCRYPTION_APP: "true"
# O encriptar endpoints especificos
JWE_ENCRYPTION_APP_ENDPOINTS: "publicGetBalance,publicGetTransactions"
# Para backoffice
JWE_ENCRYPTION_BACKOFFICE: "true"
JWE_ENCRYPTION_BACKOFFICE_ENDPOINTS: "backofficeGetUserData"Flujo de Encriptacion
- Cliente envia header
x-session-keycon session key encriptada (JWE con llave publica RSA) - Lambda descifra session key usando llave privada de Secrets Manager
- Respuesta
datase encripta con AES-256-GCM usando session key - Cliente descifra respuesta con su session key
Uso Automatico
La encriptacion se aplica automaticamente si:
- El endpoint esta configurado para encriptar
- El cliente envia el header
x-session-key - El servicio puede inicializarse correctamente
// La respuesta se encripta automaticamente
return ApiResponse.success({ data: sensitiveData });
// Respuesta encriptada:
{
code: "PROCESS_OK",
data: "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0...", // JWE
encrypted: true
}Estructura del Proyecto
src/
├── index.ts # Exportaciones publicas
├── container.config.ts # ContainerModule con bindings
├── GatewayAdapter.ts # Router principal
│
├── apigateway/ # Modulo API Gateway
│ ├── abstractions/
│ │ ├── ApiGatewayRequest.ts
│ │ ├── ApiGatewayResponse.ts
│ │ ├── FeatureEnum.ts
│ │ ├── IController.ts
│ │ ├── ResponseOptions.ts
│ │ ├── StandardResponse.ts
│ │ └── TypeGateway.ts
│ ├── decorators/
│ │ └── AuthorizedFeatures.ts
│ └── services/
│ ├── ActivitySessionService.ts
│ ├── ApiGatewayAdapter.ts
│ ├── ApiResponse.ts
│ ├── JweEncryptionService.ts
│ └── TokenService.ts
│
├── queue/ # Modulo SQS
│ ├── abstractions/
│ │ ├── IMessagePublisher.ts
│ │ ├── IMessageSubscriber.ts
│ │ ├── IPublishOptions.ts
│ │ └── TypeQueue.ts
│ ├── decorators/
│ └── services/
│ └── QueueGatewayAdapter.ts
│
├── eventBridge/ # Modulo EventBridge
│ ├── abstractions/
│ │ ├── IEventBridgeMessageSubscriber.ts
│ │ └── TypeEventBridge.ts
│ └── service/
│ └── EventBridgeGatewayAdapter.ts
│
└── endpoint/ # Decorador @Endpoint
└── endpointDecorator.tsDependencias
Runtime
| Dependencia | Version | Proposito |
|-------------|---------|-----------|
| @fiado/api-invoker | ^1.4.6 | Invocacion de APIs internas |
| @fiado/logger | ^1.0.2 | Logging estructurado |
| @fiado/type-kit | ^2.1.35 | DTOs y tipos compartidos |
| aws-lambda | ^1.0.7 | Tipos de AWS Lambda |
| inversify | ^6.2.2 | Inyeccion de dependencias |
| jose | ^4.15.5 | Encriptacion JWE |
| jsonwebtoken | ^9.0.2 | Decodificacion JWT |
| reflect-metadata | ^0.2.2 | Metadatos para decoradores |
| uuid | ^9.0.1 | Generacion de UUIDs |
Desarrollo
| Dependencia | Version | Proposito |
|-------------|---------|-----------|
| @types/jsonwebtoken | ^9.0.6 | Tipos de jsonwebtoken |
Compilacion y Publicacion
Build
npm run buildGenera archivos .js y .d.ts en el directorio bin/.
Publicacion
npm version patch|minor|major
npm publishFiado Inc. | @fiado/gateway-adapter v1.1.59
