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.
This commit is contained in:
2025-11-04 03:26:34 -03:00
parent f278ad4d17
commit c6c88f85a7
11 changed files with 3531 additions and 70 deletions

View File

@@ -14,7 +14,64 @@ export function getUserAgent(): string {
}
/**
* Tenta obter o IP local usando WebRTC
* Valida se uma string tem formato de IP válido
*/
function isValidIPFormat(ip: string): boolean {
if (!ip || ip.length < 7) return false; // IP mínimo: "1.1.1.1" = 7 chars
// Validar IPv4
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (ipv4Regex.test(ip)) {
const parts = ip.split('.');
return parts.length === 4 && parts.every(part => {
const num = parseInt(part, 10);
return !isNaN(num) && num >= 0 && num <= 255;
});
}
// Validar IPv6 básico (formato simplificado)
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$|^::[0-9a-fA-F]{0,4}(:[0-9a-fA-F]{0,4}){0,6}$|^[0-9a-fA-F]{0,4}::[0-9a-fA-F]{0,4}(:[0-9a-fA-F]{0,4}){0,5}$/;
if (ipv6Regex.test(ip)) {
return true;
}
return false;
}
/**
* Verifica se um IP é local/privado
*/
function isLocalIP(ip: string): boolean {
// IPs locais/privados
return (
ip.startsWith('127.') ||
ip.startsWith('192.168.') ||
ip.startsWith('10.') ||
ip.startsWith('172.16.') ||
ip.startsWith('172.17.') ||
ip.startsWith('172.18.') ||
ip.startsWith('172.19.') ||
ip.startsWith('172.20.') ||
ip.startsWith('172.21.') ||
ip.startsWith('172.22.') ||
ip.startsWith('172.23.') ||
ip.startsWith('172.24.') ||
ip.startsWith('172.25.') ||
ip.startsWith('172.26.') ||
ip.startsWith('172.27.') ||
ip.startsWith('172.28.') ||
ip.startsWith('172.29.') ||
ip.startsWith('172.30.') ||
ip.startsWith('172.31.') ||
ip.startsWith('169.254.') || // Link-local
ip === '::1' ||
ip.startsWith('fe80:') // IPv6 link-local
);
}
/**
* Tenta obter o IP usando WebRTC
* Prioriza IP público, mas retorna IP local se não encontrar
* Esta função não usa API externa, mas pode falhar em alguns navegadores
* Retorna undefined se não conseguir obter
*/
@@ -32,31 +89,87 @@ export async function getLocalIP(): Promise<string | undefined> {
});
let resolved = false;
let foundIPs: string[] = [];
let publicIP: string | undefined = undefined;
let localIP: string | undefined = undefined;
const timeout = setTimeout(() => {
if (!resolved) {
resolved = true;
pc.close();
resolve(undefined);
// Priorizar IP público, mas retornar local se não houver
resolve(publicIP || localIP || undefined);
}
}, 3000);
}, 5000); // Aumentar timeout para 5 segundos
pc.onicecandidate = (event) => {
if (event.candidate && !resolved) {
const candidate = event.candidate.candidate;
// Regex para extrair IP local (IPv4)
const ipMatch = candidate.match(/([0-9]{1,3}(\.[0-9]{1,3}){3})/);
if (ipMatch && ipMatch[1]) {
const ip = ipMatch[1];
// Verificar se não é IP localhost (127.0.0.1 ou ::1)
if (!ip.startsWith('127.') && !ip.startsWith('::1')) {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
pc.close();
resolve(ip);
// Regex mais rigorosa para IPv4 - deve ser um IP completo e válido
// Formato: X.X.X.X onde X é 0-255
const ipv4Match = candidate.match(/\b([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\b/);
// Regex para IPv6 - mais específica
const ipv6Match = candidate.match(/\b([0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){2,7}|::[0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){0,6}|[0-9a-fA-F]{1,4}::[0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){0,5})\b/);
let ip: string | undefined = undefined;
if (ipv4Match && ipv4Match[1]) {
const candidateIP = ipv4Match[1];
// Validar se cada octeto está entre 0-255
const parts = candidateIP.split('.');
if (parts.length === 4 && parts.every(part => {
const num = parseInt(part, 10);
return !isNaN(num) && num >= 0 && num <= 255;
})) {
ip = candidateIP;
}
} else if (ipv6Match && ipv6Match[1]) {
// Validar formato básico de IPv6
const candidateIP = ipv6Match[1];
if (candidateIP.includes(':') && candidateIP.length >= 3) {
ip = candidateIP;
}
}
// Validar se o IP é válido antes de processar
if (ip && isValidIPFormat(ip) && !foundIPs.includes(ip)) {
foundIPs.push(ip);
// Ignorar localhost
if (ip.startsWith('127.') || ip === '::1') {
return;
}
// Separar IPs públicos e locais
if (isLocalIP(ip)) {
if (!localIP) {
localIP = ip;
}
} else {
// IP público encontrado!
if (!publicIP) {
publicIP = ip;
// Se encontrou IP público, podemos resolver mais cedo
if (!resolved) {
resolved = true;
clearTimeout(timeout);
pc.close();
resolve(publicIP);
}
}
}
}
} else if (event.candidate === null) {
// No more candidates
if (!resolved) {
resolved = true;
clearTimeout(timeout);
pc.close();
// Retornar IP público se encontrou, senão local
resolve(publicIP || localIP || undefined);
}
}
};
@@ -69,10 +182,11 @@ export async function getLocalIP(): Promise<string | undefined> {
resolved = true;
clearTimeout(timeout);
pc.close();
resolve(undefined);
resolve(publicIP || localIP || undefined);
}
});
} catch (error) {
console.warn("Erro ao obter IP via WebRTC:", error);
resolve(undefined);
}
});