import crypto from 'crypto'; /** * Genera un fingerprint del dispositivo basado en headers HTTP * @param {Object} req - Request object de Express * @returns {Object} Objeto con fingerprint hash y metadata del dispositivo */ export function generateDeviceFingerprint(req) { // Extraer información del dispositivo desde headers const userAgent = req.headers['user-agent'] || ''; const acceptLanguage = req.headers['accept-language'] || ''; const acceptEncoding = req.headers['accept-encoding'] || ''; const accept = req.headers['accept'] || ''; const connection = req.headers['connection'] || ''; const upgradeInsecureRequests = req.headers['upgrade-insecure-requests'] || ''; // IP del cliente (considerando proxies) const ip = req.ip || req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.headers['x-real-ip'] || req.connection?.remoteAddress || 'unknown'; // Crear string combinado para el hash const fingerprintString = [ userAgent, acceptLanguage, acceptEncoding, accept, connection, upgradeInsecureRequests, ip ].join('|'); // Generar hash SHA-256 const fingerprintHash = crypto .createHash('sha256') .update(fingerprintString) .digest('hex'); // Extraer información legible del User-Agent const deviceInfo = parseUserAgent(userAgent); return { fingerprint: fingerprintHash, deviceInfo: { ...deviceInfo, ip: ip, userAgent: userAgent.substring(0, 200), // Limitar longitud } }; } /** * Parsea el User-Agent para extraer información legible * @param {string} userAgent - User-Agent string * @returns {Object} Información del dispositivo */ function parseUserAgent(userAgent) { if (!userAgent) { return { browser: 'Unknown', browserVersion: '', os: 'Unknown', osVersion: '', device: 'Unknown', }; } const ua = userAgent.toLowerCase(); // Detectar navegador let browser = 'Unknown'; let browserVersion = ''; if (ua.includes('chrome') && !ua.includes('edg') && !ua.includes('opr')) { browser = 'Chrome'; const match = ua.match(/chrome\/([\d.]+)/); browserVersion = match ? match[1] : ''; } else if (ua.includes('firefox')) { browser = 'Firefox'; const match = ua.match(/firefox\/([\d.]+)/); browserVersion = match ? match[1] : ''; } else if (ua.includes('safari') && !ua.includes('chrome')) { browser = 'Safari'; const match = ua.match(/version\/([\d.]+)/); browserVersion = match ? match[1] : ''; } else if (ua.includes('edg')) { browser = 'Edge'; const match = ua.match(/edg\/([\d.]+)/); browserVersion = match ? match[1] : ''; } else if (ua.includes('opr')) { browser = 'Opera'; const match = ua.match(/opr\/([\d.]+)/); browserVersion = match ? match[1] : ''; } // Detectar sistema operativo let os = 'Unknown'; let osVersion = ''; if (ua.includes('windows')) { os = 'Windows'; if (ua.includes('windows nt 10')) osVersion = '10/11'; else if (ua.includes('windows nt 6.3')) osVersion = '8.1'; else if (ua.includes('windows nt 6.2')) osVersion = '8'; else if (ua.includes('windows nt 6.1')) osVersion = '7'; } else if (ua.includes('mac os x') || ua.includes('macintosh')) { os = 'macOS'; const match = ua.match(/mac os x ([\d_]+)/); osVersion = match ? match[1].replace(/_/g, '.') : ''; } else if (ua.includes('linux')) { os = 'Linux'; } else if (ua.includes('android')) { os = 'Android'; const match = ua.match(/android ([\d.]+)/); osVersion = match ? match[1] : ''; } else if (ua.includes('ios') || ua.includes('iphone') || ua.includes('ipad')) { os = 'iOS'; const match = ua.match(/os ([\d_]+)/); osVersion = match ? match[1].replace(/_/g, '.') : ''; } // Detectar tipo de dispositivo let device = 'Desktop'; if (ua.includes('mobile') || ua.includes('android') || ua.includes('iphone')) { device = 'Mobile'; } else if (ua.includes('tablet') || ua.includes('ipad')) { device = 'Tablet'; } return { browser, browserVersion, os, osVersion, device, }; } /** * Genera un fingerprint desde el dashboard (cuando se envía desde el cliente) * @param {string} fingerprintHash - Hash del fingerprint generado en el cliente * @param {Object} deviceInfo - Información del dispositivo del cliente * @param {Object} req - Request object de Express * @returns {Object} Objeto con fingerprint y metadata combinada */ export function combineFingerprint(fingerprintHash, deviceInfo, req) { const serverFingerprint = generateDeviceFingerprint(req); // Si el cliente envió un fingerprint, combinarlo con el del servidor // Esto permite usar librerías avanzadas del cliente pero validar con servidor const combinedFingerprint = fingerprintHash ? crypto.createHash('sha256').update(fingerprintHash + serverFingerprint.fingerprint).digest('hex') : serverFingerprint.fingerprint; return { fingerprint: combinedFingerprint, deviceInfo: { ...serverFingerprint.deviceInfo, ...deviceInfo, // El del cliente tiene prioridad si existe } }; }