feat: capture and log browser information during user login

- Integrated browser information capture in the login process, including user agent and IP address.
- Enhanced device and browser detection logic to provide more detailed insights into user environments.
- Improved system detection for various operating systems and devices, ensuring accurate reporting during authentication.
This commit is contained in:
2025-11-04 02:27:56 -03:00
parent 372b2b5bf9
commit f278ad4d17
3 changed files with 272 additions and 12 deletions

View File

@@ -10,6 +10,7 @@
import NotificationBell from "$lib/components/chat/NotificationBell.svelte";
import ChatWidget from "$lib/components/chat/ChatWidget.svelte";
import PresenceManager from "$lib/components/chat/PresenceManager.svelte";
import { getBrowserInfo } from "$lib/utils/browserInfo";
let { children }: { children: Snippet } = $props();
@@ -100,9 +101,14 @@
carregandoLogin = true;
try {
// Capturar informações do navegador
const browserInfo = await getBrowserInfo();
const resultado = await convex.mutation(api.autenticacao.login, {
matriculaOuEmail: matricula.trim(),
senha: senha,
userAgent: browserInfo.userAgent || undefined,
ipAddress: browserInfo.ipAddress,
});
if (resultado.sucesso) {

View File

@@ -0,0 +1,98 @@
/**
* Função utilitária para obter informações do navegador
* Sem usar APIs externas
*/
/**
* Obtém o User-Agent do navegador
*/
export function getUserAgent(): string {
if (typeof window === 'undefined' || !window.navigator) {
return '';
}
return window.navigator.userAgent || '';
}
/**
* Tenta obter o IP local usando WebRTC
* Esta função não usa API externa, mas pode falhar em alguns navegadores
* Retorna undefined se não conseguir obter
*/
export async function getLocalIP(): Promise<string | undefined> {
return new Promise((resolve) => {
// Verificar se está em ambiente browser
if (typeof window === 'undefined' || typeof RTCPeerConnection === 'undefined') {
resolve(undefined);
return;
}
try {
const pc = new RTCPeerConnection({
iceServers: []
});
let resolved = false;
const timeout = setTimeout(() => {
if (!resolved) {
resolved = true;
pc.close();
resolve(undefined);
}
}, 3000);
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);
}
}
}
}
};
// Criar um data channel para forçar a criação de candidatos
pc.createDataChannel('');
pc.createOffer()
.then((offer) => pc.setLocalDescription(offer))
.catch(() => {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
pc.close();
resolve(undefined);
}
});
} catch (error) {
resolve(undefined);
}
});
}
/**
* Obtém informações completas do navegador
*/
export interface BrowserInfo {
userAgent: string;
ipAddress?: string;
}
export async function getBrowserInfo(): Promise<BrowserInfo> {
const userAgent = getUserAgent();
const ipAddress = await getLocalIP();
return {
userAgent,
ipAddress,
};
}