import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; import { validateRequestIP, getClientIP } from './lib/security/ipWhitelist'; import { PROTECTED_PATHS, BLOCKED_USER_AGENTS, BLOCKED_IPS, SUSPICIOUS_PATTERNS, } from './lib/security/config'; export function middleware(request: NextRequest) { // Safely get pathname and search let pathname = '/'; let search = ''; try { if (request?.nextUrl) { pathname = request.nextUrl.pathname || '/'; search = request.nextUrl.search || ''; } } catch (e) { console.warn('Error getting URL from request:', e); } // Safely get headers let headersObj: Record = {}; try { if (request?.headers && typeof request.headers.entries === 'function') { try { headersObj = Object.fromEntries(request.headers.entries()); } catch (entriesError) { // If entries() fails, try to manually extract headers console.warn('Error getting header entries, trying alternative method:', entriesError); headersObj = {}; } } } catch (e) { // If headers can't be converted, use empty object console.warn('Error converting headers:', e); } // Safely get user agent let userAgent = ''; try { if (request?.headers && typeof request.headers.get === 'function') { const ua = request.headers.get('user-agent'); userAgent = ua || ''; } } catch (e) { console.warn('Error getting user agent:', e); } const ip = getClientIP(headersObj); // Security checks const securityChecks: Array<{ check: () => boolean; action: () => NextResponse }> = []; // 1. Block malicious user agents securityChecks.push({ check: () => BLOCKED_USER_AGENTS.some(blocked => userAgent.toLowerCase().includes(blocked.toLowerCase())), action: () => { console.warn(`[SECURITY] Blocked malicious user agent: ${userAgent} from IP: ${ip}`); return new NextResponse('Forbidden', { status: 403 }); }, }); // 2. Block known malicious IPs securityChecks.push({ check: () => BLOCKED_IPS.includes(ip), action: () => { console.warn(`[SECURITY] Blocked known malicious IP: ${ip}`); return new NextResponse('Forbidden', { status: 403 }); }, }); // 3. IP whitelist check for protected paths if (PROTECTED_PATHS.some(path => pathname.startsWith(path))) { const validation = validateRequestIP(headersObj); if (!validation.allowed) { console.warn(`[SECURITY] Blocked non-whitelisted IP: ${ip} from accessing: ${pathname}`); return new NextResponse( JSON.stringify({ error: 'Forbidden', message: 'Access denied from this IP address', }), { status: 403, headers: { 'Content-Type': 'application/json' }, } ); } } // 4. Block suspicious query parameters (potential XSS/SQL injection attempts) const fullUrl = pathname + search; if (SUSPICIOUS_PATTERNS.some(pattern => pattern.test(fullUrl))) { console.warn(`[SECURITY] Blocked suspicious request pattern from IP: ${ip} - URL: ${fullUrl}`); return new NextResponse('Bad Request', { status: 400 }); } // 5. Rate limiting headers (basic implementation) // In production, use a proper rate limiting service const response = NextResponse.next(); // Add security headers (safely check if headers exist) try { if (response?.headers && typeof response.headers.set === 'function') { response.headers.set('X-Content-Type-Options', 'nosniff'); response.headers.set('X-Frame-Options', 'SAMEORIGIN'); response.headers.set('X-XSS-Protection', '1; mode=block'); response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin'); } } catch (e) { console.warn('Error setting response headers:', e); } // Execute security checks for (const { check, action } of securityChecks) { if (check()) { return action(); } } return response; } // Configure which routes the middleware runs on export const config = { matcher: [ /* * Match all request paths except: * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) * - public files (images, etc.) */ '/((?!_next/static|_next/image|favicon.ico|images|icons|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|woff|woff2|ttf|eot)).*)', ], };