← back to Wine Finder Next

lib/advancedSecurity.ts.backup

357 lines

// Advanced Security Features - Enhanced Protection Layer
import { logSecurityEvent, blockIP } from './securityMonitoring';

interface RequestFingerprint {
  userAgent: string;
  acceptLanguage: string;
  acceptEncoding: string;
  connection: string;
  deviceType: string;
  browserFingerprint: string;
}

interface GeoLocation {
  country: string;
  region: string;
  city: string;
  isHighRisk: boolean;
}

// High-risk countries for fraud (based on common fraud patterns)
const HIGH_RISK_COUNTRIES = [
  'CN', 'RU', 'NG', 'PK', 'VN', 'ID', 'IN', // Known for high bot/fraud activity
  'UA', 'RO', 'BR', 'TR', 'MY', 'TH', 'PH'  // Additional risk regions
];

// Known malicious IP ranges (examples - expand in production)
const BLOCKED_IP_RANGES = [
  { start: '0.0.0.0', end: '0.255.255.255' },      // Reserved
  { start: '127.0.0.0', end: '127.255.255.255' },  // Loopback
  { start: '10.0.0.0', end: '10.255.255.255' },    // Private
];

// Bot user agent patterns
const BOT_USER_AGENTS = [
  /bot|crawler|spider|scraper|curl|wget|python|java|golang/i,
  /headless|phantom|selenium|puppeteer|playwright/i,
  /axios|fetch|http|request|postman/i,
  /scanner|vulnerability|nikto|nmap|sqlmap/i,
  /masscan|shodan|censys|zgrab/i
];

// Suspicious header patterns
const SUSPICIOUS_HEADERS = {
  missingUserAgent: (headers: any) => !headers['user-agent'],
  invalidAccept: (headers: any) => !headers['accept'] || headers['accept'] === '*/*',
  noReferer: (headers: any) => !headers['referer'] && !headers['origin'],
  suspiciousUserAgent: (headers: any) => {
    const ua = headers['user-agent'] || '';
    return BOT_USER_AGENTS.some(pattern => pattern.test(ua));
  }
};

// Request rate tracking per fingerprint
const fingerprintRequests = new Map<string, { count: number; firstSeen: Date; lastSeen: Date }>();

// Detect bot traffic
export function detectBot(userAgent: string): boolean {
  if (!userAgent) return true; // No user agent = likely bot

  // Check against known bot patterns
  return BOT_USER_AGENTS.some(pattern => pattern.test(userAgent));
}

// Generate request fingerprint
export function generateRequestFingerprint(headers: any): string {
  const components = [
    headers['user-agent'] || 'none',
    headers['accept-language'] || 'none',
    headers['accept-encoding'] || 'none',
    headers['accept'] || 'none',
    headers['connection'] || 'none'
  ];

  // Simple hash of combined headers
  const fingerprint = components.join('|');
  let hash = 0;
  for (let i = 0; i < fingerprint.length; i++) {
    const char = fingerprint.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;
    hash = hash & hash;
  }

  return hash.toString(16);
}

// Detect suspicious request patterns
export function detectSuspiciousRequest(headers: any, ip: string): {
  suspicious: boolean;
  reasons: string[];
  riskScore: number;
} {
  const reasons: string[] = [];
  let riskScore = 0;

  // Check for missing/suspicious headers
  if (SUSPICIOUS_HEADERS.missingUserAgent(headers)) {
    reasons.push('MISSING_USER_AGENT');
    riskScore += 30;
  }

  if (SUSPICIOUS_HEADERS.invalidAccept(headers)) {
    reasons.push('INVALID_ACCEPT_HEADER');
    riskScore += 20;
  }

  if (SUSPICIOUS_HEADERS.suspiciousUserAgent(headers)) {
    reasons.push('BOT_USER_AGENT');
    riskScore += 50;
  }

  // Check fingerprint frequency
  const fingerprint = generateRequestFingerprint(headers);
  const fpData = fingerprintRequests.get(fingerprint);

  if (fpData) {
    const timeDiff = Date.now() - fpData.firstSeen.getTime();
    const requestsPerMinute = (fpData.count / timeDiff) * 60000;

    if (requestsPerMinute > 100) {
      reasons.push('EXCESSIVE_REQUEST_RATE');
      riskScore += 40;
    }

    fpData.count++;
    fpData.lastSeen = new Date();
  } else {
    fingerprintRequests.set(fingerprint, {
      count: 1,
      firstSeen: new Date(),
      lastSeen: new Date()
    });
  }

  // Check for tor/proxy headers (but not regular reverse proxy headers)
  if (headers['x-tor-exit-node']) {
    reasons.push('TOR_DETECTED');
    riskScore += 25;
  }

  // Don't penalize normal proxy headers like x-forwarded-for (used by reverse proxies)

  return {
    suspicious: riskScore >= 50,
    reasons,
    riskScore
  };
}

// Simulate basic geolocation (in production, use MaxMind GeoIP2 or similar)
export function getGeoLocation(ip: string): GeoLocation {
  // This is a placeholder - in production use real GeoIP service
  // Allow localhost and private IPs
  if (ip === '::1' || ip === '127.0.0.1' || ip.startsWith('192.168.') || ip.startsWith('10.') ||
      ip.startsWith('::ffff:127.0.0.1') || ip.startsWith('::ffff:45.61.58.125') || ip === 'unknown') {
    return {
      country: 'US',
      region: 'California',
      city: 'San Francisco',
      isHighRisk: false
    };
  }

  // Default to low risk for safety (to avoid blocking legitimate traffic)
  // In production with real GeoIP, this should check against HIGH_RISK_COUNTRIES
  return {
    country: 'XX',
    region: 'Unknown',
    city: 'Unknown',
    isHighRisk: false // Changed to false to reduce false positives
  };
}

// Check if country is high risk
export function isHighRiskCountry(countryCode: string): boolean {
  return HIGH_RISK_COUNTRIES.includes(countryCode.toUpperCase());
}

// Advanced DDoS detection
export function detectDDoS(ip: string): {
  isDDoS: boolean;
  reason: string;
  blockDuration?: number;
} {
  // Track requests per IP
  const now = Date.now();
  const oneMinute = 60000;

  // Get fingerprint data for this IP
  const fingerprint = fingerprintRequests.get(ip);

  if (!fingerprint) {
    return { isDDoS: false, reason: 'NEW_IP' };
  }

  const timeDiff = now - fingerprint.firstSeen.getTime();
  const requestsPerSecond = (fingerprint.count / timeDiff) * 1000;

  // DDoS thresholds
  if (requestsPerSecond > 50) {
    return {
      isDDoS: true,
      reason: 'EXTREME_REQUEST_RATE',
      blockDuration: 3600000 // Block for 1 hour
    };
  }

  if (requestsPerSecond > 10 && fingerprint.count > 100) {
    return {
      isDDoS: true,
      reason: 'SUSTAINED_HIGH_RATE',
      blockDuration: 600000 // Block for 10 minutes
    };
  }

  return { isDDoS: false, reason: 'NORMAL_TRAFFIC' };
}

// Comprehensive security check for all requests
export function comprehensiveSecurityCheck(req: {
  ip: string;
  headers: any;
  method: string;
  path: string;
}): {
  allowed: boolean;
  reason: string;
  riskScore: number;
  actions: string[];
} {
  const actions: string[] = [];
  let totalRiskScore = 0;

  // 1. Check for bot traffic (reduced penalty for common tools like curl)
  const isBot = detectBot(req.headers['user-agent']);
  if (isBot) {
    totalRiskScore += 20; // Reduced from 40 to allow testing tools
    actions.push('BOT_DETECTED');
  }

  // 2. Check for suspicious patterns
  const suspicious = detectSuspiciousRequest(req.headers, req.ip);
  totalRiskScore += suspicious.riskScore;
  actions.push(...suspicious.reasons);

  // 3. Check geolocation
  const geo = getGeoLocation(req.ip);
  if (geo.isHighRisk) {
    totalRiskScore += 30;
    actions.push('HIGH_RISK_COUNTRY');
  }

  // 4. Check for DDoS
  const ddos = detectDDoS(req.ip);
  if (ddos.isDDoS) {
    totalRiskScore += 100; // Instant block
    actions.push('DDOS_DETECTED');

    // Auto-block IP
    blockIP(req.ip, `DDoS attack detected: ${ddos.reason}`);

    logSecurityEvent({
      type: 'SUSPICIOUS_ACTIVITY',
      severity: 'critical',
      ipAddress: req.ip,
      userAgent: req.headers['user-agent'] || 'unknown',
      details: {
        reason: ddos.reason,
        blockDuration: ddos.blockDuration,
        method: req.method,
        path: req.path
      }
    });
  }

  // 5. Check HTTP method
  if (['DELETE', 'PUT', 'TRACE', 'CONNECT'].includes(req.method)) {
    totalRiskScore += 20;
    actions.push('UNUSUAL_HTTP_METHOD');
  }

  // Decision - increased threshold to reduce false positives
  // Allow more traffic through, only block truly suspicious requests
  const allowed = totalRiskScore < 100;

  if (!allowed) {
    logSecurityEvent({
      type: 'SUSPICIOUS_ACTIVITY',
      severity: totalRiskScore > 150 ? 'critical' : 'high',
      ipAddress: req.ip,
      userAgent: req.headers['user-agent'] || 'unknown',
      details: {
        riskScore: totalRiskScore,
        actions,
        method: req.method,
        path: req.path
      }
    });
  }

  return {
    allowed,
    reason: actions.join(', '),
    riskScore: totalRiskScore,
    actions
  };
}

// Clean up old fingerprint data (call periodically)
export function cleanupFingerprintData(): void {
  const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000;

  for (const [fingerprint, data] of fingerprintRequests.entries()) {
    if (data.lastSeen.getTime() < oneDayAgo) {
      fingerprintRequests.delete(fingerprint);
    }
  }
}

// Run cleanup every hour
setInterval(cleanupFingerprintData, 60 * 60 * 1000);

// Security dashboard data
export function getAdvancedSecurityStats(): {
  totalFingerprints: number;
  activeBots: number;
  highRiskRequests: number;
  ddosAttempts: number;
  averageRiskScore: number;
} {
  let activeBots = 0;
  let highRiskRequests = 0;
  let totalRisk = 0;

  for (const [fingerprint, data] of fingerprintRequests.entries()) {
    // Simple heuristic: if more than 50 requests/minute, likely bot
    const timeDiff = Date.now() - data.firstSeen.getTime();
    const requestsPerMinute = (data.count / timeDiff) * 60000;

    if (requestsPerMinute > 50) {
      activeBots++;
    }

    if (requestsPerMinute > 10) {
      highRiskRequests++;
      totalRisk += 50;
    }
  }

  return {
    totalFingerprints: fingerprintRequests.size,
    activeBots,
    highRiskRequests,
    ddosAttempts: 0, // Would track from DDoS detection
    averageRiskScore: fingerprintRequests.size > 0 ? totalRisk / fingerprintRequests.size : 0
  };
}