151 lines
4.1 KiB
TypeScript
151 lines
4.1 KiB
TypeScript
/**
|
|
* Função utilitária para extrair o IP do cliente de um Request HTTP
|
|
* Sem usar APIs externas - usa apenas headers HTTP
|
|
*/
|
|
|
|
/**
|
|
* Extrai o IP do cliente de um Request HTTP
|
|
* Considera headers como X-Forwarded-For, X-Real-IP, etc.
|
|
*/
|
|
export function getClientIP(request: Request): string | undefined {
|
|
// Headers que podem conter o IP do cliente (case-insensitive)
|
|
const getHeader = (name: string): string | null => {
|
|
// Tentar diferentes variações de case
|
|
const variations = [
|
|
name,
|
|
name.toLowerCase(),
|
|
name.toUpperCase(),
|
|
name.charAt(0).toUpperCase() + name.slice(1).toLowerCase()
|
|
];
|
|
|
|
for (const variation of variations) {
|
|
const value = request.headers.get(variation);
|
|
if (value) return value;
|
|
}
|
|
|
|
// As variações de case já cobrem a maioria dos casos
|
|
// Se não encontrou, retorna null
|
|
return null;
|
|
};
|
|
|
|
const forwardedFor = getHeader('x-forwarded-for');
|
|
const realIP = getHeader('x-real-ip');
|
|
const cfConnectingIP = getHeader('cf-connecting-ip'); // Cloudflare
|
|
const trueClientIP = getHeader('true-client-ip'); // Cloudflare Enterprise
|
|
const xClientIP = getHeader('x-client-ip');
|
|
const forwarded = getHeader('forwarded');
|
|
const remoteAddr = getHeader('remote-addr');
|
|
|
|
// Log para debug
|
|
console.log('Procurando IP nos headers:', {
|
|
'x-forwarded-for': forwardedFor,
|
|
'x-real-ip': realIP,
|
|
'cf-connecting-ip': cfConnectingIP,
|
|
'true-client-ip': trueClientIP,
|
|
'x-client-ip': xClientIP,
|
|
forwarded: forwarded,
|
|
'remote-addr': remoteAddr
|
|
});
|
|
|
|
// Prioridade: X-Forwarded-For pode conter múltiplos IPs (proxy chain)
|
|
// O primeiro IP é geralmente o IP original do cliente
|
|
if (forwardedFor) {
|
|
const ips = forwardedFor.split(',').map((ip) => ip.trim());
|
|
// Pegar o primeiro IP válido
|
|
for (const ip of ips) {
|
|
if (isValidIP(ip)) {
|
|
console.log('IP encontrado em X-Forwarded-For:', ip);
|
|
return ip;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Forwarded header (RFC 7239)
|
|
if (forwarded) {
|
|
// Formato: for=192.0.2.60;proto=http;by=203.0.113.43
|
|
const forMatch = forwarded.match(/for=([^;,\s]+)/i);
|
|
if (forMatch && forMatch[1]) {
|
|
const ip = forMatch[1].replace(/^\[|\]$/g, ''); // Remove brackets de IPv6
|
|
if (isValidIP(ip)) {
|
|
console.log('IP encontrado em Forwarded:', ip);
|
|
return ip;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Outros headers com IP único
|
|
if (realIP && isValidIP(realIP)) {
|
|
console.log('IP encontrado em X-Real-IP:', realIP);
|
|
return realIP;
|
|
}
|
|
|
|
if (cfConnectingIP && isValidIP(cfConnectingIP)) {
|
|
console.log('IP encontrado em CF-Connecting-IP:', cfConnectingIP);
|
|
return cfConnectingIP;
|
|
}
|
|
|
|
if (trueClientIP && isValidIP(trueClientIP)) {
|
|
console.log('IP encontrado em True-Client-IP:', trueClientIP);
|
|
return trueClientIP;
|
|
}
|
|
|
|
if (xClientIP && isValidIP(xClientIP)) {
|
|
console.log('IP encontrado em X-Client-IP:', xClientIP);
|
|
return xClientIP;
|
|
}
|
|
|
|
if (remoteAddr && isValidIP(remoteAddr)) {
|
|
console.log('IP encontrado em Remote-Addr:', remoteAddr);
|
|
return remoteAddr;
|
|
}
|
|
|
|
// Tentar extrair do URL (último recurso)
|
|
try {
|
|
const url = new URL(request.url);
|
|
// Se o servidor estiver configurado para passar IP via query param
|
|
const ipFromQuery = url.searchParams.get('ip');
|
|
if (ipFromQuery && isValidIP(ipFromQuery)) {
|
|
console.log('IP encontrado em query param:', ipFromQuery);
|
|
return ipFromQuery;
|
|
}
|
|
} catch {
|
|
// Ignorar erro de parsing do URL
|
|
}
|
|
|
|
console.log('Nenhum IP válido encontrado nos headers');
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Valida se uma string é um endereço IP válido (IPv4 ou IPv6)
|
|
*/
|
|
function isValidIP(ip: string): boolean {
|
|
if (!ip || ip.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
// Validar IPv4
|
|
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
if (ipv4Regex.test(ip)) {
|
|
const parts = ip.split('.');
|
|
return parts.every((part) => {
|
|
const num = parseInt(part, 10);
|
|
return num >= 0 && num <= 255;
|
|
});
|
|
}
|
|
|
|
// Validar IPv6 (formato simplificado)
|
|
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
|
|
if (ipv6Regex.test(ip)) {
|
|
return true;
|
|
}
|
|
|
|
// Validar IPv6 comprimido (com ::)
|
|
const ipv6CompressedRegex = /^([0-9a-fA-F]{0,4}:)*::([0-9a-fA-F]{0,4}:)*[0-9a-fA-F]{0,4}$/;
|
|
if (ipv6CompressedRegex.test(ip)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|