Español
Arquitectura
Seguridad

Seguridad

ReliaPulse implementa múltiples capas de seguridad para proteger tus datos y asegurar una operación segura.

Autenticación

Seguridad de Contraseñas

Las contraseñas se hashean usando bcrypt con factor de costo 10:

import bcrypt from "bcryptjs";
 
// Hasheo
const hash = await bcrypt.hash(password, 10);
 
// Verificación
const isValid = await bcrypt.compare(password, hash);

Requisitos de contraseña:

  • Mínimo 8 caracteres
  • Validada tanto en cliente como en servidor

Autenticación de Dos Factores (2FA)

2FA basado en TOTP siguiendo RFC 6238:

  • Flujo de Configuración:

    1. Generar secreto con otplib
    2. Crear código QR para app autenticadora
    3. Usuario verifica con código de 6 dígitos
    4. Se generan 10 códigos de respaldo
  • Flujo de Login:

    1. Usuario ingresa email/contraseña
    2. Frontend llama a /api/auth/check-2fa para validar credenciales
    3. Si son válidas y 2FA está habilitado, muestra input de código
    4. Usuario ingresa código de 6 dígitos (o código de respaldo)
    5. Frontend llama a NextAuth signIn() con credenciales + código 2FA
    6. Servidor verifica TOTP y completa el login
  • Almacenamiento del Secreto:

    • Encriptado con AES-256-GCM
    • Clave de encriptación desde variable ENCRYPTION_KEY
// Encriptación
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const encrypted = cipher.update(secret, 'utf8', 'hex');
 
// Desencriptación
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
const decrypted = decipher.update(encrypted, 'hex', 'utf8');

Autenticación con API Key

API keys para acceso programático:

  • Almacenamiento: Hash SHA-256 (no reversible)
  • Formato: sk_live_<random_base64url> (43 chars)
  • Permisos: Granulares, por recurso
// Generación de key
const rawKey = `sk_live_${crypto.randomBytes(32).toString('base64url')}`;
const hash = crypto.createHash('sha256').update(rawKey).digest('hex');
 
// Validación
const inputHash = crypto.createHash('sha256').update(inputKey).digest('hex');
const isValid = inputHash === storedHash;

SSO/SAML

SSO empresarial via SAML Jackson:

  • Compatible con SAML 2.0
  • Importación de metadata del IdP
  • Exportación de metadata del SP
  • Detección SSO basada en dominio

Autorización

Control de Acceso Basado en Roles (RBAC)

8 roles predefinidos con permisos granulares:

RolDescripción
OWNERAcceso completo, puede eliminar organización
ADMINGestionar todo excepto facturación
MEMBERCrear/editar componentes, incidentes
VIEWERAcceso solo lectura
INCIDENT_MANAGERGestionar incidentes y postmortems
ONCALL_RESPONDERGestionar on-call y alertas
STATUS_EDITOREditar páginas de estado y branding
SUBSCRIBER_MANAGERGestionar suscriptores y canales

Roles Personalizados

Las organizaciones pueden crear roles personalizados:

// Crear rol personalizado con permisos específicos
POST /api/v1/organization/roles
{
  "name": "Developer",
  "permissions": [
    "COMPONENTS_VIEW",
    "COMPONENTS_CREATE",
    "INCIDENTS_VIEW",
    "INCIDENTS_CREATE"
  ]
}

Verificación de Permisos

Cada ruta API valida permisos:

import { requirePermission, PERMISSIONS } from "@/lib/rbac";
 
export async function POST(request: NextRequest) {
  const authResult = await requirePermission(
    request,
    PERMISSIONS.COMPONENTS_CREATE
  );
  if (authResult.error) return authResult.error;
 
  // Usuario tiene permiso, continuar
}

Validación de Entrada

Validación de Requests

Todas las entradas validadas con Zod:

const createComponentSchema = z.object({
  name: z.string().min(1).max(100),
  description: z.string().max(500).optional(),
  type: z.enum(["SERVICE", "ENDPOINT", "METRIC"]),
  url: z.string().url().optional(),
});
 
// Validar body del request
const body = await request.json();
const result = createComponentSchema.safeParse(body);
if (!result.success) {
  return NextResponse.json(
    { error: result.error.issues },
    { status: 400 }
  );
}

Validación de URLs

URLs externas validadas para protección SSRF.

Protección SSRF

Prevención de Server-Side Request Forgery:

import { validateExternalUrl } from "@/lib/security/ssrf";
 
// Direcciones bloqueadas
const BLOCKED = [
  // Localhost
  "127.0.0.0/8", "localhost", "::1",
  // Redes privadas
  "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16",
  // Link-local
  "169.254.0.0/16", "fe80::/10",
  // Metadata cloud
  "169.254.169.254", "metadata.google.internal"
];
 
// Validación
const validation = await validateExternalUrl(url);
if (!validation.isValid) {
  throw new Error(validation.error);
}

Aplicado a:

  • URLs de suscriptores webhook
  • Endpoints de canales de notificación
  • URLs de integración (Prometheus, Grafana)
  • Targets de monitores uptime

Rate Limiting

Rate limiting en memoria con ventana deslizante:

// middleware.ts
const RATE_LIMITS = {
  api: { window: 60_000, max: 100 },     // 100/min
  auth: { window: 60_000, max: 30 },      // 30/min
  webhook: { window: 60_000, max: 200 },  // 200/min
  public: { window: 60_000, max: 60 },    // 60/min
};

Headers de respuesta:

  • X-RateLimit-Limit - Requests máximos
  • X-RateLimit-Remaining - Requests restantes
  • X-RateLimit-Reset - Timestamp de reset de ventana
  • Retry-After - Segundos hasta reintento (en 429)

Protección de Datos

Aislamiento Multi-Tenant

Todas las queries filtran por organización:

// Cada query incluye organizationId
const components = await prisma.component.findMany({
  where: {
    organizationId: session.user.organizationId,
  },
});

Enmascaramiento de Datos Sensibles

API keys y secretos enmascarados en respuestas:

// Respuesta de API key
{
  "id": "key-123",
  "key": "sk_live_...abc", // Solo últimos 3 chars mostrados
  "permissions": ["components:read"]
}
 
// Config de integración
{
  "type": "DATADOG",
  "config": {
    "apiKey": "***REDACTED***",
    "appKey": "***REDACTED***"
  }
}

Logging de Auditoría

Todas las operaciones sensibles registradas:

await createAuditLog({
  action: "COMPONENT_CREATE",
  entityType: "COMPONENT",
  entityId: component.id,
  userId: session.user.id,
  organizationId,
  changes: { name: component.name },
});

Acciones registradas:

  • Eventos de autenticación
  • Operaciones CRUD de recursos
  • Cambios de permisos
  • Actualizaciones de configuración

Trazabilidad de Requests

Cada request asignado con ID único:

// middleware.ts
const requestId = request.headers.get('x-request-id')
  || crypto.randomUUID();
 
response.headers.set('x-request-id', requestId);

Usado para:

  • Correlación de logs
  • Debugging
  • Tickets de soporte

Headers de Seguridad

Headers recomendados (configurar en next.config.js):

const securityHeaders = [
  { key: 'X-DNS-Prefetch-Control', value: 'on' },
  { key: 'Strict-Transport-Security', value: 'max-age=63072000' },
  { key: 'X-Frame-Options', value: 'DENY' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'origin-when-cross-origin' },
];

Variables de Entorno

Secretos Requeridos

# Autenticación
NEXTAUTH_SECRET=<random-32-chars>
 
# Base de datos
DATABASE_URL=postgresql://...
 
# Encriptación (para secretos 2FA)
ENCRYPTION_KEY=<random-32-chars>

Generación

Generar secretos seguros:

# Usando openssl
openssl rand -base64 32
 
# Usando Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"

Políticas de Seguridad

Políticas de Organización

Las organizaciones pueden aplicar:

  • Requerir 2FA - Todos los miembros deben habilitar 2FA
  • Solo SSO - Deshabilitar login con contraseña
  • Dominios de Email - Restringir invitaciones a dominios específicos
PATCH /api/v1/organization
{
  "require2FA": true,
  "ssoEnforced": true,
  "allowedEmailDomains": ["company.com"]
}

Seguridad de Dependencias

Actualizaciones Automáticas

Dependabot configurado para actualizaciones de seguridad:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10

Escaneo de Vulnerabilidades

Auditorías regulares recomendadas:

npm audit
npm audit fix

Reportar Vulnerabilidades

Si descubres una vulnerabilidad de seguridad:

  1. No abras un issue público
  2. Envía email con preocupaciones de seguridad privadamente
  3. Incluye pasos para reproducir
  4. Permite tiempo para una corrección antes de divulgación