Files
sgse-app/packages/backend/convex/utils/getClientIP.ts
deyvisonwanderley c6c88f85a7 feat: enhance login process with IP capture and improved error handling
- Implemented an internal mutation for login that captures the user's IP address and user agent for better security and tracking.
- Enhanced the HTTP login endpoint to extract and log client IP, improving the overall authentication process.
- Added validation for IP addresses to ensure only valid formats are recorded, enhancing data integrity.
- Updated the login mutation to handle rate limiting and user status checks more effectively, providing clearer feedback on login attempts.
2025-11-04 03:26:34 -03:00

152 lines
4.3 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;
}