Changes include: - Updated architecture documentation - Enhanced module definitions (OQI-001 to OQI-008) - ML integration documentation updates - Trading strategies documentation - Orchestration and inventory updates - Docker configuration updates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
859 lines
25 KiB
Markdown
859 lines
25 KiB
Markdown
---
|
|
id: "ET-INV-007"
|
|
title: "Seguridad y Validaciones"
|
|
type: "Technical Specification"
|
|
status: "Done"
|
|
priority: "Alta"
|
|
epic: "OQI-004"
|
|
project: "trading-platform"
|
|
version: "1.0.0"
|
|
created_date: "2025-12-05"
|
|
updated_date: "2026-01-04"
|
|
---
|
|
|
|
# ET-INV-007: Seguridad y Validaciones
|
|
|
|
**Epic:** OQI-004 Cuentas de Inversión
|
|
**Versión:** 1.0
|
|
**Fecha:** 2025-12-05
|
|
**Responsable:** Requirements-Analyst
|
|
|
|
---
|
|
|
|
## 1. Descripción
|
|
|
|
Define las medidas de seguridad, validaciones y controles para el módulo de cuentas de inversión:
|
|
- Autenticación y autorización
|
|
- Validación de datos de entrada
|
|
- KYC (Know Your Customer) básico
|
|
- Límites de transacciones
|
|
- Prevención de fraude
|
|
- Auditoría y logging
|
|
- Encriptación de datos sensibles
|
|
|
|
---
|
|
|
|
## 2. Arquitectura de Seguridad
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Security Architecture │
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ Request Layer │ │
|
|
│ │ - Rate Limiting │ │
|
|
│ │ - CORS │ │
|
|
│ │ - Helmet (Security Headers) │ │
|
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ Authentication Layer │ │
|
|
│ │ - JWT Validation │ │
|
|
│ │ - Token Refresh │ │
|
|
│ │ - Session Management │ │
|
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ Authorization Layer │ │
|
|
│ │ - Role-Based Access Control (RBAC) │ │
|
|
│ │ - Resource Ownership Verification │ │
|
|
│ │ - Action Permissions │ │
|
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ Validation Layer │ │
|
|
│ │ - Input Sanitization │ │
|
|
│ │ - Business Rules Validation │ │
|
|
│ │ - Amount Limits │ │
|
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ Fraud Detection │ │
|
|
│ │ - Suspicious Activity Detection │ │
|
|
│ │ - Velocity Checks │ │
|
|
│ │ - Anomaly Detection │ │
|
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ Audit & Logging │ │
|
|
│ │ - Activity Logs │ │
|
|
│ │ - Security Events │ │
|
|
│ │ - Compliance Reporting │ │
|
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Autenticación y Autorización
|
|
|
|
### 3.1 Auth Middleware
|
|
|
|
```typescript
|
|
// src/middlewares/auth.middleware.ts
|
|
|
|
import { Request, Response, NextFunction } from 'express';
|
|
import jwt from 'jsonwebtoken';
|
|
import { AppError } from '../utils/errors';
|
|
import { logger } from '../utils/logger';
|
|
|
|
interface JwtPayload {
|
|
user_id: string;
|
|
email: string;
|
|
role: string;
|
|
exp: number;
|
|
}
|
|
|
|
// Extender Request type
|
|
declare global {
|
|
namespace Express {
|
|
interface Request {
|
|
user?: {
|
|
id: string;
|
|
email: string;
|
|
role: string;
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Middleware de autenticación JWT
|
|
*/
|
|
export const authenticate = async (
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction
|
|
): Promise<void> => {
|
|
try {
|
|
const authHeader = req.headers.authorization;
|
|
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
throw new AppError('No authentication token provided', 401);
|
|
}
|
|
|
|
const token = authHeader.substring(7);
|
|
const secret = process.env.JWT_SECRET!;
|
|
|
|
// Verificar token
|
|
const decoded = jwt.verify(token, secret) as JwtPayload;
|
|
|
|
// Adjuntar usuario al request
|
|
req.user = {
|
|
id: decoded.user_id,
|
|
email: decoded.email,
|
|
role: decoded.role,
|
|
};
|
|
|
|
// Log de acceso
|
|
logger.debug('User authenticated', {
|
|
user_id: req.user.id,
|
|
endpoint: req.path,
|
|
method: req.method,
|
|
});
|
|
|
|
next();
|
|
} catch (error: any) {
|
|
if (error.name === 'TokenExpiredError') {
|
|
logger.warn('Expired token', { path: req.path });
|
|
return next(new AppError('Token expired', 401));
|
|
}
|
|
|
|
if (error.name === 'JsonWebTokenError') {
|
|
logger.warn('Invalid token', { path: req.path });
|
|
return next(new AppError('Invalid token', 401));
|
|
}
|
|
|
|
next(error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Middleware para requerir rol de admin
|
|
*/
|
|
export const requireAdmin = (
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction
|
|
): void => {
|
|
if (!req.user) {
|
|
throw new AppError('Authentication required', 401);
|
|
}
|
|
|
|
if (req.user.role !== 'admin') {
|
|
logger.warn('Unauthorized admin access attempt', {
|
|
user_id: req.user.id,
|
|
role: req.user.role,
|
|
path: req.path,
|
|
});
|
|
throw new AppError('Admin access required', 403);
|
|
}
|
|
|
|
next();
|
|
};
|
|
|
|
/**
|
|
* Middleware para verificar ownership de recurso
|
|
*/
|
|
export const requireOwnership = (resourceType: 'account' | 'withdrawal') => {
|
|
return async (
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction
|
|
): Promise<void> => {
|
|
try {
|
|
if (!req.user) {
|
|
throw new AppError('Authentication required', 401);
|
|
}
|
|
|
|
const resourceId = req.params.id;
|
|
|
|
// Verificar ownership según tipo de recurso
|
|
// Esto se debe implementar en el repository
|
|
const repository = new InvestmentRepository();
|
|
let isOwner = false;
|
|
|
|
if (resourceType === 'account') {
|
|
const account = await repository.getAccountById(resourceId);
|
|
isOwner = account?.user_id === req.user.id;
|
|
} else if (resourceType === 'withdrawal') {
|
|
const withdrawal = await repository.getWithdrawalRequestById(resourceId);
|
|
isOwner = withdrawal?.user_id === req.user.id;
|
|
}
|
|
|
|
if (!isOwner && req.user.role !== 'admin') {
|
|
logger.warn('Unauthorized resource access attempt', {
|
|
user_id: req.user.id,
|
|
resource_type: resourceType,
|
|
resource_id: resourceId,
|
|
});
|
|
throw new AppError('Access denied', 403);
|
|
}
|
|
|
|
next();
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
};
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Validaciones de Negocio
|
|
|
|
### 4.1 Investment Validator Service
|
|
|
|
```typescript
|
|
// src/services/validation/investment-validator.service.ts
|
|
|
|
import { InvestmentRepository } from '../../modules/investment/investment.repository';
|
|
import { AppError } from '../../utils/errors';
|
|
import { logger } from '../../utils/logger';
|
|
|
|
export class InvestmentValidatorService {
|
|
private repository: InvestmentRepository;
|
|
|
|
constructor() {
|
|
this.repository = new InvestmentRepository();
|
|
}
|
|
|
|
/**
|
|
* Valida que el usuario pueda crear una cuenta
|
|
*/
|
|
async validateAccountCreation(userId: string, productId: string): Promise<void> {
|
|
// Verificar KYC completado
|
|
const kycStatus = await this.checkKYCStatus(userId);
|
|
if (!kycStatus.completed) {
|
|
throw new AppError('KYC verification required before investing', 400);
|
|
}
|
|
|
|
// Verificar límite de cuentas por usuario
|
|
const accountCount = await this.repository.getAccountCountByUser(userId);
|
|
const MAX_ACCOUNTS = parseInt(process.env.MAX_ACCOUNTS_PER_USER || '10');
|
|
|
|
if (accountCount >= MAX_ACCOUNTS) {
|
|
throw new AppError(`Maximum ${MAX_ACCOUNTS} accounts allowed`, 400);
|
|
}
|
|
|
|
// Verificar que no exista cuenta duplicada
|
|
const existingAccount = await this.repository.getAccountByUserAndProduct(
|
|
userId,
|
|
productId
|
|
);
|
|
|
|
if (existingAccount) {
|
|
throw new AppError('Account already exists for this product', 409);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Valida un depósito
|
|
*/
|
|
async validateDeposit(
|
|
userId: string,
|
|
accountId: string,
|
|
amount: number
|
|
): Promise<void> {
|
|
// Verificar cuenta existe y pertenece al usuario
|
|
const account = await this.repository.getAccountById(accountId);
|
|
|
|
if (!account) {
|
|
throw new AppError('Account not found', 404);
|
|
}
|
|
|
|
if (account.user_id !== userId) {
|
|
throw new AppError('Access denied', 403);
|
|
}
|
|
|
|
if (account.status !== 'active') {
|
|
throw new AppError('Account is not active', 409);
|
|
}
|
|
|
|
// Verificar monto mínimo
|
|
const MIN_DEPOSIT = parseFloat(process.env.MIN_DEPOSIT_AMOUNT || '50');
|
|
if (amount < MIN_DEPOSIT) {
|
|
throw new AppError(`Minimum deposit is $${MIN_DEPOSIT}`, 400);
|
|
}
|
|
|
|
// Verificar monto máximo diario
|
|
const dailyTotal = await this.repository.getDailyDepositTotal(userId);
|
|
const MAX_DAILY = parseFloat(process.env.MAX_DAILY_DEPOSIT || '50000');
|
|
|
|
if (dailyTotal + amount > MAX_DAILY) {
|
|
throw new AppError(`Daily deposit limit of $${MAX_DAILY} exceeded`, 400);
|
|
}
|
|
|
|
// Verificar límite del producto
|
|
const product = await this.repository.getProductById(account.product_id);
|
|
|
|
if (product.max_investment) {
|
|
const totalInvested = account.total_deposited + amount;
|
|
if (totalInvested > product.max_investment) {
|
|
throw new AppError(
|
|
`Maximum investment for this product is $${product.max_investment}`,
|
|
400
|
|
);
|
|
}
|
|
}
|
|
|
|
// Velocity check - máximo 5 depósitos por hora
|
|
const recentDeposits = await this.repository.getRecentDeposits(userId, 3600);
|
|
if (recentDeposits.length >= 5) {
|
|
throw new AppError('Too many deposits. Please try again later', 429);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Valida un retiro
|
|
*/
|
|
async validateWithdrawal(
|
|
userId: string,
|
|
accountId: string,
|
|
amount: number
|
|
): Promise<void> {
|
|
// Verificar cuenta
|
|
const account = await this.repository.getAccountById(accountId);
|
|
|
|
if (!account) {
|
|
throw new AppError('Account not found', 404);
|
|
}
|
|
|
|
if (account.user_id !== userId) {
|
|
throw new AppError('Access denied', 403);
|
|
}
|
|
|
|
if (account.status !== 'active') {
|
|
throw new AppError('Account is not active', 409);
|
|
}
|
|
|
|
// Verificar balance suficiente
|
|
if (amount > account.current_balance) {
|
|
throw new AppError('Insufficient balance', 400);
|
|
}
|
|
|
|
// Verificar monto mínimo de retiro
|
|
const MIN_WITHDRAWAL = parseFloat(process.env.MIN_WITHDRAWAL_AMOUNT || '50');
|
|
if (amount < MIN_WITHDRAWAL) {
|
|
throw new AppError(`Minimum withdrawal is $${MIN_WITHDRAWAL}`, 400);
|
|
}
|
|
|
|
// Verificar que no haya solicitud de retiro pendiente
|
|
const pendingWithdrawal = await this.repository.getPendingWithdrawalByAccount(
|
|
accountId
|
|
);
|
|
|
|
if (pendingWithdrawal) {
|
|
throw new AppError('There is already a pending withdrawal request', 409);
|
|
}
|
|
|
|
// Verificar límite diario de retiros
|
|
const dailyWithdrawals = await this.repository.getDailyWithdrawalTotal(userId);
|
|
const MAX_DAILY_WITHDRAWAL = parseFloat(
|
|
process.env.MAX_DAILY_WITHDRAWAL || '25000'
|
|
);
|
|
|
|
if (dailyWithdrawals + amount > MAX_DAILY_WITHDRAWAL) {
|
|
throw new AppError(
|
|
`Daily withdrawal limit of $${MAX_DAILY_WITHDRAWAL} exceeded`,
|
|
400
|
|
);
|
|
}
|
|
|
|
// Verificar lock period (ej: no retiros en primeros 30 días)
|
|
const LOCK_PERIOD_DAYS = parseInt(process.env.ACCOUNT_LOCK_PERIOD_DAYS || '0');
|
|
if (LOCK_PERIOD_DAYS > 0) {
|
|
const accountAge = Date.now() - new Date(account.opened_at).getTime();
|
|
const lockPeriodMs = LOCK_PERIOD_DAYS * 24 * 60 * 60 * 1000;
|
|
|
|
if (accountAge < lockPeriodMs) {
|
|
const daysRemaining = Math.ceil(
|
|
(lockPeriodMs - accountAge) / (24 * 60 * 60 * 1000)
|
|
);
|
|
throw new AppError(
|
|
`Account is locked for withdrawals. ${daysRemaining} days remaining`,
|
|
400
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifica estado de KYC del usuario
|
|
*/
|
|
private async checkKYCStatus(userId: string): Promise<{
|
|
completed: boolean;
|
|
level: string;
|
|
}> {
|
|
// Implementar integración con servicio de KYC
|
|
// Por ahora, retornar mock
|
|
return {
|
|
completed: true,
|
|
level: 'basic',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Detecta actividad sospechosa
|
|
*/
|
|
async detectSuspiciousActivity(userId: string): Promise<boolean> {
|
|
// Múltiples cuentas creadas en corto tiempo
|
|
const recentAccounts = await this.repository.getRecentAccountsByUser(
|
|
userId,
|
|
86400
|
|
); // 24h
|
|
|
|
if (recentAccounts.length >= 3) {
|
|
logger.warn('Suspicious: Multiple accounts created', { user_id: userId });
|
|
return true;
|
|
}
|
|
|
|
// Patrón de depósito-retiro rápido
|
|
const recentTransactions = await this.repository.getRecentTransactions(
|
|
userId,
|
|
3600
|
|
); // 1h
|
|
|
|
const hasDepositAndWithdrawal =
|
|
recentTransactions.some((t) => t.type === 'deposit') &&
|
|
recentTransactions.some((t) => t.type === 'withdrawal');
|
|
|
|
if (hasDepositAndWithdrawal) {
|
|
logger.warn('Suspicious: Quick deposit-withdrawal pattern', {
|
|
user_id: userId,
|
|
});
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Rate Limiting
|
|
|
|
### 5.1 Rate Limiter Middleware
|
|
|
|
```typescript
|
|
// src/middlewares/rate-limit.middleware.ts
|
|
|
|
import rateLimit from 'express-rate-limit';
|
|
import RedisStore from 'rate-limit-redis';
|
|
import { createClient } from 'redis';
|
|
|
|
// Cliente Redis para rate limiting distribuido
|
|
const redisClient = createClient({
|
|
url: process.env.REDIS_URL || 'redis://localhost:6379',
|
|
});
|
|
|
|
redisClient.connect();
|
|
|
|
/**
|
|
* Rate limiter general para API
|
|
*/
|
|
export const apiLimiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000, // 15 minutos
|
|
max: 100, // 100 requests por ventana
|
|
message: 'Too many requests, please try again later',
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
store: new RedisStore({
|
|
client: redisClient,
|
|
prefix: 'rl:api:',
|
|
}),
|
|
});
|
|
|
|
/**
|
|
* Rate limiter estricto para operaciones sensibles
|
|
*/
|
|
export const strictLimiter = rateLimit({
|
|
windowMs: 60 * 60 * 1000, // 1 hora
|
|
max: 5, // 5 requests por hora
|
|
message: 'Too many requests for this operation',
|
|
store: new RedisStore({
|
|
client: redisClient,
|
|
prefix: 'rl:strict:',
|
|
}),
|
|
});
|
|
|
|
/**
|
|
* Rate limiter personalizado
|
|
*/
|
|
export const customRateLimit = (maxRequests: number, windowSeconds: number) => {
|
|
return rateLimit({
|
|
windowMs: windowSeconds * 1000,
|
|
max: maxRequests,
|
|
store: new RedisStore({
|
|
client: redisClient,
|
|
prefix: 'rl:custom:',
|
|
}),
|
|
});
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Encriptación de Datos
|
|
|
|
### 6.1 Encryption Service
|
|
|
|
```typescript
|
|
// src/services/security/encryption.service.ts
|
|
|
|
import crypto from 'crypto';
|
|
|
|
export class EncryptionService {
|
|
private algorithm = 'aes-256-gcm';
|
|
private key: Buffer;
|
|
|
|
constructor() {
|
|
const secret = process.env.ENCRYPTION_KEY!;
|
|
this.key = crypto.scryptSync(secret, 'salt', 32);
|
|
}
|
|
|
|
/**
|
|
* Encripta datos sensibles
|
|
*/
|
|
encrypt(text: string): string {
|
|
const iv = crypto.randomBytes(16);
|
|
const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);
|
|
|
|
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
encrypted += cipher.final('hex');
|
|
|
|
const authTag = cipher.getAuthTag();
|
|
|
|
// Retornar iv:authTag:encrypted
|
|
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
|
|
}
|
|
|
|
/**
|
|
* Desencripta datos
|
|
*/
|
|
decrypt(encryptedData: string): string {
|
|
const parts = encryptedData.split(':');
|
|
const iv = Buffer.from(parts[0], 'hex');
|
|
const authTag = Buffer.from(parts[1], 'hex');
|
|
const encrypted = parts[2];
|
|
|
|
const decipher = crypto.createDecipheriv(this.algorithm, this.key, iv);
|
|
decipher.setAuthTag(authTag);
|
|
|
|
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
decrypted += decipher.final('utf8');
|
|
|
|
return decrypted;
|
|
}
|
|
|
|
/**
|
|
* Hash de datos (one-way)
|
|
*/
|
|
hash(data: string): string {
|
|
return crypto.createHash('sha256').update(data).digest('hex');
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6.2 Uso en Modelo de Datos
|
|
|
|
```typescript
|
|
// Encriptar datos de banco antes de guardar
|
|
const encryptionService = new EncryptionService();
|
|
|
|
const destinationDetails = {
|
|
bank_account: '****1234',
|
|
routing_number: encryptionService.encrypt('026009593'),
|
|
account_holder_name: 'John Doe',
|
|
};
|
|
|
|
await repository.createWithdrawalRequest({
|
|
// ...
|
|
destination_details: destinationDetails,
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Auditoría y Logging
|
|
|
|
### 7.1 Audit Logger
|
|
|
|
```typescript
|
|
// src/services/security/audit-logger.service.ts
|
|
|
|
import { logger } from '../../utils/logger';
|
|
|
|
export enum AuditAction {
|
|
ACCOUNT_CREATED = 'ACCOUNT_CREATED',
|
|
DEPOSIT_INITIATED = 'DEPOSIT_INITIATED',
|
|
DEPOSIT_COMPLETED = 'DEPOSIT_COMPLETED',
|
|
WITHDRAWAL_REQUESTED = 'WITHDRAWAL_REQUESTED',
|
|
WITHDRAWAL_APPROVED = 'WITHDRAWAL_APPROVED',
|
|
WITHDRAWAL_REJECTED = 'WITHDRAWAL_REJECTED',
|
|
WITHDRAWAL_COMPLETED = 'WITHDRAWAL_COMPLETED',
|
|
ACCOUNT_PAUSED = 'ACCOUNT_PAUSED',
|
|
ACCOUNT_CLOSED = 'ACCOUNT_CLOSED',
|
|
SUSPICIOUS_ACTIVITY = 'SUSPICIOUS_ACTIVITY',
|
|
}
|
|
|
|
interface AuditLogEntry {
|
|
action: AuditAction;
|
|
user_id: string;
|
|
resource_type: string;
|
|
resource_id: string;
|
|
details?: Record<string, any>;
|
|
ip_address?: string;
|
|
user_agent?: string;
|
|
}
|
|
|
|
export class AuditLoggerService {
|
|
/**
|
|
* Registra evento de auditoría
|
|
*/
|
|
log(entry: AuditLogEntry): void {
|
|
logger.info('AUDIT', {
|
|
timestamp: new Date().toISOString(),
|
|
action: entry.action,
|
|
user_id: entry.user_id,
|
|
resource_type: entry.resource_type,
|
|
resource_id: entry.resource_id,
|
|
details: entry.details,
|
|
ip_address: entry.ip_address,
|
|
user_agent: entry.user_agent,
|
|
});
|
|
|
|
// Opcionalmente, guardar en tabla de auditoría
|
|
// await auditRepository.create(entry);
|
|
}
|
|
|
|
/**
|
|
* Log de evento de seguridad
|
|
*/
|
|
logSecurityEvent(
|
|
event: string,
|
|
userId: string,
|
|
severity: 'low' | 'medium' | 'high' | 'critical',
|
|
details?: Record<string, any>
|
|
): void {
|
|
logger.warn('SECURITY_EVENT', {
|
|
timestamp: new Date().toISOString(),
|
|
event,
|
|
user_id: userId,
|
|
severity,
|
|
details,
|
|
});
|
|
|
|
// Si es crítico, enviar alerta
|
|
if (severity === 'critical') {
|
|
// await alertService.sendSecurityAlert(event, userId, details);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 7.2 Uso en Controllers
|
|
|
|
```typescript
|
|
// En InvestmentController
|
|
const auditLogger = new AuditLoggerService();
|
|
|
|
async createAccount(req: Request, res: Response) {
|
|
// ... lógica de creación ...
|
|
|
|
auditLogger.log({
|
|
action: AuditAction.ACCOUNT_CREATED,
|
|
user_id: req.user!.id,
|
|
resource_type: 'account',
|
|
resource_id: account.id,
|
|
details: {
|
|
product_id: data.product_id,
|
|
initial_investment: data.initial_investment,
|
|
},
|
|
ip_address: req.ip,
|
|
user_agent: req.headers['user-agent'],
|
|
});
|
|
|
|
// ... respuesta ...
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Configuración de Seguridad
|
|
|
|
### 8.1 Variables de Entorno
|
|
|
|
```bash
|
|
# Authentication
|
|
JWT_SECRET=your-super-secret-jwt-key-change-in-production
|
|
JWT_EXPIRATION=1d
|
|
JWT_REFRESH_EXPIRATION=7d
|
|
|
|
# Encryption
|
|
ENCRYPTION_KEY=your-super-secret-encryption-key-32-chars
|
|
|
|
# Rate Limiting
|
|
REDIS_URL=redis://localhost:6379
|
|
RATE_LIMIT_WINDOW_MS=900000
|
|
RATE_LIMIT_MAX_REQUESTS=100
|
|
|
|
# Business Limits
|
|
MAX_ACCOUNTS_PER_USER=10
|
|
MIN_DEPOSIT_AMOUNT=50.00
|
|
MAX_DAILY_DEPOSIT=50000.00
|
|
MIN_WITHDRAWAL_AMOUNT=50.00
|
|
MAX_DAILY_WITHDRAWAL=25000.00
|
|
ACCOUNT_LOCK_PERIOD_DAYS=30
|
|
|
|
# KYC
|
|
REQUIRE_KYC_FOR_INVESTMENT=true
|
|
KYC_SERVICE_URL=https://kyc-service.example.com
|
|
```
|
|
|
|
### 8.2 Helmet Configuration
|
|
|
|
```typescript
|
|
// src/app.ts
|
|
|
|
import helmet from 'helmet';
|
|
|
|
app.use(
|
|
helmet({
|
|
contentSecurityPolicy: {
|
|
directives: {
|
|
defaultSrc: ["'self'"],
|
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
scriptSrc: ["'self'"],
|
|
imgSrc: ["'self'", 'data:', 'https:'],
|
|
},
|
|
},
|
|
hsts: {
|
|
maxAge: 31536000,
|
|
includeSubDomains: true,
|
|
preload: true,
|
|
},
|
|
})
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Testing de Seguridad
|
|
|
|
### 9.1 Security Tests
|
|
|
|
```typescript
|
|
// tests/security/auth.test.ts
|
|
|
|
describe('Authentication Security', () => {
|
|
it('should reject requests without token', async () => {
|
|
const response = await request(app).get('/api/v1/investment/accounts');
|
|
|
|
expect(response.status).toBe(401);
|
|
});
|
|
|
|
it('should reject expired tokens', async () => {
|
|
const expiredToken = generateExpiredToken();
|
|
|
|
const response = await request(app)
|
|
.get('/api/v1/investment/accounts')
|
|
.set('Authorization', `Bearer ${expiredToken}`);
|
|
|
|
expect(response.status).toBe(401);
|
|
expect(response.body.error).toContain('expired');
|
|
});
|
|
|
|
it('should reject access to other users accounts', async () => {
|
|
const user1Token = await getAuthToken('user1');
|
|
const user2AccountId = 'account-belongs-to-user2';
|
|
|
|
const response = await request(app)
|
|
.get(`/api/v1/investment/accounts/${user2AccountId}`)
|
|
.set('Authorization', `Bearer ${user1Token}`);
|
|
|
|
expect(response.status).toBe(403);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Checklist de Seguridad
|
|
|
|
### 10.1 Pre-Deployment Security Checklist
|
|
|
|
- [ ] Todas las rutas requieren autenticación
|
|
- [ ] JWT secret es fuerte y único
|
|
- [ ] Encryption key es de 32 caracteres
|
|
- [ ] Rate limiting configurado en todos los endpoints
|
|
- [ ] CORS configurado correctamente
|
|
- [ ] Helmet habilitado con CSP
|
|
- [ ] Logs de auditoría funcionando
|
|
- [ ] Validaciones de input en todos los endpoints
|
|
- [ ] Ownership verificado en recursos sensibles
|
|
- [ ] KYC habilitado para nuevas cuentas
|
|
- [ ] Límites de transacciones configurados
|
|
- [ ] Datos de banco encriptados
|
|
- [ ] HTTPS forzado en producción
|
|
- [ ] Variables de entorno seguras
|
|
- [ ] Secrets no en código fuente
|
|
|
|
---
|
|
|
|
## 11. Referencias
|
|
|
|
- OWASP Top 10
|
|
- JWT Best Practices
|
|
- PCI DSS Compliance
|
|
- GDPR Data Protection
|
|
- Express.js Security Best Practices
|