prepare("INSERT INTO user_sessions (user_id, ip_address, country, user_agent) VALUES (?, ?, ?, ?)"); $stmt->execute([$userId, $ip, $country, $userAgent]); // Update user $stmt = db()->prepare("UPDATE users SET last_login_at = NOW(), last_login_ip = ? WHERE id = ?"); $stmt->execute([$ip, $userId]); } catch (\Throwable $e) { // Log error but don't prevent login error_log("Auth::login tracking error: " . $e->getMessage()); } } public static function logout(): void { if (session_status() === PHP_SESSION_NONE) { session_start(); } $_SESSION = []; session_destroy(); if (isset($_COOKIE[session_name()])) { setcookie(session_name(), '', time() - 42000, '/'); } header('Location: login.php', true, 302); exit; } public static function getIpAddress(): string { if (!empty($_SERVER['HTTP_CLIENT_IP'])) { return $_SERVER['HTTP_CLIENT_IP']; } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { return explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]; } else { return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; } } public static function getCountryFromIp(string $ip): ?string { if ($ip === '127.0.0.1' || $ip === '::1') return 'Localhost'; try { $ctx = stream_context_create(['http' => ['timeout' => 2]]); $resp = @file_get_contents("http://ip-api.com/json/{$ip}?fields=country", false, $ctx); if ($resp) { $data = json_decode($resp, true); return $data['country'] ?? 'Unknown'; } } catch (\Throwable $e) { // Ignore errors for geolocation } return 'Unknown'; } public static function recordResetAttempt(string $email, string $ip): void { // We could log this to a separate table or activity_log $stmt = db()->prepare("INSERT INTO activity_log (tenant_id, action, details) VALUES (?, ?, ?)"); $stmt->execute([0, 'Password Reset Attempt', "Email: $email, IP: $ip"]); } }