Initial commit: Animexe Laravel platform
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AdminAccessMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if (!auth()->check()) {
|
||||
return redirect()->route('admin.login');
|
||||
}
|
||||
|
||||
if (!auth()->user()->isModerator()) {
|
||||
abort(403, 'Bu alana erişim yetkiniz yok.');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AdminMiddleware
|
||||
{
|
||||
/**
|
||||
* Usage:
|
||||
* ->middleware(['admin']) → admin ONLY (moderators denied)
|
||||
* ->middleware(['admin:animes.edit']) → admin, OR moderator WITH that permission
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, string $permission = null)
|
||||
{
|
||||
if (!auth()->check()) {
|
||||
return redirect()->route('admin.login');
|
||||
}
|
||||
|
||||
$user = auth()->user();
|
||||
|
||||
// Admins always pass
|
||||
if ($user->isAdmin()) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// Must be at least a moderator
|
||||
if ($user->role !== 'moderator') {
|
||||
abort(403, 'Bu alana erişim yetkiniz yok.');
|
||||
}
|
||||
|
||||
// Moderators always need a specific permission — no blanket access
|
||||
if (!$permission || !$user->can_mod($permission)) {
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['error' => 'Bu işlem için yetkiniz yok.'], 403);
|
||||
}
|
||||
return back()->with('error', 'Bu sayfaya erişim için gerekli izne sahip değilsiniz.');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class BotDetector
|
||||
{
|
||||
// Tamamen engelle — 403
|
||||
private const BAD_BOTS = [
|
||||
'scrapy', 'httrack', 'webcopier', 'webzip', 'teleportpro', 'webstripper',
|
||||
'offline explorer', 'larbin', 'libwww-perl',
|
||||
'masscan', 'nikto', 'sqlmap', 'nmap', 'zgrab', 'nuclei',
|
||||
'dirsearch', 'gobuster', 'feroxbuster', 'ffuf',
|
||||
'ahrefsbot', 'semrushbot', 'dotbot', 'mj12bot', 'blexbot',
|
||||
'bytespider', 'gptbot', 'claudebot', 'chatgpt-user',
|
||||
'ccbot', 'anthropic-ai', 'cohere-ai', 'omgili', 'omgilibot',
|
||||
'pinterestbot', 'petalbot', 'proximic', 'mediatoolkitbot',
|
||||
'dataforseobot', 'sitechecker', 'seokicks', 'linkfluence',
|
||||
'serpstatbot', 'serendeputy', 'riddler', 'netcraftsurveyagent',
|
||||
'netsystemsresearch', 'ioncrawl', 'brandverity',
|
||||
'wp_is_mobile', 'wordpress', 'wpbot',
|
||||
];
|
||||
|
||||
// Bu IP prefix'leri için tüm kontroller atlanır (güvenilir crawler'lar)
|
||||
private const TRUSTED_IP_PREFIXES = [
|
||||
'66.249.', // Googlebot
|
||||
'64.233.', // Google
|
||||
'74.125.', // Google
|
||||
'209.85.', // Google
|
||||
'216.239.', // Google
|
||||
'34.68.', // Google Cloud us-central1
|
||||
'34.64.', // Google Cloud
|
||||
'35.187.', // Google Cloud
|
||||
'35.190.', // Google Cloud
|
||||
];
|
||||
|
||||
// İzin ver ama bot olarak işaretle (SEO crawler'lar)
|
||||
private const GOOD_BOTS = [
|
||||
'googlebot', 'google-inspectiontool', 'google-structured-data-testing-tool',
|
||||
'adsbot-google', 'adsbot-google-mobile', 'mediapartners-google',
|
||||
'apis-google', 'feedfetcher-google', 'google-adwords-instant',
|
||||
'bingbot', 'msnbot', 'adidxbot', 'bingpreview',
|
||||
'duckduckbot', 'baiduspider', 'yandexbot', 'yandexmobilebot',
|
||||
'slurp', 'teoma', 'ia_archiver',
|
||||
'facebot', 'facebookexternalhit',
|
||||
'twitterbot', 'linkedinbot', 'whatsapp',
|
||||
'applebot', 'discordbot', 'telegrambot',
|
||||
'slackbot', 'pingdom',
|
||||
];
|
||||
|
||||
// Rate-limit uygula (dakikada 10'dan fazla → uyarı, 30'dan fazla → otomatik engel)
|
||||
private const GENERIC_BOTS = [
|
||||
'curl/', 'wget/', 'python-requests', 'python-urllib',
|
||||
'go-http-client', 'java/', 'okhttp/', 'libcurl',
|
||||
'node-fetch', 'node.js', 'axios', 'got/',
|
||||
'apache-httpclient', 'php/', 'perl/', 'ruby',
|
||||
'postman', 'insomnia', 'httpie',
|
||||
];
|
||||
|
||||
// Bu path'ler için sadece log tut, engelleme yapma
|
||||
private const SKIP_PATHS = [
|
||||
'/up', '/api/', '/sitemap',
|
||||
];
|
||||
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$ip = $request->ip();
|
||||
$ua = strtolower($request->userAgent() ?? '');
|
||||
$path = $request->path();
|
||||
|
||||
// Skip paths
|
||||
foreach (self::SKIP_PATHS as $skip) {
|
||||
if (str_starts_with('/' . $path, $skip)) {
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
// Güvenilir IP (Google vb.) — tüm kontrolleri atla
|
||||
foreach (self::TRUSTED_IP_PREFIXES as $prefix) {
|
||||
if (str_starts_with($ip, $prefix)) {
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
// Manuel engelli IP kontrolü
|
||||
if ($this->isBlockedIp($ip)) {
|
||||
$this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'ip_blocked', 'blocked_ip');
|
||||
return response('Erişim engellendi.', 403);
|
||||
}
|
||||
|
||||
// UA boşsa bot olarak işaretle
|
||||
if (empty($ua)) {
|
||||
$request->attributes->set('is_bot', true);
|
||||
$request->attributes->set('bot_type', 'noua');
|
||||
$this->logBot($ip, '', '/' . $path, $request->method(), 'allowed', 'no_ua');
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// Kötü bot mu?
|
||||
foreach (self::BAD_BOTS as $pattern) {
|
||||
if (str_contains($ua, $pattern)) {
|
||||
$this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'blocked', $pattern);
|
||||
return response('', 403);
|
||||
}
|
||||
}
|
||||
|
||||
// İyi bot mu?
|
||||
foreach (self::GOOD_BOTS as $pattern) {
|
||||
if (str_contains($ua, $pattern)) {
|
||||
$request->attributes->set('is_bot', true);
|
||||
$request->attributes->set('bot_type', 'good');
|
||||
// İyi botlar için çok agresif rate limit (dakikada 60)
|
||||
if ($this->isRateLimited($ip, 60, 'good_bot')) {
|
||||
return response('', 429);
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
// Generic araç mı?
|
||||
foreach (self::GENERIC_BOTS as $pattern) {
|
||||
if (str_contains($ua, $pattern)) {
|
||||
$request->attributes->set('is_bot', true);
|
||||
$request->attributes->set('bot_type', 'generic');
|
||||
if ($this->isRateLimited($ip, 10, 'generic')) {
|
||||
$this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'rate_limited', $pattern);
|
||||
// 30+ istek → otomatik engelle
|
||||
$count = Cache::get("bot_count_{$ip}", 0);
|
||||
if ($count > 30) {
|
||||
$this->autoBlock($ip, 'Otomatik: dakikada 30+ generic bot isteği');
|
||||
}
|
||||
return response('', 429);
|
||||
}
|
||||
$this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'allowed', $pattern);
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
// Normal kullanıcı — genel rate limit (dakikada 120 istek)
|
||||
if ($this->isRateLimited($ip, 120, 'human')) {
|
||||
$this->logBot($ip, $request->userAgent(), '/' . $path, $request->method(), 'rate_limited', 'human_flood');
|
||||
$count = Cache::get("bot_count_{$ip}", 0);
|
||||
if ($count > 200) {
|
||||
$this->autoBlock($ip, 'Otomatik: dakikada 200+ istek flood');
|
||||
}
|
||||
return response('', 429);
|
||||
}
|
||||
|
||||
$request->attributes->set('is_bot', false);
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
private function isBlockedIp(string $ip): bool
|
||||
{
|
||||
return Cache::remember("blocked_ip_{$ip}", 300, function () use ($ip) {
|
||||
try {
|
||||
return DB::table('blocked_ips')
|
||||
->where('ip', $ip)
|
||||
->where(function ($q) {
|
||||
$q->whereNull('expires_at')->orWhere('expires_at', '>', now());
|
||||
})
|
||||
->exists();
|
||||
} catch (\Exception) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function isRateLimited(string $ip, int $maxPerMinute, string $type): bool
|
||||
{
|
||||
$key = "rl_{$type}_{$ip}";
|
||||
$count = Cache::get($key, 0);
|
||||
|
||||
if ($count === 0) {
|
||||
Cache::put($key, 1, 60);
|
||||
} else {
|
||||
Cache::increment($key);
|
||||
}
|
||||
|
||||
// Bot count ayrı izle
|
||||
Cache::put("bot_count_{$ip}", Cache::get("bot_count_{$ip}", 0) + 1, 60);
|
||||
|
||||
return $count >= $maxPerMinute;
|
||||
}
|
||||
|
||||
private function autoBlock(string $ip, string $reason): void
|
||||
{
|
||||
try {
|
||||
DB::table('blocked_ips')->insertOrIgnore([
|
||||
'ip' => $ip,
|
||||
'reason' => $reason,
|
||||
'auto_blocked' => 1,
|
||||
'blocked_at' => now(),
|
||||
'expires_at' => now()->addHours(24),
|
||||
]);
|
||||
Cache::forget("blocked_ip_{$ip}");
|
||||
} catch (\Exception) {}
|
||||
}
|
||||
|
||||
private function logBot(string $ip, ?string $ua, string $path, string $method, string $action, string $botName): void
|
||||
{
|
||||
try {
|
||||
DB::table('analytics_bot_logs')->insert([
|
||||
'ip' => $ip,
|
||||
'user_agent' => mb_substr($ua ?? '', 0, 500),
|
||||
'path' => mb_substr($path, 0, 500),
|
||||
'method' => $method,
|
||||
'action' => $action,
|
||||
'bot_name' => mb_substr($botName, 0, 100),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
} catch (\Exception) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ImportApiMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$apiKey = config('app.import_api_key');
|
||||
$provided = $request->header('X-Import-Key') ?? $request->query('api_key');
|
||||
|
||||
if (!$apiKey || $provided !== $apiKey) {
|
||||
return response()->json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Video player sayfasını başka sitelere embed edilmekten korur.
|
||||
* X-Frame-Options: SAMEORIGIN → sadece kendi domainimizden iframe açılabilir.
|
||||
*/
|
||||
class SecurePlayer
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$response = $next($request);
|
||||
|
||||
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
|
||||
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
||||
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\SeoRedirect;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SeoRedirectMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if ($request->isMethod('GET')) {
|
||||
try {
|
||||
$path = '/' . ltrim($request->path(), '/');
|
||||
$redirect = cache()->remember('seo_redirect_' . md5($path), 300, function () use ($path) {
|
||||
return SeoRedirect::where('from_path', $path)->where('is_active', true)->first();
|
||||
});
|
||||
|
||||
if ($redirect) {
|
||||
SeoRedirect::where('id', $redirect->id)->increment('hits');
|
||||
cache()->forget('seo_redirect_' . md5($path));
|
||||
return redirect($redirect->to_path, $redirect->type);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// DB/cache hatası — redirect yerine normal akışa devam et, site çökmesin
|
||||
\Illuminate\Support\Facades\Log::error('SeoRedirectMiddleware DB error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user