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