feat: improve login process by integrating GPS location tracking and optimizing IP address handling, enhancing user data accuracy and experience

This commit is contained in:
2025-11-30 15:32:21 -03:00
parent f1c2ae0e6b
commit 3204440a38
5 changed files with 239 additions and 103 deletions

View File

@@ -13,20 +13,7 @@
import { Menu, User, Home, UserPlus, XCircle, LogIn, Tag, Plus, Check } from 'lucide-svelte';
import { authClient } from '$lib/auth';
import { resolve } from '$app/paths';
// Função para obter IP público (similar ao sistema de ponto)
async function obterIPPublico(): Promise<string | undefined> {
try {
const response = await fetch('https://api.ipify.org?format=json');
if (response.ok) {
const data = (await response.json()) as { ip: string };
return data.ip;
}
} catch (error) {
console.warn('Erro ao obter IP público:', error);
}
return undefined;
}
import { obterIPPublico } from '$lib/utils/deviceInfo';
let { children }: { children: Snippet } = $props();
@@ -137,11 +124,34 @@
erroLogin = '';
carregandoLogin = true;
// Obter IP público e userAgent antes do login
const [ipPublico, userAgent] = await Promise.all([
obterIPPublico().catch(() => undefined),
Promise.resolve(typeof navigator !== 'undefined' ? navigator.userAgent : undefined)
]);
// Obter IP público e userAgent (rápido, não bloqueia)
const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : undefined;
// Obter IP público com timeout curto (não bloquear login)
const ipPublicoPromise = obterIPPublico().catch(() => undefined);
const ipPublicoTimeout = new Promise<undefined>((resolve) =>
setTimeout(() => resolve(undefined), 2000) // Timeout de 2 segundos
);
const ipPublico = await Promise.race([ipPublicoPromise, ipPublicoTimeout]);
// Função para coletar GPS em background (não bloqueia login)
async function coletarGPS(): Promise<any> {
try {
const { obterLocalizacaoRapida } = await import('$lib/utils/deviceInfo');
// Usar versão rápida com timeout curto (3 segundos máximo)
const gpsPromise = obterLocalizacaoRapida();
const gpsTimeout = new Promise<{}>((resolve) =>
setTimeout(() => resolve({}), 3000)
);
return await Promise.race([gpsPromise, gpsTimeout]);
} catch (err) {
console.warn('Erro ao obter GPS (não bloqueia login):', err);
return {};
}
}
// Iniciar coleta de GPS em background (não esperar)
const gpsPromise = coletarGPS();
const result = await authClient.signIn.email(
{ email: matricula.trim(), password: senha },
@@ -149,12 +159,30 @@
onError: async (ctx) => {
// Registrar tentativa de login falha
try {
// Tentar obter GPS se já estiver disponível (não esperar)
let localizacaoGPS: any = {};
try {
localizacaoGPS = await Promise.race([
gpsPromise,
new Promise<{}>((resolve) => setTimeout(() => resolve({}), 100))
]);
} catch {
// Ignorar se GPS não estiver pronto
}
await convexClient.mutation(api.logsLogin.registrarTentativaLogin, {
matriculaOuEmail: matricula.trim(),
sucesso: false,
motivoFalha: ctx.error?.message || 'Erro desconhecido',
userAgent: userAgent,
ipAddress: ipPublico,
latitudeGPS: localizacaoGPS.latitude,
longitudeGPS: localizacaoGPS.longitude,
precisaoGPS: localizacaoGPS.precisao,
enderecoGPS: localizacaoGPS.endereco,
cidadeGPS: localizacaoGPS.cidade,
estadoGPS: localizacaoGPS.estado,
paisGPS: localizacaoGPS.pais,
});
} catch (err) {
console.error('Erro ao registrar tentativa de login falha:', err);
@@ -172,6 +200,17 @@
// Aguardar um pouco para o usuário ser sincronizado no Convex
await new Promise((resolve) => setTimeout(resolve, 500));
// Tentar obter GPS se já estiver disponível (não esperar)
let localizacaoGPS: any = {};
try {
localizacaoGPS = await Promise.race([
gpsPromise,
new Promise<{}>((resolve) => setTimeout(() => resolve({}), 100))
]);
} catch {
// Ignorar se GPS não estiver pronto
}
// Buscar o usuário no Convex usando getCurrentUser
const usuario = await convexClient.query(api.auth.getCurrentUser, {});
@@ -182,6 +221,13 @@
sucesso: true,
userAgent: userAgent,
ipAddress: ipPublico,
latitudeGPS: localizacaoGPS.latitude,
longitudeGPS: localizacaoGPS.longitude,
precisaoGPS: localizacaoGPS.precisao,
enderecoGPS: localizacaoGPS.endereco,
cidadeGPS: localizacaoGPS.cidade,
estadoGPS: localizacaoGPS.estado,
paisGPS: localizacaoGPS.pais,
});
} else {
// Se não encontrou o usuário, registrar sem usuarioId (será atualizado depois)
@@ -190,6 +236,13 @@
sucesso: true,
userAgent: userAgent,
ipAddress: ipPublico,
latitudeGPS: localizacaoGPS.latitude,
longitudeGPS: localizacaoGPS.longitude,
precisaoGPS: localizacaoGPS.precisao,
enderecoGPS: localizacaoGPS.endereco,
cidadeGPS: localizacaoGPS.cidade,
estadoGPS: localizacaoGPS.estado,
paisGPS: localizacaoGPS.pais,
});
}
} catch (err) {

View File

@@ -526,10 +526,97 @@ async function obterLocalizacaoMultipla(): Promise<{
};
}
/**
* Obtém localização via GPS de forma rápida (uma única leitura, sem reverse geocoding)
* Usado para login - não bloqueia o fluxo
*/
export async function obterLocalizacaoRapida(): Promise<{
latitude?: number;
longitude?: number;
precisao?: number;
endereco?: string;
cidade?: string;
estado?: string;
pais?: string;
}> {
if (typeof navigator === 'undefined' || !navigator.geolocation) {
return {};
}
try {
// Uma única leitura rápida com timeout curto
const leitura = await capturarLocalizacaoUnica(true, 3000); // 3 segundos máximo
if (!leitura.latitude || !leitura.longitude || leitura.confiabilidade === 0) {
return {};
}
// Tentar obter endereço via reverse geocoding (com timeout curto)
let endereco = '';
let cidade = '';
let estado = '';
let pais = '';
try {
const geocodePromise = fetch(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${leitura.latitude}&lon=${leitura.longitude}&zoom=18&addressdetails=1`,
{
headers: {
'User-Agent': 'SGSE-App/1.0'
}
}
);
const geocodeTimeout = new Promise<Response>((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 2000)
);
const response = await Promise.race([geocodePromise, geocodeTimeout]);
if (response.ok) {
const data = (await response.json()) as {
address?: {
road?: string;
house_number?: string;
city?: string;
town?: string;
state?: string;
country?: string;
};
};
if (data.address) {
const addr = data.address;
if (addr.road) {
endereco = `${addr.road}${addr.house_number ? `, ${addr.house_number}` : ''}`;
}
cidade = addr.city || addr.town || '';
estado = addr.state || '';
pais = addr.country || '';
}
}
} catch (error) {
// Ignorar erro de geocoding - não é crítico
console.warn('Erro ao obter endereço (não crítico):', error);
}
return {
latitude: leitura.latitude,
longitude: leitura.longitude,
precisao: leitura.precisao,
endereco,
cidade,
estado,
pais
};
} catch (error) {
console.warn('Erro ao obter localização rápida:', error);
return {};
}
}
/**
* Obtém localização via GPS com múltiplas tentativas e validações anti-spoofing
*/
async function obterLocalizacao(): Promise<{
export async function obterLocalizacao(): Promise<{
latitude?: number;
longitude?: number;
precisao?: number;
@@ -644,7 +731,7 @@ async function obterLocalizacao(): Promise<{
/**
* Obtém IP público
*/
async function obterIPPublico(): Promise<string | undefined> {
export async function obterIPPublico(): Promise<string | undefined> {
try {
const response = await fetch('https://api.ipify.org?format=json');
if (response.ok) {