Initialer Laravel Commit für BetiX
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

This commit is contained in:
2026-04-04 18:01:50 +02:00
commit 0280278978
374 changed files with 65210 additions and 0 deletions

View 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;
}
}