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:
@@ -10,6 +10,7 @@
|
|||||||
import NotificationBell from "$lib/components/chat/NotificationBell.svelte";
|
import NotificationBell from "$lib/components/chat/NotificationBell.svelte";
|
||||||
import ChatWidget from "$lib/components/chat/ChatWidget.svelte";
|
import ChatWidget from "$lib/components/chat/ChatWidget.svelte";
|
||||||
import PresenceManager from "$lib/components/chat/PresenceManager.svelte";
|
import PresenceManager from "$lib/components/chat/PresenceManager.svelte";
|
||||||
|
import { getBrowserInfo } from "$lib/utils/browserInfo";
|
||||||
|
|
||||||
let { children }: { children: Snippet } = $props();
|
let { children }: { children: Snippet } = $props();
|
||||||
|
|
||||||
@@ -100,9 +101,14 @@
|
|||||||
carregandoLogin = true;
|
carregandoLogin = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Capturar informações do navegador
|
||||||
|
const browserInfo = await getBrowserInfo();
|
||||||
|
|
||||||
const resultado = await convex.mutation(api.autenticacao.login, {
|
const resultado = await convex.mutation(api.autenticacao.login, {
|
||||||
matriculaOuEmail: matricula.trim(),
|
matriculaOuEmail: matricula.trim(),
|
||||||
senha: senha,
|
senha: senha,
|
||||||
|
userAgent: browserInfo.userAgent || undefined,
|
||||||
|
ipAddress: browserInfo.ipAddress,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resultado.sucesso) {
|
if (resultado.sucesso) {
|
||||||
|
|||||||
98
apps/web/src/lib/utils/browserInfo.ts
Normal file
98
apps/web/src/lib/utils/browserInfo.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -37,26 +37,182 @@ export async function registrarLogin(
|
|||||||
|
|
||||||
// Helpers para extrair informações do userAgent
|
// Helpers para extrair informações do userAgent
|
||||||
function extrairDevice(userAgent: string): string {
|
function extrairDevice(userAgent: string): string {
|
||||||
if (/mobile/i.test(userAgent)) return "Mobile";
|
const ua = userAgent.toLowerCase();
|
||||||
if (/tablet/i.test(userAgent)) return "Tablet";
|
|
||||||
|
// Detectar dispositivos móveis primeiro
|
||||||
|
if (/mobile|android|iphone|ipod|blackberry|opera mini|iemobile|wpdesktop/i.test(ua)) {
|
||||||
|
// Verificar se é tablet
|
||||||
|
if (/ipad|tablet|playbook|silk|(android(?!.*mobile))/i.test(ua)) {
|
||||||
|
return "Tablet";
|
||||||
|
}
|
||||||
|
return "Mobile";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detectar outros dispositivos
|
||||||
|
if (/smart-tv|smarttv|googletv|appletv|roku|chromecast/i.test(ua)) {
|
||||||
|
return "Smart TV";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/watch|wear/i.test(ua)) {
|
||||||
|
return "Smart Watch";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Padrão: Desktop
|
||||||
return "Desktop";
|
return "Desktop";
|
||||||
}
|
}
|
||||||
|
|
||||||
function extrairBrowser(userAgent: string): string {
|
function extrairBrowser(userAgent: string): string {
|
||||||
if (/edg/i.test(userAgent)) return "Edge";
|
const ua = userAgent.toLowerCase();
|
||||||
if (/chrome/i.test(userAgent)) return "Chrome";
|
|
||||||
if (/firefox/i.test(userAgent)) return "Firefox";
|
// Ordem de detecção é importante (Edge deve vir antes de Chrome)
|
||||||
if (/safari/i.test(userAgent)) return "Safari";
|
if (/edgios/i.test(ua)) {
|
||||||
if (/opera/i.test(userAgent)) return "Opera";
|
return "Edge iOS";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/edg/i.test(ua)) {
|
||||||
|
// Extrair versão do Edge
|
||||||
|
const match = ua.match(/edg[e\/]([\d.]+)/i);
|
||||||
|
return match ? `Edge ${match[1]}` : "Edge";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/opr|opera/i.test(ua)) {
|
||||||
|
const match = ua.match(/(?:opr|opera)[\/\s]([\d.]+)/i);
|
||||||
|
return match ? `Opera ${match[1]}` : "Opera";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/chrome|crios/i.test(ua) && !/edg|opr|opera/i.test(ua)) {
|
||||||
|
const match = ua.match(/chrome[/\s]([\d.]+)/i);
|
||||||
|
return match ? `Chrome ${match[1]}` : "Chrome";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/firefox|fxios/i.test(ua)) {
|
||||||
|
const match = ua.match(/firefox[/\s]([\d.]+)/i);
|
||||||
|
return match ? `Firefox ${match[1]}` : "Firefox";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/safari/i.test(ua) && !/chrome|crios|android/i.test(ua)) {
|
||||||
|
const match = ua.match(/version[/\s]([\d.]+)/i);
|
||||||
|
return match ? `Safari ${match[1]}` : "Safari";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/msie|trident/i.test(ua)) {
|
||||||
|
const match = ua.match(/(?:msie |rv:)([\d.]+)/i);
|
||||||
|
return match ? `Internet Explorer ${match[1]}` : "Internet Explorer";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/samsungbrowser/i.test(ua)) {
|
||||||
|
return "Samsung Internet";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/ucbrowser/i.test(ua)) {
|
||||||
|
return "UC Browser";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/micromessenger/i.test(ua)) {
|
||||||
|
return "WeChat";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/baiduboxapp/i.test(ua)) {
|
||||||
|
return "Baidu Browser";
|
||||||
|
}
|
||||||
|
|
||||||
return "Desconhecido";
|
return "Desconhecido";
|
||||||
}
|
}
|
||||||
|
|
||||||
function extrairSistema(userAgent: string): string {
|
function extrairSistema(userAgent: string): string {
|
||||||
if (/windows/i.test(userAgent)) return "Windows";
|
const ua = userAgent.toLowerCase();
|
||||||
if (/mac/i.test(userAgent)) return "MacOS";
|
|
||||||
if (/linux/i.test(userAgent)) return "Linux";
|
// Windows
|
||||||
if (/android/i.test(userAgent)) return "Android";
|
if (/windows nt 10.0/i.test(ua)) {
|
||||||
if (/ios/i.test(userAgent)) return "iOS";
|
return "Windows 10/11";
|
||||||
|
}
|
||||||
|
if (/windows nt 6.3/i.test(ua)) {
|
||||||
|
return "Windows 8.1";
|
||||||
|
}
|
||||||
|
if (/windows nt 6.2/i.test(ua)) {
|
||||||
|
return "Windows 8";
|
||||||
|
}
|
||||||
|
if (/windows nt 6.1/i.test(ua)) {
|
||||||
|
return "Windows 7";
|
||||||
|
}
|
||||||
|
if (/windows nt 6.0/i.test(ua)) {
|
||||||
|
return "Windows Vista";
|
||||||
|
}
|
||||||
|
if (/windows nt 5.1/i.test(ua)) {
|
||||||
|
return "Windows XP";
|
||||||
|
}
|
||||||
|
if (/windows/i.test(ua)) {
|
||||||
|
return "Windows";
|
||||||
|
}
|
||||||
|
|
||||||
|
// macOS
|
||||||
|
if (/macintosh|mac os x/i.test(ua)) {
|
||||||
|
const match = ua.match(/mac os x ([\d_]+)/i);
|
||||||
|
if (match) {
|
||||||
|
const version = match[1].replace(/_/g, '.');
|
||||||
|
return `macOS ${version}`;
|
||||||
|
}
|
||||||
|
return "macOS";
|
||||||
|
}
|
||||||
|
|
||||||
|
// iOS
|
||||||
|
if (/iphone|ipad|ipod/i.test(ua)) {
|
||||||
|
const match = ua.match(/os ([\d_]+)/i);
|
||||||
|
if (match) {
|
||||||
|
const version = match[1].replace(/_/g, '.');
|
||||||
|
return `iOS ${version}`;
|
||||||
|
}
|
||||||
|
return "iOS";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Android
|
||||||
|
if (/android/i.test(ua)) {
|
||||||
|
const match = ua.match(/android ([\d.]+)/i);
|
||||||
|
if (match) {
|
||||||
|
return `Android ${match[1]}`;
|
||||||
|
}
|
||||||
|
return "Android";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linux
|
||||||
|
if (/linux/i.test(ua)) {
|
||||||
|
// Tentar identificar distribuição
|
||||||
|
if (/ubuntu/i.test(ua)) {
|
||||||
|
return "Ubuntu";
|
||||||
|
}
|
||||||
|
if (/debian/i.test(ua)) {
|
||||||
|
return "Debian";
|
||||||
|
}
|
||||||
|
if (/fedora/i.test(ua)) {
|
||||||
|
return "Fedora";
|
||||||
|
}
|
||||||
|
if (/centos/i.test(ua)) {
|
||||||
|
return "CentOS";
|
||||||
|
}
|
||||||
|
if (/redhat/i.test(ua)) {
|
||||||
|
return "Red Hat";
|
||||||
|
}
|
||||||
|
if (/suse/i.test(ua)) {
|
||||||
|
return "SUSE";
|
||||||
|
}
|
||||||
|
return "Linux";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chrome OS
|
||||||
|
if (/cros/i.test(ua)) {
|
||||||
|
return "Chrome OS";
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlackBerry
|
||||||
|
if (/blackberry/i.test(ua)) {
|
||||||
|
return "BlackBerry OS";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows Phone
|
||||||
|
if (/windows phone/i.test(ua)) {
|
||||||
|
return "Windows Phone";
|
||||||
|
}
|
||||||
|
|
||||||
return "Desconhecido";
|
return "Desconhecido";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user