Initialer Laravel Commit für BetiX
This commit is contained in:
168
app/Http/Middleware/GeoBlockMiddleware.php
Normal file
168
app/Http/Middleware/GeoBlockMiddleware.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\AppSetting;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class GeoBlockMiddleware
|
||||
{
|
||||
private const BYPASS_PATHS = [
|
||||
'blocked', 'favicon.ico', 'up',
|
||||
'admin', 'login', 'register',
|
||||
];
|
||||
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
// Skip for admin users, static paths, and the blocked page itself
|
||||
if ($this->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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user