166 lines
5.1 KiB
JavaScript
166 lines
5.1 KiB
JavaScript
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
|
|
}
|
|
};
|
|
}
|
|
|