$timeout) { self::logoutAdmin(); return false; } return true; } /** * Logout admin user */ public static function logoutAdmin() { unset($_SESSION['admin_authenticated']); unset($_SESSION['admin_username']); unset($_SESSION['admin_login_time']); session_regenerate_id(true); } /** * Hash password using bcrypt */ public static function hashPassword($password) { $options = [ 'cost' => 12, // Increase cost for production ]; return password_hash($password, PASSWORD_ARGON2I, $options); } /** * Verify password hash needs rehash */ public static function passwordNeedsRehash($hash) { $options = [ 'cost' => 12, ]; return password_needs_rehash($hash, PASSWORD_ARGON2I, $options); } /** * Generate secure random string */ public static function generateSecureToken($length = 32) { return bin2hex(random_bytes($length)); } /** * Sanitize input data */ public static function sanitizeInput($input, $type = 'string') { switch ($type) { case 'string': $sanitized = trim($input); $sanitized = filter_var($sanitized, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES); return htmlspecialchars($sanitized, ENT_QUOTES, 'UTF-8'); case 'email': $sanitized = filter_var(trim($input), FILTER_SANITIZE_EMAIL); return filter_var($sanitized, FILTER_VALIDATE_EMAIL) ? $sanitized : ''; case 'url': $sanitized = filter_var(trim($input), FILTER_SANITIZE_URL); return filter_var($sanitized, FILTER_VALIDATE_URL, FILTER_FLAG_QUERY_REQUIRED) ? $sanitized : ''; case 'int': return filter_var($input, FILTER_VALIDATE_INT); case 'float': return filter_var($input, FILTER_VALIDATE_FLOAT); default: return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8'); } } /** * Validate URL against whitelist */ public static function isValidStreamUrl($url) { if (empty($url)) { return false; } $allowedDomains = Config::get('stream.allowed_domains', []); if (empty($allowedDomains)) { $allowedDomains = ['38.64.28.91:23456']; // Default fallback } if (!is_array($allowedDomains)) { $allowedDomains = explode(',', $allowedDomains); } // Normalize domains $allowedDomains = array_map(function($domain) { return trim(strtolower($domain)); }, $allowedDomains); // Parse URL $parsedUrl = parse_url($url); if (!$parsedUrl || !isset($parsedUrl['host']) || !isset($parsedUrl['scheme'])) { return false; } $urlDomain = strtolower($parsedUrl['host']); $urlPort = $parsedUrl['port'] ?? ($parsedUrl['scheme'] === 'https' ? 443 : 80); $fullDomain = $urlDomain . ':' . $urlPort; // Check if domain is allowed foreach ($allowedDomains as $allowed) { if ($fullDomain === $allowed || $urlDomain === $allowed) { // Additional validation: only allow specific paths $path = $parsedUrl['path'] ?? ''; if (preg_match('#^/.*\.(m3u8|ts)$#', $path) || $path === '/stream.m3u8') { return true; } } } return false; } /** * Generate rate limiting key */ public static function getRateLimitKey($identifier, $action = 'general') { $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; return "rate_limit:{$action}:{$ip}:{$identifier}"; } /** * Check if request is within rate limits */ public static function checkRateLimit($identifier, $action = 'general', $maxRequests = null, $timeWindow = 60) { if (!$maxRequests) { $maxRequests = Config::get('rate_limit.requests_per_minute', 60); } $key = self::getRateLimitKey($identifier, $action); // Simple file-based rate limiting (use Redis/APCu in production) $cacheFile = sys_get_temp_dir() . '/rate_limit_' . md5($key); $data = []; if (file_exists($cacheFile)) { $data = json_decode(file_get_contents($cacheFile), true) ?: []; } $now = time(); $windowStart = $now - $timeWindow; // Filter old requests $data = array_filter($data, function($timestamp) use ($windowStart) { return $timestamp > $windowStart; }); if (count($data) >= $maxRequests) { return false; // Rate limit exceeded } // Add current request $data[] = $now; // Keep only recent requests $data = array_slice($data, -$maxRequests); file_put_contents($cacheFile, json_encode($data)); return true; } /** * Get client IP address */ public static function getClientIP() { $headers = [ 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR' ]; foreach ($headers as $header) { if (!empty($_SERVER[$header])) { // Handle comma-separated IPs (X-Forwarded-For) $ip = trim(explode(',', $_SERVER[$header])[0]); // Basic IP validation if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { return $ip; } } } return $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'; } /** * Log security event */ public static function logSecurityEvent($event, $details = []) { $logData = [ 'timestamp' => date('Y-m-d H:i:s'), 'event' => $event, 'ip' => self::getClientIP(), 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown', 'details' => $details ]; error_log("SECURITY: " . json_encode($logData)); } /** * Check for suspicious request patterns */ public static function detectSuspiciousActivity() { $warnings = []; // Check for multiple failed authentication attempts if (isset($_SESSION['auth_attempts']) && $_SESSION['auth_attempts'] > 3) { $warnings[] = 'Multiple authentication failures'; } // Check for rapid requests (potential DDoS) $requestCount = $_SESSION['recent_requests'] ?? 0; if ($requestCount > 100) { $warnings[] = 'High request frequency detected'; } // Log warnings foreach ($warnings as $warning) { self::logSecurityEvent('suspicious_activity', ['warning' => $warning]); } return $warnings; } }