Network Security

HTTPS configuration, Content Security Policy, rate limiting, and network protection measures.

Network Security

Smart Shelf implements comprehensive network security measures to protect against various attack vectors and ensure secure communication between clients and servers.

HTTPS & TLS Configuration

SSL/TLS Enforcement

// next.config.mjs
const nextConfig = {
  // Force HTTPS in production
  async redirects() {
    if (process.env.NODE_ENV === 'production') {
      return [
        {
          source: '/(.*)',
          has: [
            {
              type: 'header',
              key: 'x-forwarded-proto',
              value: 'http',
            },
          ],
          destination: 'https://smartshelf.app/:path*',
          permanent: true,
        },
      ]
    }
    return []
  },
  
  // Security headers
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=31536000; includeSubDomains; preload'
          },
          {
            key: 'X-Frame-Options',
            value: 'DENY'
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff'
          },
          {
            key: 'X-XSS-Protection',
            value: '1; mode=block'
          },
          {
            key: 'Referrer-Policy',
            value: 'strict-origin-when-cross-origin'
          },
          {
            key: 'Permissions-Policy',
            value: 'camera=(), microphone=(), geolocation=(), payment=()'
          }
        ]
      }
    ]
  }
}

export default nextConfig

Middleware HTTPS Enforcement

// middleware.ts
import { NextRequest, NextResponse } from 'next/server'

export function middleware(request: NextRequest) {
  const response = NextResponse.next()
  
  // Enforce HTTPS in production
  if (
    process.env.NODE_ENV === 'production' &&
    request.headers.get('x-forwarded-proto') !== 'https'
  ) {
    return NextResponse.redirect(
      `https://${request.headers.get('host')}${request.nextUrl.pathname}${request.nextUrl.search}`,
      301
    )
  }
  
  // Add security headers
  response.headers.set('X-DNS-Prefetch-Control', 'off')
  response.headers.set('X-Frame-Options', 'DENY')
  response.headers.set('X-Content-Type-Options', 'nosniff')
  response.headers.set('Referrer-Policy', 'origin-when-cross-origin')
  response.headers.set('X-XSS-Protection', '1; mode=block')
  
  return response
}

Content Security Policy (CSP)

Comprehensive CSP Implementation

// lib/security/csp.ts
export class ContentSecurityPolicy {
  
  private static getCSPDirectives(): Record<string, string[]> {
    const isProd = process.env.NODE_ENV === 'production'
    
    return {
      'default-src': ["'self'"],
      'script-src': [
        "'self'",
        ...(isProd ? [] : ["'unsafe-eval'", "'unsafe-inline'"]),
        'https://vercel.live',
        'https://*.supabase.co',
        'https://va.vercel-scripts.com'
      ],
      'style-src': [
        "'self'",
        "'unsafe-inline'",
        'https://fonts.googleapis.com'
      ],
      'font-src': [
        "'self'",
        'https://fonts.gstatic.com',
        'data:'
      ],
      'img-src': [
        "'self'",
        'data:',
        'blob:',
        'https:',
        'https://*.supabase.co'
      ],
      'connect-src': [
        "'self'",
        'https://*.supabase.co',
        'wss://*.supabase.co',
        'https://vercel.live',
        'https://vitals.vercel-insights.com'
      ],
      'frame-src': ["'none'"],
      'object-src': ["'none'"],
      'base-uri': ["'self'"],
      'form-action': ["'self'"],
      'frame-ancestors': ["'none'"],
      'upgrade-insecure-requests': []
    }
  }
  
  static generateCSPHeader(): string {
    const directives = this.getCSPDirectives()
    
    return Object.entries(directives)
      .map(([directive, sources]) => {
        if (sources.length === 0) {
          return directive
        }
        return `${directive} ${sources.join(' ')}`
      })
      .join('; ')
  }
  
  static applyCSP(response: NextResponse): NextResponse {
    const csp = this.generateCSPHeader()
    response.headers.set('Content-Security-Policy', csp)
    
    // Also set CSP report-only for monitoring
    if (process.env.CSP_REPORT_URI) {
      const reportOnlyCSP = `${csp}; report-uri ${process.env.CSP_REPORT_URI}`
      response.headers.set('Content-Security-Policy-Report-Only', reportOnlyCSP)
    }
    
    return response
  }
}

// Apply CSP in middleware
export function middleware(request: NextRequest) {
  const response = NextResponse.next()
  return ContentSecurityPolicy.applyCSP(response)
}

CSP Violation Reporting

// app/api/csp-report/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
  try {
    const report = await request.json()
    
    // Log CSP violation
    console.error('CSP Violation:', {
      'document-uri': report['document-uri'],
      'violated-directive': report['violated-directive'],
      'blocked-uri': report['blocked-uri'],
      'original-policy': report['original-policy'],
      'user-agent': request.headers.get('user-agent'),
      timestamp: new Date().toISOString()
    })
    
    // Store violation in database for analysis
    await supabase
      .from('csp_violations')
      .insert({
        document_uri: report['document-uri'],
        violated_directive: report['violated-directive'],
        blocked_uri: report['blocked-uri'],
        original_policy: report['original-policy'],
        user_agent: request.headers.get('user-agent'),
        ip_address: request.headers.get('x-forwarded-for') || 'unknown',
        created_at: new Date().toISOString()
      })
    
    return NextResponse.json({ received: true })
  } catch (error) {
    console.error('CSP report processing failed:', error)
    return NextResponse.json({ error: 'Failed to process report' }, { status: 500 })
  }
}

Rate Limiting

Advanced Rate Limiting Strategy

// lib/security/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!
})

export class RateLimitManager {
  
  // Authentication rate limiting - strict
  static authRateLimit = new Ratelimit({
    redis,
    limiter: Ratelimit.slidingWindow(5, '15 m'),
    analytics: true,
    prefix: 'rl:auth'
  })
  
  // General API rate limiting
  static apiRateLimit = new Ratelimit({
    redis,
    limiter: Ratelimit.slidingWindow(100, '1 h'),
    analytics: true,
    prefix: 'rl:api'
  })
  
  // Premium users get higher limits
  static premiumApiRateLimit = new Ratelimit({
    redis,
    limiter: Ratelimit.slidingWindow(1000, '1 h'),
    analytics: true,
    prefix: 'rl:api:premium'
  })
  
  // Search API - more restrictive
  static searchRateLimit = new Ratelimit({
    redis,
    limiter: Ratelimit.slidingWindow(20, '1 m'),
    analytics: true,
    prefix: 'rl:search'
  })
  
  // File upload rate limiting
  static uploadRateLimit = new Ratelimit({
    redis,
    limiter: Ratelimit.slidingWindow(10, '10 m'),
    analytics: true,
    prefix: 'rl:upload'
  })
  
  // Get appropriate rate limiter based on user and endpoint
  static getRateLimiter(userRole: string, endpoint: string): Ratelimit {
    if (endpoint.includes('/auth/')) {
      return this.authRateLimit
    }
    
    if (endpoint.includes('/search')) {
      return this.searchRateLimit
    }
    
    if (endpoint.includes('/upload')) {
      return this.uploadRateLimit
    }
    
    if (userRole === 'admin' || userRole === 'premium') {
      return this.premiumApiRateLimit
    }
    
    return this.apiRateLimit
  }
  
  // Apply rate limiting with custom response
  static async applyRateLimit(
    identifier: string,
    rateLimiter: Ratelimit,
    request: NextRequest
  ): Promise<NextResponse | null> {
    const { success, limit, reset, remaining } = await rateLimiter.limit(identifier)
    
    if (!success) {
      const resetTime = new Date(reset)
      
      // Log rate limit violation
      await this.logRateLimitViolation({
        identifier,
        endpoint: request.nextUrl.pathname,
        limit,
        reset_time: resetTime.toISOString(),
        ip_address: request.headers.get('x-forwarded-for') || 'unknown',
        user_agent: request.headers.get('user-agent') || 'unknown'
      })
      
      return NextResponse.json(
        {
          error: 'Rate limit exceeded',
          limit,
          remaining: 0,
          reset: resetTime.toISOString()
        },
        {
          status: 429,
          headers: {
            'X-RateLimit-Limit': limit.toString(),
            'X-RateLimit-Remaining': '0',
            'X-RateLimit-Reset': reset.toString(),
            'Retry-After': Math.ceil((reset - Date.now()) / 1000).toString()
          }
        }
      )
    }
    
    return null // No rate limit exceeded
  }
  
  private static async logRateLimitViolation(details: any): Promise<void> {
    await supabase
      .from('rate_limit_violations')
      .insert({
        ...details,
        created_at: new Date().toISOString()
      })
  }
}

Rate Limiting Middleware

// lib/security/rate-limit-middleware.ts
export function withRateLimit(
  handler: (req: NextRequest) => Promise<NextResponse>,
  options?: {
    rateLimiter?: Ratelimit
    getIdentifier?: (req: NextRequest) => string
  }
) {
  return async function(request: NextRequest): Promise<NextResponse> {
    try {
      // Get user info for role-based limiting
      const user = await getCurrentUser(request)
      const userRole = user?.user_metadata?.role || 'viewer'
      
      // Determine rate limiter
      const rateLimiter = options?.rateLimiter || 
        RateLimitManager.getRateLimiter(userRole, request.nextUrl.pathname)
      
      // Get identifier (IP + user ID if authenticated)
      const identifier = options?.getIdentifier?.(request) || 
        `${request.headers.get('x-forwarded-for') || 'unknown'}:${user?.id || 'anonymous'}`
      
      // Apply rate limiting
      const rateLimitResponse = await RateLimitManager.applyRateLimit(
        identifier,
        rateLimiter,
        request
      )
      
      if (rateLimitResponse) {
        return rateLimitResponse
      }
      
      // Proceed with original handler
      return await handler(request)
      
    } catch (error) {
      console.error('Rate limiting error:', error)
      return NextResponse.json(
        { error: 'Internal server error' },
        { status: 500 }
      )
    }
  }
}

DDoS Protection

Application-Level DDoS Protection

// lib/security/ddos-protection.ts
export class DDoSProtection {
  
  private static suspiciousIPs = new Set<string>()
  private static blockedIPs = new Set<string>()
  
  static async checkForDDoSAttack(request: NextRequest): Promise<boolean> {
    const ip = request.headers.get('x-forwarded-for') || 'unknown'
    const now = Date.now()
    
    // Check if IP is already blocked
    if (this.blockedIPs.has(ip)) {
      return true
    }
    
    // Collect metrics for the IP
    const metrics = await this.collectIPMetrics(ip, now)
    
    // Analyze patterns
    const isDDoS = await this.analyzeDDoSPatterns(ip, metrics)
    
    if (isDDoS) {
      await this.blockIP(ip, 'DDoS_detected')
      return true
    }
    
    return false
  }
  
  private static async collectIPMetrics(ip: string, timestamp: number) {
    const timeWindow = 60 * 1000 // 1 minute
    const startTime = new Date(timestamp - timeWindow).toISOString()
    
    const { data: requests } = await supabase
      .from('request_logs')
      .select('*')
      .eq('ip_address', ip)
      .gte('created_at', startTime)
    
    return {
      request_count: requests?.length || 0,
      unique_endpoints: new Set(requests?.map(r => r.endpoint)).size,
      error_rate: requests?.filter(r => r.status >= 400).length || 0,
      avg_request_size: requests?.reduce((sum, r) => sum + (r.request_size || 0), 0) / (requests?.length || 1)
    }
  }
  
  private static async analyzeDDoSPatterns(ip: string, metrics: any): Promise<boolean> {
    // Pattern 1: Too many requests per minute
    if (metrics.request_count > 1000) {
      await this.logSecurityEvent(ip, 'high_request_volume', metrics)
      return true
    }
    
    // Pattern 2: High error rate (scanning for vulnerabilities)
    if (metrics.error_rate > 50 && metrics.request_count > 100) {
      await this.logSecurityEvent(ip, 'high_error_rate', metrics)
      return true
    }
    
    // Pattern 3: Requesting too many different endpoints (reconnaissance)
    if (metrics.unique_endpoints > 50) {
      await this.logSecurityEvent(ip, 'endpoint_scanning', metrics)
      return true
    }
    
    // Pattern 4: Unusual request patterns
    if (metrics.avg_request_size > 1000000) { // > 1MB average
      await this.logSecurityEvent(ip, 'large_payload_attack', metrics)
      return true
    }
    
    return false
  }
  
  private static async blockIP(ip: string, reason: string): Promise<void> {
    this.blockedIPs.add(ip)
    
    // Store in database for persistent blocking
    await supabase
      .from('blocked_ips')
      .insert({
        ip_address: ip,
        reason,
        blocked_at: new Date().toISOString(),
        expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString() // 24 hours
      })
    
    // Log security incident
    await this.logSecurityEvent(ip, 'ip_blocked', { reason })
    
    // Notify security team
    await this.notifySecurityTeam({
      type: 'ip_blocked',
      ip_address: ip,
      reason,
      timestamp: new Date().toISOString()
    })
  }
  
  private static async logSecurityEvent(ip: string, event_type: string, details: any): Promise<void> {
    await supabase
      .from('security_events')
      .insert({
        ip_address: ip,
        event_type,
        details,
        severity: 'high',
        created_at: new Date().toISOString()
      })
  }
  
  private static async notifySecurityTeam(alert: any): Promise<void> {
    // Implementation would send alerts to security team
    console.log('Security Alert:', alert)
  }
}

Web Application Firewall (WAF)

Custom WAF Rules

// lib/security/waf.ts
export class WebApplicationFirewall {
  
  // Common attack patterns
  private static readonly ATTACK_PATTERNS = {
    sql_injection: [
      /(\s|^)(union|select|insert|update|delete|drop|create|alter|exec|execute)(\s|$)/i,
      /(\s|^)(or|and)\s+\d+\s*=\s*\d+/i,
      /'\s*(or|and)\s*'.*?'\s*=\s*'/i,
      /(\s|^)(sleep|benchmark|waitfor)\s*\(/i
    ],
    xss: [
      /<script[^>]*>.*?<\/script>/i,
      /javascript:/i,
      /on\w+\s*=/i,
      /<iframe[^>]*>.*?<\/iframe>/i,
      /eval\s*\(/i,
      /expression\s*\(/i
    ],
    command_injection: [
      /(\s|^)(cat|ls|pwd|id|uname|whoami|ps|netstat|ifconfig)(\s|$)/i,
      /[;&|`$()]/,
      /\.\.\//,
      /\/etc\/passwd/i,
      /\/bin\/(sh|bash|csh|tcsh|zsh)/i
    ],
    directory_traversal: [
      /\.\.\//,
      /\.\.\\/,
      /\/etc\/passwd/i,
      /\/windows\/system32/i,
      /\\windows\\system32/i
    ],
    file_inclusion: [
      /php:\/\//i,
      /file:\/\//i,
      /ftp:\/\//i,
      /data:\/\//i,
      /include\s*\(/i,
      /require\s*\(/i
    ]
  }
  
  static async analyzeRequest(request: NextRequest): Promise<{
    blocked: boolean
    reasons: string[]
    risk_score: number
  }> {
    const reasons: string[] = []
    let riskScore = 0
    
    // Analyze URL
    const urlAnalysis = this.analyzeURL(request.url)
    if (urlAnalysis.malicious) {
      reasons.push(...urlAnalysis.reasons)
      riskScore += urlAnalysis.score
    }
    
    // Analyze headers
    const headerAnalysis = this.analyzeHeaders(request.headers)
    if (headerAnalysis.malicious) {
      reasons.push(...headerAnalysis.reasons)
      riskScore += headerAnalysis.score
    }
    
    // Analyze body (for POST requests)
    if (request.method === 'POST' || request.method === 'PUT') {
      try {
        const body = await request.text()
        const bodyAnalysis = this.analyzeBody(body)
        if (bodyAnalysis.malicious) {
          reasons.push(...bodyAnalysis.reasons)
          riskScore += bodyAnalysis.score
        }
      } catch (error) {
        // Body might not be text, skip analysis
      }
    }
    
    const blocked = riskScore >= 7 || reasons.length >= 3
    
    if (blocked) {
      await this.logWAFBlock(request, reasons, riskScore)
    }
    
    return { blocked, reasons, risk_score: riskScore }
  }
  
  private static analyzeURL(url: string): { malicious: boolean; reasons: string[]; score: number } {
    const reasons: string[] = []
    let score = 0
    
    // Check for attack patterns in URL
    Object.entries(this.ATTACK_PATTERNS).forEach(([attackType, patterns]) => {
      patterns.forEach(pattern => {
        if (pattern.test(url)) {
          reasons.push(`${attackType}_in_url`)
          score += 3
        }
      })
    })
    
    // Check for suspicious characters
    if (/[<>'"{}()]/g.test(url)) {
      reasons.push('suspicious_characters_in_url')
      score += 1
    }
    
    // Check for excessively long URLs
    if (url.length > 2000) {
      reasons.push('excessive_url_length')
      score += 2
    }
    
    return { malicious: score > 0, reasons, score }
  }
  
  private static analyzeHeaders(headers: Headers): { malicious: boolean; reasons: string[]; score: number } {
    const reasons: string[] = []
    let score = 0
    
    // Check User-Agent
    const userAgent = headers.get('user-agent') || ''
    if (!userAgent || userAgent.length < 10) {
      reasons.push('suspicious_user_agent')
      score += 2
    }
    
    // Check for attack patterns in headers
    headers.forEach((value, key) => {
      Object.entries(this.ATTACK_PATTERNS).forEach(([attackType, patterns]) => {
        patterns.forEach(pattern => {
          if (pattern.test(value)) {
            reasons.push(`${attackType}_in_header_${key}`)
            score += 3
          }
        })
      })
    })
    
    // Check for HTTP method override attacks
    const methodOverride = headers.get('x-http-method-override')
    if (methodOverride && !['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(methodOverride.toUpperCase())) {
      reasons.push('invalid_method_override')
      score += 2
    }
    
    return { malicious: score > 0, reasons, score }
  }
  
  private static analyzeBody(body: string): { malicious: boolean; reasons: string[]; score: number } {
    const reasons: string[] = []
    let score = 0
    
    // Check for attack patterns in body
    Object.entries(this.ATTACK_PATTERNS).forEach(([attackType, patterns]) => {
      patterns.forEach(pattern => {
        if (pattern.test(body)) {
          reasons.push(`${attackType}_in_body`)
          score += 4 // Body attacks are more serious
        }
      })
    })
    
    // Check for excessive body size
    if (body.length > 10000000) { // 10MB
      reasons.push('excessive_body_size')
      score += 3
    }
    
    return { malicious: score > 0, reasons, score }
  }
  
  private static async logWAFBlock(request: NextRequest, reasons: string[], riskScore: number): Promise<void> {
    await supabase
      .from('waf_blocks')
      .insert({
        ip_address: request.headers.get('x-forwarded-for') || 'unknown',
        method: request.method,
        url: request.url,
        user_agent: request.headers.get('user-agent') || 'unknown',
        reasons,
        risk_score: riskScore,
        blocked_at: new Date().toISOString()
      })
  }
}

WAF Middleware Implementation

// lib/security/waf-middleware.ts
export function withWAF(handler: (req: NextRequest) => Promise<NextResponse>) {
  return async function(request: NextRequest): Promise<NextResponse> {
    try {
      // Skip WAF for certain paths (like health checks)
      const skipPaths = ['/health', '/api/health', '/_next/', '/favicon.ico']
      if (skipPaths.some(path => request.nextUrl.pathname.startsWith(path))) {
        return await handler(request)
      }
      
      // Analyze request with WAF
      const analysis = await WebApplicationFirewall.analyzeRequest(request)
      
      if (analysis.blocked) {
        return NextResponse.json(
          {
            error: 'Request blocked by security policy',
            reference_id: crypto.randomUUID()
          },
          { status: 403 }
        )
      }
      
      // Add security score to headers for monitoring
      const response = await handler(request)
      response.headers.set('X-Security-Score', analysis.risk_score.toString())
      
      return response
      
    } catch (error) {
      console.error('WAF processing error:', error)
      // In case of WAF error, allow request to proceed but log the incident
      return await handler(request)
    }
  }
}

This comprehensive network security framework provides multiple layers of protection against various attack vectors while maintaining application performance and user experience.