268
DEPLOYMENT.md
Archivo normal
268
DEPLOYMENT.md
Archivo normal
@@ -0,0 +1,268 @@
|
|||||||
|
# Deployment con Nginx - API Ping Service
|
||||||
|
|
||||||
|
Esta guía explica cómo configurar nginx como proxy reverso para el servicio de ping, asegurando que se obtenga correctamente la IP real de los clientes.
|
||||||
|
|
||||||
|
## 🔧 Configuración de Nginx
|
||||||
|
|
||||||
|
### 1. Configuración Básica
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
# Configuración para obtener IP real del cliente
|
||||||
|
real_ip_header X-Forwarded-For;
|
||||||
|
real_ip_recursive on;
|
||||||
|
|
||||||
|
# IPs confiables (ajustar según tu infraestructura)
|
||||||
|
set_real_ip_from 10.0.0.0/8;
|
||||||
|
set_real_ip_from 172.16.0.0/12;
|
||||||
|
set_real_ip_from 192.168.0.0/16;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Rate Limiting en Nginx
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# En el bloque http {}
|
||||||
|
http {
|
||||||
|
# Zona de rate limiting para API general
|
||||||
|
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;
|
||||||
|
|
||||||
|
# Zona específica para ping (más restrictiva)
|
||||||
|
limit_req_zone $binary_remote_addr zone=ping:10m rate=10r/m;
|
||||||
|
|
||||||
|
# Rate limiting por IP real (después del proxy)
|
||||||
|
limit_req_zone $realip_remote_addr zone=real_ip:10m rate=10r/m;
|
||||||
|
}
|
||||||
|
|
||||||
|
# En el bloque server {}
|
||||||
|
server {
|
||||||
|
location /api/ping {
|
||||||
|
limit_req zone=ping burst=5 nodelay;
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
# ... otros headers de proxy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. SSL/HTTPS (Recomendado)
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
ssl_certificate /path/to/certificate.pem;
|
||||||
|
ssl_certificate_key /path/to/private.key;
|
||||||
|
|
||||||
|
# Headers de seguridad
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
|
||||||
|
# ... resto de la configuración
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌐 Configuración para Cloudflare
|
||||||
|
|
||||||
|
Si usas Cloudflare, agrega estas IPs confiables:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# IPs de Cloudflare (actualizar periódicamente)
|
||||||
|
set_real_ip_from 173.245.48.0/20;
|
||||||
|
set_real_ip_from 103.21.244.0/22;
|
||||||
|
set_real_ip_from 103.22.200.0/22;
|
||||||
|
# ... (ver archivo nginx.conf completo)
|
||||||
|
|
||||||
|
# Headers específicos para Cloudflare
|
||||||
|
real_ip_header CF-Connecting-IP;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Pasos de Deployment
|
||||||
|
|
||||||
|
### 1. Preparar la Aplicación
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Compilar para producción
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Iniciar en modo producción
|
||||||
|
npm start
|
||||||
|
|
||||||
|
# O usar PM2 para gestión de procesos
|
||||||
|
npm install -g pm2
|
||||||
|
pm2 start npm --name "api-ping" -- start
|
||||||
|
pm2 save
|
||||||
|
pm2 startup
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configurar Nginx
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copiar configuración
|
||||||
|
sudo cp nginx.conf /etc/nginx/sites-available/api-ping
|
||||||
|
sudo ln -s /etc/nginx/sites-available/api-ping /etc/nginx/sites-enabled/
|
||||||
|
|
||||||
|
# Verificar configuración
|
||||||
|
sudo nginx -t
|
||||||
|
|
||||||
|
# Recargar nginx
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Variables de Entorno
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env.production
|
||||||
|
NODE_ENV=production
|
||||||
|
TRUST_PROXY=true
|
||||||
|
RATE_LIMIT_MAX=10
|
||||||
|
RATE_LIMIT_WINDOW_MS=60000
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Verificación del Setup
|
||||||
|
|
||||||
|
### 1. Verificar Headers de Proxy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verificar que nginx está pasando los headers correctos
|
||||||
|
curl -H "X-Forwarded-For: 203.0.113.1" http://your-domain.com/api/status
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Test de Rate Limiting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Script para probar rate limiting
|
||||||
|
for i in {1..15}; do
|
||||||
|
echo "Request $i:"
|
||||||
|
curl -s http://your-domain.com/api/ping \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"target":"8.8.8.8","count":1}' \
|
||||||
|
| jq '.rateLimit // .error'
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Verificar IP del Cliente
|
||||||
|
|
||||||
|
El endpoint `/api/status` ahora incluye información de debugging:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"clientInfo": {
|
||||||
|
"ip": "203.0.113.1",
|
||||||
|
"isProxied": true,
|
||||||
|
"userAgent": "curl/7.68.0",
|
||||||
|
"proxyHeaders": {
|
||||||
|
"x-forwarded-for": "203.0.113.1",
|
||||||
|
"x-real-ip": "203.0.113.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛡️ Seguridad Adicional
|
||||||
|
|
||||||
|
### 1. Fail2ban para Protección DDoS
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# /etc/fail2ban/jail.local
|
||||||
|
[api-ping]
|
||||||
|
enabled = true
|
||||||
|
port = http,https
|
||||||
|
filter = api-ping
|
||||||
|
logpath = /var/log/nginx/api-ping-access.log
|
||||||
|
maxretry = 20
|
||||||
|
bantime = 3600
|
||||||
|
findtime = 300
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Monitoreo de Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Monitorear requests sospechosos
|
||||||
|
tail -f /var/log/nginx/api-ping-access.log | grep -E "(429|5[0-9]{2})"
|
||||||
|
|
||||||
|
# Analizar IPs con más requests
|
||||||
|
awk '{print $1}' /var/log/nginx/api-ping-access.log | sort | uniq -c | sort -nr | head -10
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Monitoreo y Métricas
|
||||||
|
|
||||||
|
### 1. Nginx Status
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location /nginx_status {
|
||||||
|
stub_status on;
|
||||||
|
access_log off;
|
||||||
|
allow 127.0.0.1;
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Logs Estructurados
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
log_format api_ping '$remote_addr - $remote_user [$time_local] '
|
||||||
|
'"$request" $status $body_bytes_sent '
|
||||||
|
'"$http_referer" "$http_user_agent" '
|
||||||
|
'"$http_x_forwarded_for" "$http_x_real_ip" '
|
||||||
|
'$request_time $upstream_response_time';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/api-ping-access.log api_ping;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Troubleshooting
|
||||||
|
|
||||||
|
### Problema: IP incorrecta
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verificar headers que llegan a la aplicación
|
||||||
|
curl -H "X-Debug: true" http://your-domain.com/api/status
|
||||||
|
|
||||||
|
# Verificar configuración de nginx
|
||||||
|
sudo nginx -T | grep -A 10 -B 10 real_ip
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problema: Rate limiting no funciona
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verificar que la IP se está obteniendo correctamente
|
||||||
|
# La aplicación debe usar la IP real, no la del proxy (127.0.0.1)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problema: CORS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verificar headers CORS
|
||||||
|
curl -H "Origin: http://example.com" -H "Access-Control-Request-Method: POST" \
|
||||||
|
-X OPTIONS http://your-domain.com/api/ping -v
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 Checklist de Deployment
|
||||||
|
|
||||||
|
- [ ] Aplicación compilada para producción
|
||||||
|
- [ ] PM2 o similar configurado para gestión de procesos
|
||||||
|
- [ ] Nginx configurado con proxy_pass
|
||||||
|
- [ ] Headers X-Forwarded-* configurados
|
||||||
|
- [ ] real_ip_header configurado
|
||||||
|
- [ ] IPs confiables (set_real_ip_from) configuradas
|
||||||
|
- [ ] Rate limiting configurado en nginx (opcional)
|
||||||
|
- [ ] SSL/HTTPS configurado
|
||||||
|
- [ ] Headers de seguridad configurados
|
||||||
|
- [ ] Logs configurados
|
||||||
|
- [ ] Monitoring configurado
|
||||||
|
- [ ] Backup y recovery plan preparado
|
||||||
|
|
||||||
|
Con esta configuración, el servicio obtendrá correctamente la IP real de los clientes incluso cuando esté detrás de nginx, Cloudflare u otros proxies. 🎉
|
||||||
@@ -1,4 +1,29 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {};
|
const nextConfig = {
|
||||||
|
// Configuración para proxy reverso (nginx)
|
||||||
|
async headers() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: '/api/:path*',
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'X-Forwarded-Proto',
|
||||||
|
value: 'https',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
// Configuración experimental para mejorar el manejo de headers
|
||||||
|
experimental: {
|
||||||
|
serverComponentsExternalPackages: ['ping'],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Configuración de red
|
||||||
|
env: {
|
||||||
|
TRUST_PROXY: 'true',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
167
nginx.conf
Archivo normal
167
nginx.conf
Archivo normal
@@ -0,0 +1,167 @@
|
|||||||
|
# Configuración de nginx para API Ping Service
|
||||||
|
# Este archivo debe ser adaptado según tu configuración específica
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name your-domain.com; # Cambiar por tu dominio
|
||||||
|
|
||||||
|
# Redirección HTTPS (recomendado para producción)
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
listen [::]:443 ssl http2;
|
||||||
|
server_name your-domain.com; # Cambiar por tu dominio
|
||||||
|
|
||||||
|
# Configuración SSL (ajustar rutas según tu setup)
|
||||||
|
ssl_certificate /path/to/your/certificate.pem;
|
||||||
|
ssl_certificate_key /path/to/your/private.key;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
|
||||||
|
|
||||||
|
# Headers de seguridad
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
|
||||||
|
|
||||||
|
# Configuración para obtener la IP real del cliente
|
||||||
|
real_ip_header X-Forwarded-For;
|
||||||
|
real_ip_recursive on;
|
||||||
|
|
||||||
|
# IPs confiables (ajustar según tu infraestructura)
|
||||||
|
# Cloudflare IPs (si usas Cloudflare)
|
||||||
|
set_real_ip_from 173.245.48.0/20;
|
||||||
|
set_real_ip_from 103.21.244.0/22;
|
||||||
|
set_real_ip_from 103.22.200.0/22;
|
||||||
|
set_real_ip_from 103.31.4.0/22;
|
||||||
|
set_real_ip_from 141.101.64.0/18;
|
||||||
|
set_real_ip_from 108.162.192.0/18;
|
||||||
|
set_real_ip_from 190.93.240.0/20;
|
||||||
|
set_real_ip_from 188.114.96.0/20;
|
||||||
|
set_real_ip_from 197.234.240.0/22;
|
||||||
|
set_real_ip_from 198.41.128.0/17;
|
||||||
|
set_real_ip_from 162.158.0.0/15;
|
||||||
|
set_real_ip_from 104.16.0.0/13;
|
||||||
|
set_real_ip_from 104.24.0.0/14;
|
||||||
|
set_real_ip_from 172.64.0.0/13;
|
||||||
|
set_real_ip_from 131.0.72.0/22;
|
||||||
|
set_real_ip_from 2400:cb00::/32;
|
||||||
|
set_real_ip_from 2606:4700::/32;
|
||||||
|
set_real_ip_from 2803:f800::/32;
|
||||||
|
set_real_ip_from 2405:b500::/32;
|
||||||
|
set_real_ip_from 2405:8100::/32;
|
||||||
|
set_real_ip_from 2a06:98c0::/29;
|
||||||
|
set_real_ip_from 2c0f:f248::/32;
|
||||||
|
|
||||||
|
# IPs locales/privadas (ajustar según tu red)
|
||||||
|
set_real_ip_from 10.0.0.0/8;
|
||||||
|
set_real_ip_from 172.16.0.0/12;
|
||||||
|
set_real_ip_from 192.168.0.0/16;
|
||||||
|
|
||||||
|
# Proxy a Next.js
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:3000; # Ajustar puerto si es necesario
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
|
||||||
|
# Timeouts
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configuración específica para API endpoints
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
|
||||||
|
# Rate limiting adicional en nginx (opcional)
|
||||||
|
limit_req zone=api burst=20 nodelay;
|
||||||
|
|
||||||
|
# Headers CORS para API
|
||||||
|
add_header Access-Control-Allow-Origin "*" always;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
|
||||||
|
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
|
||||||
|
|
||||||
|
# Manejar preflight requests
|
||||||
|
if ($request_method = OPTIONS) {
|
||||||
|
add_header Access-Control-Allow-Origin "*";
|
||||||
|
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
||||||
|
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
|
||||||
|
add_header Content-Length 0;
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Rate limiting para proteger la API
|
||||||
|
location /api/ping {
|
||||||
|
limit_req zone=ping burst=5 nodelay;
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Archivos estáticos con cache
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
add_header X-Cache-Status "STATIC";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
access_log /var/log/nginx/api-ping-access.log;
|
||||||
|
error_log /var/log/nginx/api-ping-error.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Rate limiting zones (definir en http block)
|
||||||
|
# Agregar estas líneas al bloque http {} en nginx.conf:
|
||||||
|
#
|
||||||
|
# http {
|
||||||
|
# # Rate limiting para API general
|
||||||
|
# limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;
|
||||||
|
#
|
||||||
|
# # Rate limiting específico para ping (más restrictivo)
|
||||||
|
# limit_req_zone $binary_remote_addr zone=ping:10m rate=10r/m;
|
||||||
|
#
|
||||||
|
# # Rate limiting por IP real (considerando proxy)
|
||||||
|
# limit_req_zone $realip_remote_addr zone=real_ip:10m rate=10r/m;
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Configuración adicional para development/testing
|
||||||
|
# Archivo separado: nginx-dev.conf
|
||||||
|
#
|
||||||
|
# server {
|
||||||
|
# listen 80;
|
||||||
|
# server_name localhost;
|
||||||
|
#
|
||||||
|
# location / {
|
||||||
|
# proxy_pass http://127.0.0.1:3000;
|
||||||
|
# proxy_set_header Host $host;
|
||||||
|
# proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
# }
|
||||||
|
# }
|
||||||
@@ -3,17 +3,25 @@ import { NextResponse } from 'next/server';
|
|||||||
import { RateLimiter } from '../../../lib/rate-limiter';
|
import { RateLimiter } from '../../../lib/rate-limiter';
|
||||||
import { validateIP } from '../../../lib/validators';
|
import { validateIP } from '../../../lib/validators';
|
||||||
import { PING_CONFIG } from '../../../lib/config.js';
|
import { PING_CONFIG } from '../../../lib/config.js';
|
||||||
|
import { getClientIP, getProxyInfo } from '../../../lib/proxy-utils.js';
|
||||||
|
|
||||||
// Crear instancia del rate limiter
|
// Crear instancia del rate limiter
|
||||||
const rateLimiter = new RateLimiter();
|
const rateLimiter = new RateLimiter();
|
||||||
|
|
||||||
export async function POST(request) {
|
export async function POST(request) {
|
||||||
try {
|
try {
|
||||||
// Obtener IP del cliente para rate limiting
|
// Obtener IP del cliente considerando proxies (nginx, cloudflare, etc.)
|
||||||
const forwarded = request.headers.get('x-forwarded-for');
|
const clientIP = getClientIP(request);
|
||||||
const clientIP = forwarded
|
const proxyInfo = getProxyInfo(request);
|
||||||
? forwarded.split(',')[0].trim()
|
|
||||||
: request.headers.get('x-real-ip') || 'unknown';
|
// Log para debugging en desarrollo
|
||||||
|
if (PING_CONFIG.DEBUG.LOG_REQUESTS) {
|
||||||
|
console.log('Ping request from:', {
|
||||||
|
clientIP,
|
||||||
|
isProxied: proxyInfo.isProxied,
|
||||||
|
proxyHeaders: proxyInfo.proxyHeaders
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Verificar rate limit
|
// Verificar rate limit
|
||||||
const rateLimitResult = await rateLimiter.checkLimit(clientIP);
|
const rateLimitResult = await rateLimiter.checkLimit(clientIP);
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { RateLimiter } from '../../../lib/rate-limiter';
|
import { RateLimiter } from '../../../lib/rate-limiter';
|
||||||
|
import { getClientIP, getProxyInfo } from '../../../lib/proxy-utils.js';
|
||||||
|
|
||||||
// Crear instancia del rate limiter
|
// Crear instancia del rate limiter
|
||||||
const rateLimiter = new RateLimiter();
|
const rateLimiter = new RateLimiter();
|
||||||
|
|
||||||
export async function GET(request) {
|
export async function GET(request) {
|
||||||
try {
|
try {
|
||||||
// Obtener IP del cliente
|
// Obtener IP del cliente considerando proxies
|
||||||
const forwarded = request.headers.get('x-forwarded-for');
|
const clientIP = getClientIP(request);
|
||||||
const clientIP = forwarded
|
const proxyInfo = getProxyInfo(request);
|
||||||
? forwarded.split(',')[0].trim()
|
|
||||||
: request.headers.get('x-real-ip') || 'unknown';
|
|
||||||
|
|
||||||
// Obtener estadísticas del rate limiter para este cliente
|
// Obtener estadísticas del rate limiter para este cliente
|
||||||
const rateLimitStats = rateLimiter.getStats(clientIP);
|
const rateLimitStats = rateLimiter.getStats(clientIP);
|
||||||
@@ -23,7 +22,12 @@ export async function GET(request) {
|
|||||||
uptime: process.uptime(),
|
uptime: process.uptime(),
|
||||||
clientInfo: {
|
clientInfo: {
|
||||||
ip: clientIP,
|
ip: clientIP,
|
||||||
rateLimit: rateLimitStats
|
isProxied: proxyInfo.isProxied,
|
||||||
|
userAgent: proxyInfo.userAgent,
|
||||||
|
rateLimit: rateLimitStats,
|
||||||
|
...(process.env.NODE_ENV === 'development' && {
|
||||||
|
proxyHeaders: proxyInfo.proxyHeaders
|
||||||
|
})
|
||||||
},
|
},
|
||||||
features: {
|
features: {
|
||||||
maxPingsPerRequest: 10,
|
maxPingsPerRequest: 10,
|
||||||
|
|||||||
129
src/lib/proxy-utils.js
Archivo normal
129
src/lib/proxy-utils.js
Archivo normal
@@ -0,0 +1,129 @@
|
|||||||
|
/**
|
||||||
|
* Extrae la IP real del cliente considerando proxies como nginx
|
||||||
|
* @param {Request} request - La request de Next.js
|
||||||
|
* @returns {string} La IP del cliente
|
||||||
|
*/
|
||||||
|
export function getClientIP(request) {
|
||||||
|
// Headers que pueden contener la IP real del cliente
|
||||||
|
const forwardedFor = request.headers.get('x-forwarded-for');
|
||||||
|
const realIP = request.headers.get('x-real-ip');
|
||||||
|
const cfConnectingIP = request.headers.get('cf-connecting-ip'); // Cloudflare
|
||||||
|
const trueClientIP = request.headers.get('true-client-ip'); // Cloudflare Enterprise
|
||||||
|
const xClientIP = request.headers.get('x-client-ip');
|
||||||
|
const xForwardedHost = request.headers.get('x-forwarded-host');
|
||||||
|
|
||||||
|
// Prioridad de headers (más confiables primero)
|
||||||
|
let clientIP = null;
|
||||||
|
|
||||||
|
// 1. Cloudflare headers (muy confiables)
|
||||||
|
if (trueClientIP) {
|
||||||
|
clientIP = trueClientIP.trim();
|
||||||
|
} else if (cfConnectingIP) {
|
||||||
|
clientIP = cfConnectingIP.trim();
|
||||||
|
}
|
||||||
|
// 2. X-Real-IP (nginx real_ip_module)
|
||||||
|
else if (realIP) {
|
||||||
|
clientIP = realIP.trim();
|
||||||
|
}
|
||||||
|
// 3. X-Forwarded-For (puede contener múltiples IPs)
|
||||||
|
else if (forwardedFor) {
|
||||||
|
// X-Forwarded-For puede contener múltiples IPs separadas por comas
|
||||||
|
// La primera es la IP original del cliente
|
||||||
|
const ips = forwardedFor.split(',').map(ip => ip.trim());
|
||||||
|
clientIP = ips[0];
|
||||||
|
}
|
||||||
|
// 4. Otros headers menos comunes
|
||||||
|
else if (xClientIP) {
|
||||||
|
clientIP = xClientIP.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar que la IP obtenida sea válida
|
||||||
|
if (clientIP && isValidIP(clientIP)) {
|
||||||
|
return clientIP;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: usar una IP por defecto
|
||||||
|
return '127.0.0.1';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valida si una cadena es una IP válida (IPv4 o IPv6)
|
||||||
|
* @param {string} ip - La IP a validar
|
||||||
|
* @returns {boolean} True si es válida
|
||||||
|
*/
|
||||||
|
function isValidIP(ip) {
|
||||||
|
// Regex básico para IPv4
|
||||||
|
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||||
|
// Regex básico para IPv6
|
||||||
|
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::1$|^::$/;
|
||||||
|
|
||||||
|
if (ipv4Regex.test(ip)) {
|
||||||
|
// Validar rangos de IPv4 (0-255)
|
||||||
|
const parts = ip.split('.');
|
||||||
|
return parts.every(part => {
|
||||||
|
const num = parseInt(part, 10);
|
||||||
|
return num >= 0 && num <= 255;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ipv6Regex.test(ip) || ip === '::1' || ip.startsWith('::ffff:')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene información detallada del proxy y cliente
|
||||||
|
* @param {Request} request - La request de Next.js
|
||||||
|
* @returns {object} Información del cliente y proxy
|
||||||
|
*/
|
||||||
|
export function getProxyInfo(request) {
|
||||||
|
const headers = {};
|
||||||
|
|
||||||
|
// Recopilar todos los headers relevantes para debugging
|
||||||
|
const proxyHeaders = [
|
||||||
|
'x-forwarded-for',
|
||||||
|
'x-real-ip',
|
||||||
|
'x-forwarded-proto',
|
||||||
|
'x-forwarded-host',
|
||||||
|
'x-forwarded-port',
|
||||||
|
'cf-connecting-ip',
|
||||||
|
'true-client-ip',
|
||||||
|
'x-client-ip',
|
||||||
|
'x-cluster-client-ip',
|
||||||
|
'forwarded'
|
||||||
|
];
|
||||||
|
|
||||||
|
proxyHeaders.forEach(header => {
|
||||||
|
const value = request.headers.get(header);
|
||||||
|
if (value) {
|
||||||
|
headers[header] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const clientIP = getClientIP(request);
|
||||||
|
|
||||||
|
return {
|
||||||
|
clientIP,
|
||||||
|
proxyHeaders: headers,
|
||||||
|
isProxied: Object.keys(headers).length > 0,
|
||||||
|
userAgent: request.headers.get('user-agent') || 'unknown'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware helper para configurar trust proxy en desarrollo
|
||||||
|
* @param {Request} request
|
||||||
|
* @returns {object} Configuración de trust
|
||||||
|
*/
|
||||||
|
export function getTrustProxyConfig(request) {
|
||||||
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
const trustProxy = process.env.TRUST_PROXY === 'true';
|
||||||
|
|
||||||
|
return {
|
||||||
|
trustProxy: isProduction || trustProxy,
|
||||||
|
clientIP: getClientIP(request),
|
||||||
|
environment: process.env.NODE_ENV || 'development'
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
|
import { getClientIP } from './lib/proxy-utils.js';
|
||||||
|
|
||||||
export function middleware(request) {
|
export function middleware(request) {
|
||||||
// Crear la respuesta
|
// Crear la respuesta
|
||||||
const response = NextResponse.next();
|
const response = NextResponse.next();
|
||||||
|
|
||||||
|
// Obtener IP real del cliente
|
||||||
|
const clientIP = getClientIP(request);
|
||||||
|
|
||||||
// Agregar headers de seguridad
|
// Agregar headers de seguridad
|
||||||
response.headers.set('X-Content-Type-Options', 'nosniff');
|
response.headers.set('X-Content-Type-Options', 'nosniff');
|
||||||
response.headers.set('X-Frame-Options', 'DENY');
|
response.headers.set('X-Frame-Options', 'DENY');
|
||||||
@@ -12,16 +16,36 @@ export function middleware(request) {
|
|||||||
|
|
||||||
// Headers específicos para API
|
// Headers específicos para API
|
||||||
if (request.nextUrl.pathname.startsWith('/api/')) {
|
if (request.nextUrl.pathname.startsWith('/api/')) {
|
||||||
|
// CORS headers
|
||||||
response.headers.set('Access-Control-Allow-Origin', '*');
|
response.headers.set('Access-Control-Allow-Origin', '*');
|
||||||
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||||
response.headers.set('Access-Control-Allow-Headers', 'Content-Type');
|
response.headers.set('Access-Control-Allow-Headers', 'Content-Type');
|
||||||
|
|
||||||
|
// Headers para debugging de proxy (solo en desarrollo)
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
response.headers.set('X-Debug-Client-IP', clientIP);
|
||||||
|
response.headers.set('X-Debug-Original-IP', request.ip || 'unknown');
|
||||||
|
}
|
||||||
|
|
||||||
// Manejar preflight requests
|
// Manejar preflight requests
|
||||||
if (request.method === 'OPTIONS') {
|
if (request.method === 'OPTIONS') {
|
||||||
return new Response(null, { status: 200, headers: response.headers });
|
return new Response(null, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
||||||
|
'Access-Control-Allow-Headers': 'Content-Type',
|
||||||
|
'Access-Control-Max-Age': '86400', // 24 horas
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Headers para contenido estático
|
||||||
|
if (request.nextUrl.pathname.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg)$/)) {
|
||||||
|
response.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
|
||||||
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Referencia en una nueva incidencia
Block a user