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

@@ -2,61 +2,6 @@ import { v } from "convex/values";
import { mutation, query, QueryCtx, MutationCtx } from "./_generated/server";
import { Doc, Id } from "./_generated/dataModel";
/**
* Obtém geolocalização aproximada por IP usando serviço externo
* Similar ao sistema de ponto
*/
async function obterGeoPorIP(ipAddress: string): Promise<{
latitude: number;
longitude: number;
cidade?: string;
estado?: string;
pais?: string;
endereco?: string;
} | null> {
try {
// Usar ipapi.co (gratuito, sem chave para uso limitado)
const response = await fetch(`https://ipapi.co/${ipAddress}/json/`, {
headers: {
'User-Agent': 'SGSE-App/1.0'
}
});
if (response.ok) {
const data = (await response.json()) as {
latitude?: number;
longitude?: number;
city?: string;
region?: string;
country_name?: string;
error?: boolean;
};
if (!data.error && data.latitude && data.longitude) {
// Montar endereço completo
const partesEndereco: string[] = [];
if (data.city) partesEndereco.push(data.city);
if (data.region) partesEndereco.push(data.region);
if (data.country_name) partesEndereco.push(data.country_name);
const endereco = partesEndereco.length > 0 ? partesEndereco.join(', ') : undefined;
return {
latitude: data.latitude,
longitude: data.longitude,
cidade: data.city,
estado: data.region,
pais: data.country_name,
endereco
};
}
}
} catch (error) {
console.warn('Erro ao obter geolocalização por IP:', error);
}
return null;
}
/**
* Helper para registrar tentativas de login
*/
@@ -98,6 +43,13 @@ export async function registrarLogin(
motivoFalha?: string;
ipAddress?: string;
userAgent?: string;
latitudeGPS?: number;
longitudeGPS?: number;
precisaoGPS?: number;
enderecoGPS?: string;
cidadeGPS?: string;
estadoGPS?: string;
paisGPS?: string;
}
) {
// Extrair informações do userAgent
@@ -108,25 +60,8 @@ export async function registrarLogin(
// Validar e sanitizar IP antes de salvar
const ipAddressValidado = validarIP(dados.ipAddress);
// Obter geolocalização por IP se disponível (de forma assíncrona para não bloquear)
let geolocalizacao: {
latitude?: number;
longitude?: number;
cidade?: string;
estado?: string;
pais?: string;
endereco?: string;
} | null = null;
if (ipAddressValidado) {
// Obter geolocalização por IP (não bloquear se falhar)
try {
geolocalizacao = await obterGeoPorIP(ipAddressValidado);
} catch (error) {
console.warn('Erro ao obter geolocalização por IP:', error);
// Continuar sem localização se houver erro
}
}
// Nota: Geolocalização por IP removida porque fetch() não pode ser usado em mutations do Convex
// A localização GPS já é coletada no frontend e enviada diretamente
await ctx.db.insert("logsLogin", {
usuarioId: dados.usuarioId,
@@ -138,13 +73,21 @@ export async function registrarLogin(
device,
browser,
sistema,
// Informações de Localização
latitude: geolocalizacao?.latitude,
longitude: geolocalizacao?.longitude,
cidade: geolocalizacao?.cidade,
estado: geolocalizacao?.estado,
pais: geolocalizacao?.pais,
endereco: geolocalizacao?.endereco,
// Informações de Localização por IP (removido - usar GPS do frontend)
latitude: undefined,
longitude: undefined,
cidade: undefined,
estado: undefined,
pais: undefined,
endereco: undefined,
// Informações de Localização (GPS do navegador)
latitudeGPS: dados.latitudeGPS,
longitudeGPS: dados.longitudeGPS,
precisaoGPS: dados.precisaoGPS,
enderecoGPS: dados.enderecoGPS,
cidadeGPS: dados.cidadeGPS,
estadoGPS: dados.estadoGPS,
paisGPS: dados.paisGPS,
timestamp: Date.now(),
});
@@ -374,6 +317,13 @@ export const registrarTentativaLogin = mutation({
motivoFalha: v.optional(v.string()),
ipAddress: v.optional(v.string()),
userAgent: v.optional(v.string()),
latitudeGPS: v.optional(v.number()),
longitudeGPS: v.optional(v.number()),
precisaoGPS: v.optional(v.number()),
enderecoGPS: v.optional(v.string()),
cidadeGPS: v.optional(v.string()),
estadoGPS: v.optional(v.string()),
paisGPS: v.optional(v.string()),
},
handler: async (ctx, args) => {
await registrarLogin(ctx, {
@@ -383,6 +333,13 @@ export const registrarTentativaLogin = mutation({
motivoFalha: args.motivoFalha,
ipAddress: args.ipAddress,
userAgent: args.userAgent,
latitudeGPS: args.latitudeGPS,
longitudeGPS: args.longitudeGPS,
precisaoGPS: args.precisaoGPS,
enderecoGPS: args.enderecoGPS,
cidadeGPS: args.cidadeGPS,
estadoGPS: args.estadoGPS,
paisGPS: args.paisGPS,
});
return { success: true };
},

View File

@@ -787,13 +787,21 @@ export default defineSchema({
device: v.optional(v.string()),
browser: v.optional(v.string()),
sistema: v.optional(v.string()),
// Informações de Localização
// Informações de Localização (por IP)
latitude: v.optional(v.number()),
longitude: v.optional(v.number()),
endereco: v.optional(v.string()),
cidade: v.optional(v.string()),
estado: v.optional(v.string()),
pais: v.optional(v.string()),
// Informações de Localização (GPS do navegador)
latitudeGPS: v.optional(v.number()),
longitudeGPS: v.optional(v.number()),
precisaoGPS: v.optional(v.number()),
enderecoGPS: v.optional(v.string()),
cidadeGPS: v.optional(v.string()),
estadoGPS: v.optional(v.string()),
paisGPS: v.optional(v.string()),
timestamp: v.number(),
})
.index("by_usuario", ["usuarioId"])