130 líneas
3.6 KiB
JavaScript
130 líneas
3.6 KiB
JavaScript
/**
|
|
* 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'
|
|
};
|
|
}
|