import { type NextRequest, NextResponse } from 'next/server'; import { updateSession } from '@/utils/supabase/middleware'; // In-memory store for tracking IPs (use Redis in production) const ipAttempts = new Map(); const bannedIPs = new Set(); // Suspicious patterns that indicate malicious activity const MALICIOUS_PATTERNS = [ /web-inf/i, /\.jsp/i, /\.php/i, /puttest/i, /WEB-INF/i, /\.xml$/i, /perl/i, /xampp/i, /phpwebgallery/i, /FileManager/i, /standalonemanager/i, /h2console/i, /WebAdmin/i, /login_form\.php/i, /%2e/i, /%u002e/i, /\.%00/i, /\.\./, /lcgi/i, ]; // Suspicious HTTP methods const SUSPICIOUS_METHODS = ['TRACE', 'PUT', 'DELETE', 'PATCH']; const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute const MAX_ATTEMPTS = 10; // Max suspicious requests per window const BAN_DURATION = 30 * 60 * 1000; // 30 minutes const getClientIP = (request: NextRequest): string => { const forwarded = request.headers.get('x-forwarded-for'); const realIP = request.headers.get('x-real-ip'); if (forwarded) { return forwarded.split(',')[0].trim(); } if (realIP) { return realIP; } return request.ip ?? 'unknown'; }; const isPathSuspicious = (pathname: string): boolean => { return MALICIOUS_PATTERNS.some((pattern) => pattern.test(pathname)); }; const isMethodSuspicious = (method: string): boolean => { return SUSPICIOUS_METHODS.includes(method); }; const updateIPAttempts = (ip: string): boolean => { const now = Date.now(); const attempts = ipAttempts.get(ip); if (!attempts || now - attempts.lastAttempt > RATE_LIMIT_WINDOW) { ipAttempts.set(ip, { count: 1, lastAttempt: now }); return false; } attempts.count++; attempts.lastAttempt = now; if (attempts.count > MAX_ATTEMPTS) { bannedIPs.add(ip); // Clean up the attempts record ipAttempts.delete(ip); // Auto-unban after duration (in production, use a proper scheduler) setTimeout(() => { bannedIPs.delete(ip); }, BAN_DURATION); return true; } return false; }; export const middleware = async (request: NextRequest) => { const { pathname } = request.nextUrl; const method = request.method; const ip = getClientIP(request); // Check if IP is already banned if (bannedIPs.has(ip)) { console.log(`🚫 Blocked banned IP: ${ip} trying to access ${pathname}`); return new NextResponse('Access denied.', { status: 403 }); } // Check for suspicious activity const isSuspiciousPath = isPathSuspicious(pathname); const isSuspiciousMethod = isMethodSuspicious(method); if (isSuspiciousPath || isSuspiciousMethod) { console.log(`⚠️ Suspicious activity from ${ip}: ${method} ${pathname}`); const shouldBan = updateIPAttempts(ip); if (shouldBan) { console.log(`🔨 IP ${ip} has been banned for suspicious activity`); return new NextResponse('Access denied - IP banned', { status: 403 }); } // Return 404 to not reveal the blocking mechanism return new NextResponse('Not Found', { status: 404 }); } return await updateSession(request); }; export const config = { matcher: [ /* * Match all request paths except: * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) * - /monitoring-tunnel (Sentry monitoring) * - images - .svg, .png, .jpg, .jpeg, .gif, .webp * Feel free to modify this pattern to include more paths. */ '/((?!_next/static|_next/image|favicon.ico|monitoring-tunnel|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', ], };