shouldBypass($request)) { return $next($request); } $settings = AppSetting::get('geo.settings', []); if (empty($settings['enabled'])) { return $next($request); } $ip = $request->ip(); // Never block localhost in dev if (in_array($ip, ['127.0.0.1', '::1', '::ffff:127.0.0.1'])) { return $next($request); } $geoData = $this->fetchGeoData($ip); $country = $geoData['countryCode'] ?? null; $isProxy = $geoData['proxy'] ?? false; $isHosting = $geoData['hosting'] ?? false; // VPN / Proxy check if (!empty($settings['vpn_block'])) { $vpnProvider = $settings['vpn_provider'] ?? 'none'; $isVpn = match ($vpnProvider) { 'ipqualityscore' => Cache::remember("geo_vpn_iqs_{$ip}", 3600, fn() => $this->checkIpQualityScore($ip, $settings['vpn_api_key'] ?? '')), 'proxycheck' => Cache::remember("geo_vpn_pc_{$ip}", 3600, fn() => $this->checkProxyCheck($ip, $settings['vpn_api_key'] ?? '')), default => ($isProxy || $isHosting), // free ip-api.com fallback }; if ($isVpn) { return $this->blockResponse($request, $settings, 'vpn'); } } // Country check if ($country) { $mode = $settings['mode'] ?? 'blacklist'; $blocked = array_map('strtoupper', $settings['blocked_countries'] ?? []); $allowed = array_map('strtoupper', $settings['allowed_countries'] ?? []); $upper = strtoupper($country); $isBlocked = match ($mode) { 'whitelist' => !empty($allowed) && !in_array($upper, $allowed), default => !empty($blocked) && in_array($upper, $blocked), }; if ($isBlocked) { return $this->blockResponse($request, $settings, 'country'); } } return $next($request); } private function shouldBypass(Request $request): bool { $path = ltrim($request->path(), '/'); foreach (self::BYPASS_PATHS as $bypass) { if ($path === $bypass || str_starts_with($path, $bypass . '/')) { return true; } } // Skip API routes if ($request->is('api/*')) { return true; } return false; } private function blockResponse(Request $request, array $settings, string $reason) { $message = $settings['block_message'] ?? 'This service is not available in your region.'; $redirectUrl = $settings['redirect_url'] ?? ''; if ($redirectUrl) { return redirect()->away($redirectUrl); } if ($request->header('X-Inertia')) { return Inertia::render('GeoBlocked', [ 'message' => $message, 'reason' => $reason, ])->toResponse($request)->setStatusCode(403); } return Inertia::render('GeoBlocked', [ 'message' => $message, 'reason' => $reason, ])->toResponse($request)->setStatusCode(403); } private function fetchGeoData(string $ip): array { return Cache::remember("geo_data_{$ip}", 3600, function () use ($ip) { try { $res = Http::timeout(3)->get("http://ip-api.com/json/{$ip}", [ 'fields' => 'countryCode,proxy,hosting', ]); if ($res->ok()) { return $res->json() ?? []; } } catch (\Throwable) {} return []; }); } private function checkIpQualityScore(string $ip, string $apiKey): bool { if (!$apiKey) return false; try { $res = Http::timeout(4)->get("https://ipqualityscore.com/api/json/ip/{$apiKey}/{$ip}", [ 'strictness' => 1, 'allow_public_access_points' => 'true', 'fast' => 'true', ]); if ($res->ok()) { $data = $res->json(); return (bool) ($data['vpn'] ?? $data['proxy'] ?? $data['tor'] ?? false); } } catch (\Throwable) {} return false; } private function checkProxyCheck(string $ip, string $apiKey): bool { if (!$apiKey) return false; try { $res = Http::timeout(4)->get("https://proxycheck.io/v2/{$ip}", [ 'key' => $apiKey, 'vpn' => 1, 'asn' => 0, ]); if ($res->ok()) { $data = $res->json(); $entry = $data[$ip] ?? []; return strtolower($entry['proxy'] ?? 'no') === 'yes'; } } catch (\Throwable) {} return false; } }