← back to Wine Finder Next

middleware.ts

93 lines

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { comprehensiveSecurityCheck } from './lib/advancedSecurity';
import { isIPBlocked } from './lib/securityMonitoring';

// Security headers
export function middleware(request: NextRequest) {
  // Extract client IP
  const forwardedFor = request.headers.get('x-forwarded-for');
  const realIp = request.headers.get('x-real-ip');
  const ip = forwardedFor?.split(',')[0] || realIp || 'unknown';

  // Check if IP is blocked
  if (isIPBlocked(ip)) {
    return NextResponse.json({
      success: false,
      error: 'Access denied. Your IP has been blocked due to suspicious activity.'
    }, { status: 403 });
  }

  // Run comprehensive security check for API routes
  if (request.nextUrl.pathname.startsWith('/api/')) {
    const securityCheck = comprehensiveSecurityCheck({
      ip,
      headers: Object.fromEntries(request.headers),
      method: request.method,
      path: request.nextUrl.pathname
    });

    if (!securityCheck.allowed) {
      return NextResponse.json({
        success: false,
        error: 'Security check failed',
        reason: securityCheck.reason,
        riskScore: securityCheck.riskScore
      }, { status: 403 });
    }
  }

  const response = NextResponse.next();

  // Security Headers
  response.headers.set('X-DNS-Prefetch-Control', 'on');
  response.headers.set('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload');
  response.headers.set('X-Frame-Options', 'SAMEORIGIN');
  response.headers.set('X-Content-Type-Options', 'nosniff');
  response.headers.set('X-XSS-Protection', '1; mode=block');
  response.headers.set('Referrer-Policy', 'origin-when-cross-origin');
  response.headers.set('X-Permitted-Cross-Domain-Policies', 'none');

  // Remove server identification
  response.headers.delete('X-Powered-By');
  response.headers.delete('Server');

  // Content Security Policy
  response.headers.set(
    'Content-Security-Policy',
    [
      "default-src 'self'",
      "script-src 'self' 'unsafe-inline' 'unsafe-eval'", // Next.js requires unsafe-eval for dev
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https:",
      "font-src 'self' data:",
      "connect-src 'self' https:",
      "frame-ancestors 'self'",
      "base-uri 'self'",
      "form-action 'self'"
    ].join('; ')
  );

  // Permissions Policy (formerly Feature Policy)
  response.headers.set(
    'Permissions-Policy',
    'camera=(), microphone=(), geolocation=(), interest-cohort=()'
  );

  return response;
}

// Apply to all routes
export const config = {
  matcher: [
    /*
     * Match all request paths except:
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     * - public folder
     */
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
};