API Security

API authentication, input validation, SQL injection prevention, and secure API design.

API Security

Smart Shelf's API security framework ensures all API endpoints are protected against common attacks while maintaining performance and usability. Our multi-layered approach includes authentication, authorization, input validation, and comprehensive security monitoring.

API Authentication & Authorization

JWT Token Management

// lib/api/auth/jwt.ts
import jwt from 'jsonwebtoken'
import { User } from '@supabase/supabase-js'

interface JWTPayload {
  sub: string // user ID
  role: string
  permissions: string[]
  iat: number
  exp: number
  jti: string // token ID for revocation
}

export class APITokenManager {
  
  static generateAPIToken(user: User, permissions: string[]): string {
    const payload: JWTPayload = {
      sub: user.id,
      role: user.user_metadata?.role || 'viewer',
      permissions,
      iat: Math.floor(Date.now() / 1000),
      exp: Math.floor(Date.now() / 1000) + (24 * 60 * 60), // 24 hours
      jti: crypto.randomUUID()
    }
    
    return jwt.sign(payload, process.env.JWT_SECRET!, {
      algorithm: 'HS256',
      issuer: 'smart-shelf-api',
      audience: 'smart-shelf-client'
    })
  }
  
  static async verifyAPIToken(token: string): Promise<JWTPayload> {
    try {
      const payload = jwt.verify(token, process.env.JWT_SECRET!, {
        algorithms: ['HS256'],
        issuer: 'smart-shelf-api',
        audience: 'smart-shelf-client'
      }) as JWTPayload
      
      // Check if token is revoked
      const isRevoked = await this.isTokenRevoked(payload.jti)
      if (isRevoked) {
        throw new Error('Token has been revoked')
      }
      
      return payload
    } catch (error) {
      throw new Error('Invalid or expired token')
    }
  }
  
  static async revokeToken(tokenId: string): Promise<void> {
    await supabase
      .from('revoked_tokens')
      .insert({
        token_id: tokenId,
        revoked_at: new Date().toISOString()
      })
  }
  
  private static async isTokenRevoked(tokenId: string): Promise<boolean> {
    const { data } = await supabase
      .from('revoked_tokens')
      .select('token_id')
      .eq('token_id', tokenId)
      .single()
    
    return !!data
  }
}

API Key Management

// lib/api/auth/api-keys.ts
export interface APIKey {
  id: string
  name: string
  key_hash: string
  permissions: string[]
  rate_limit: number
  expires_at?: string
  last_used_at?: string
  is_active: boolean
  created_by: string
}

export class APIKeyManager {
  
  static async generateAPIKey(
    name: string, 
    permissions: string[], 
    userId: string,
    options: { rateLimit?: number; expiresIn?: number } = {}
  ): Promise<{ id: string; key: string }> {
    const id = crypto.randomUUID()
    const key = `sk_${crypto.randomBytes(32).toString('hex')}`
    const keyHash = await this.hashAPIKey(key)
    
    const expiresAt = options.expiresIn 
      ? new Date(Date.now() + options.expiresIn).toISOString()
      : null
    
    await supabase
      .from('api_keys')
      .insert({
        id,
        name,
        key_hash: keyHash,
        permissions,
        rate_limit: options.rateLimit || 1000,
        expires_at: expiresAt,
        is_active: true,
        created_by: userId
      })
    
    return { id, key }
  }
  
  static async validateAPIKey(key: string): Promise<APIKey | null> {
    const keyHash = await this.hashAPIKey(key)
    
    const { data: apiKey } = await supabase
      .from('api_keys')
      .select('*')
      .eq('key_hash', keyHash)
      .eq('is_active', true)
      .single()
    
    if (!apiKey) {
      return null
    }
    
    // Check expiration
    if (apiKey.expires_at && new Date(apiKey.expires_at) < new Date()) {
      return null
    }
    
    // Update last used timestamp
    await supabase
      .from('api_keys')
      .update({ last_used_at: new Date().toISOString() })
      .eq('id', apiKey.id)
    
    return apiKey
  }
  
  private static async hashAPIKey(key: string): Promise<string> {
    const encoder = new TextEncoder()
    const data = encoder.encode(key)
    const hashBuffer = await crypto.subtle.digest('SHA-256', data)
    const hashArray = Array.from(new Uint8Array(hashBuffer))
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
  }
}

Input Validation & Sanitization

Comprehensive Input Validation

// lib/api/validation/input-validator.ts
import { z } from 'zod'
import DOMPurify from 'isomorphic-dompurify'

export class InputValidator {
  
  // Base validation schemas
  static readonly baseSchemas = {
    id: z.string().uuid('Invalid ID format'),
    email: z.string().email('Invalid email format').max(255),
    phone: z.string().regex(/^\+?[\d\s\-\(\)]+$/, 'Invalid phone format').max(20),
    name: z.string().min(1).max(255).regex(/^[a-zA-Z0-9\s\-_.]+$/, 'Invalid characters in name'),
    sku: z.string().min(1).max(100).regex(/^[A-Z0-9\-]+$/, 'SKU must contain only uppercase letters, numbers, and hyphens'),
    currency: z.number().positive('Amount must be positive').max(999999.99),
    url: z.string().url('Invalid URL format'),
    slug: z.string().regex(/^[a-z0-9\-]+$/, 'Invalid slug format')
  }
  
  // Product validation schema
  static readonly productSchema = z.object({
    name: this.baseSchemas.name,
    sku: this.baseSchemas.sku,
    description: z.string().max(2000).optional(),
    price: this.baseSchemas.currency,
    cost_price: this.baseSchemas.currency.optional(),
    category_id: this.baseSchemas.id,
    supplier_id: this.baseSchemas.id.optional(),
    barcode: z.string().regex(/^\d{8,14}$/, 'Invalid barcode format').optional(),
    weight: z.number().positive().optional(),
    dimensions: z.object({
      length: z.number().positive(),
      width: z.number().positive(),
      height: z.number().positive()
    }).optional(),
    tags: z.array(z.string().max(50)).max(10).optional(),
    images: z.array(this.baseSchemas.url).max(5).optional()
  })
  
  // User validation schema
  static readonly userSchema = z.object({
    email: this.baseSchemas.email,
    full_name: z.string().min(2).max(100).regex(/^[a-zA-Z\s\-'\.]+$/, 'Invalid name characters'),
    phone: this.baseSchemas.phone.optional(),
    role: z.enum(['admin', 'manager', 'employee', 'viewer']),
    department: z.string().min(1).max(100).optional(),
    avatar_url: this.baseSchemas.url.optional()
  })
  
  // Inventory movement validation
  static readonly inventoryMovementSchema = z.object({
    product_id: this.baseSchemas.id,
    warehouse_id: this.baseSchemas.id,
    quantity: z.number().int().min(-999999).max(999999),
    type: z.enum(['in', 'out', 'adjustment', 'transfer']),
    reason: z.string().min(1).max(255),
    reference_number: z.string().max(100).optional(),
    notes: z.string().max(1000).optional()
  })
  
  // Sanitize HTML content
  static sanitizeHTML(input: string): string {
    return DOMPurify.sanitize(input, {
      ALLOWED_TAGS: ['b', 'i', 'u', 'strong', 'em', 'p', 'br'],
      ALLOWED_ATTR: []
    })
  }
  
  // Sanitize user input
  static sanitizeInput(input: any): any {
    if (typeof input === 'string') {
      return input
        .trim()
        .replace(/[\x00-\x1F\x7F]/g, '') // Remove control characters
        .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // Remove scripts
        .slice(0, 10000) // Limit length
    }
    
    if (typeof input === 'object' && input !== null) {
      const sanitized: any = Array.isArray(input) ? [] : {}
      for (const key in input) {
        if (input.hasOwnProperty(key)) {
          sanitized[key] = this.sanitizeInput(input[key])
        }
      }
      return sanitized
    }
    
    return input
  }
  
  // Check for potential SQL injection patterns
  static containsSQLInjection(input: string): boolean {
    const sqlPatterns = [
      /(\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,
      /\/\*.*?\*\//,
      /--[^\r\n]*/,
      /;\s*(drop|delete|update|insert)/i
    ]
    
    return sqlPatterns.some(pattern => pattern.test(input))
  }
  
  // Check for XSS patterns
  static containsXSS(input: string): boolean {
    const xssPatterns = [
      /<script[^>]*>.*?<\/script>/i,
      /javascript:/i,
      /on\w+\s*=/i,
      /<iframe[^>]*>.*?<\/iframe>/i,
      /eval\s*\(/i,
      /expression\s*\(/i,
      /<object[^>]*>.*?<\/object>/i,
      /<embed[^>]*>.*?<\/embed>/i
    ]
    
    return xssPatterns.some(pattern => pattern.test(input))
  }
  
  // Validate and sanitize API input
  static async validateAPIInput(
    schema: z.ZodSchema,
    input: unknown
  ): Promise<{ success: true; data: any } | { success: false; errors: string[] }> {
    try {
      // First sanitize the input
      const sanitizedInput = this.sanitizeInput(input)
      
      // Check for injection attacks
      const stringified = JSON.stringify(sanitizedInput)
      if (this.containsSQLInjection(stringified)) {
        return { success: false, errors: ['Potential SQL injection detected'] }
      }
      
      if (this.containsXSS(stringified)) {
        return { success: false, errors: ['Potential XSS attack detected'] }
      }
      
      // Validate with schema
      const validatedData = schema.parse(sanitizedInput)
      
      return { success: true, data: validatedData }
    } catch (error) {
      if (error instanceof z.ZodError) {
        return {
          success: false,
          errors: error.errors.map(e => `${e.path.join('.')}: ${e.message}`)
        }
      }
      
      return { success: false, errors: ['Validation failed'] }
    }
  }
}

Request Validation Middleware

// lib/api/middleware/validation.ts
export function withValidation(
  schema: z.ZodSchema,
  options: { 
    validateQuery?: boolean
    validateBody?: boolean
    validateParams?: boolean
  } = { validateBody: true }
) {
  return function(handler: (req: NextRequest, context: any) => Promise<NextResponse>) {
    return async function(request: NextRequest, context: any): Promise<NextResponse> {
      try {
        const validationErrors: string[] = []
        
        // Validate request body
        if (options.validateBody && (request.method === 'POST' || request.method === 'PUT' || request.method === 'PATCH')) {
          try {
            const body = await request.json()
            const bodyValidation = await InputValidator.validateAPIInput(schema, body)
            
            if (!bodyValidation.success) {
              validationErrors.push(...bodyValidation.errors)
            } else {
              // Add validated data to request context
              context.validatedBody = bodyValidation.data
            }
          } catch (error) {
            validationErrors.push('Invalid JSON in request body')
          }
        }
        
        // Validate query parameters
        if (options.validateQuery) {
          const searchParams = Object.fromEntries(request.nextUrl.searchParams)
          const queryValidation = await InputValidator.validateAPIInput(schema, searchParams)
          
          if (!queryValidation.success) {
            validationErrors.push(...queryValidation.errors)
          } else {
            context.validatedQuery = queryValidation.data
          }
        }
        
        // Validate URL parameters
        if (options.validateParams && context.params) {
          const paramsValidation = await InputValidator.validateAPIInput(schema, context.params)
          
          if (!paramsValidation.success) {
            validationErrors.push(...paramsValidation.errors)
          } else {
            context.validatedParams = paramsValidation.data
          }
        }
        
        if (validationErrors.length > 0) {
          return NextResponse.json(
            {
              error: 'Validation failed',
              details: validationErrors
            },
            { status: 400 }
          )
        }
        
        return await handler(request, context)
        
      } catch (error) {
        console.error('Validation middleware error:', error)
        return NextResponse.json(
          { error: 'Internal validation error' },
          { status: 500 }
        )
      }
    }
  }
}

SQL Injection Prevention

Parameterized Queries with Supabase

// lib/api/database/safe-queries.ts
export class SafeQueryBuilder {
  
  // Safe product queries with proper parameterization
  static async getProducts(filters: {
    category_id?: string
    search?: string
    min_price?: number
    max_price?: number
    tags?: string[]
    limit?: number
    offset?: number
  }) {
    const supabase = createClient()
    
    let query = supabase
      .from('products')
      .select(`
        id,
        name,
        sku,
        description,
        price,
        category:categories(name),
        supplier:suppliers(name),
        inventory:inventory(quantity_on_hand)
      `)
    
    // Apply filters safely using Supabase's built-in parameterization
    if (filters.category_id) {
      query = query.eq('category_id', filters.category_id)
    }
    
    if (filters.search) {
      // Use full-text search to prevent injection
      query = query.textSearch('name', filters.search, {
        type: 'websearch',
        config: 'english'
      })
    }
    
    if (filters.min_price !== undefined) {
      query = query.gte('price', filters.min_price)
    }
    
    if (filters.max_price !== undefined) {
      query = query.lte('price', filters.max_price)
    }
    
    if (filters.tags && filters.tags.length > 0) {
      query = query.overlaps('tags', filters.tags)
    }
    
    // Apply pagination
    const limit = Math.min(filters.limit || 50, 100) // Cap at 100
    const offset = filters.offset || 0
    
    const { data, error, count } = await query
      .range(offset, offset + limit - 1)
      .order('created_at', { ascending: false })
    
    if (error) {
      throw new Error(`Database query failed: ${error.message}`)
    }
    
    return { data, count }
  }
  
  // Safe inventory operations
  static async createStockMovement(movement: {
    product_id: string
    warehouse_id: string
    quantity: number
    type: string
    reason: string
    reference_number?: string
    notes?: string
    created_by: string
  }) {
    const supabase = createClient()
    
    // Use database transaction for consistency
    const { data, error } = await supabase.rpc('create_stock_movement', {
      p_product_id: movement.product_id,
      p_warehouse_id: movement.warehouse_id,
      p_quantity: movement.quantity,
      p_movement_type: movement.type,
      p_reason: movement.reason,
      p_reference_number: movement.reference_number,
      p_notes: movement.notes,
      p_created_by: movement.created_by
    })
    
    if (error) {
      throw new Error(`Stock movement failed: ${error.message}`)
    }
    
    return data
  }
  
  // Safe user queries with proper access control
  static async getUsersByRole(role: string, requestingUserId: string) {
    const supabase = createClient()
    
    // First check if requesting user has permission
    const { data: requestingUser } = await supabase
      .from('users')
      .select('role')
      .eq('id', requestingUserId)
      .single()
    
    if (!requestingUser || !['admin', 'manager'].includes(requestingUser.role)) {
      throw new Error('Insufficient permissions')
    }
    
    // Use parameterized query
    const { data, error } = await supabase
      .from('users')
      .select('id, email, full_name, role, created_at, last_login')
      .eq('role', role)
      .eq('is_active', true)
      .order('created_at', { ascending: false })
    
    if (error) {
      throw new Error(`User query failed: ${error.message}`)
    }
    
    return data
  }
}

Database Function Security

-- Secure stored procedures with proper parameter validation
CREATE OR REPLACE FUNCTION create_stock_movement(
  p_product_id UUID,
  p_warehouse_id UUID,
  p_quantity INTEGER,
  p_movement_type TEXT,
  p_reason TEXT,
  p_reference_number TEXT DEFAULT NULL,
  p_notes TEXT DEFAULT NULL,
  p_created_by UUID
)
RETURNS JSON AS $$
DECLARE
  v_current_stock INTEGER;
  v_new_stock INTEGER;
  v_movement_id UUID;
  v_result JSON;
BEGIN
  -- Input validation
  IF p_product_id IS NULL OR p_warehouse_id IS NULL OR p_created_by IS NULL THEN
    RAISE EXCEPTION 'Required parameters cannot be null';
  END IF;
  
  IF p_quantity = 0 THEN
    RAISE EXCEPTION 'Quantity cannot be zero';
  END IF;
  
  IF p_movement_type NOT IN ('in', 'out', 'adjustment', 'transfer') THEN
    RAISE EXCEPTION 'Invalid movement type';
  END IF;
  
  IF length(p_reason) < 3 OR length(p_reason) > 255 THEN
    RAISE EXCEPTION 'Reason must be between 3 and 255 characters';
  END IF;
  
  -- Check if user has permission
  IF NOT EXISTS (
    SELECT 1 FROM users 
    WHERE id = p_created_by 
    AND role IN ('admin', 'manager', 'employee')
    AND is_active = true
  ) THEN
    RAISE EXCEPTION 'User does not have permission to create stock movements';
  END IF;
  
  -- Check if product and warehouse exist
  IF NOT EXISTS (SELECT 1 FROM products WHERE id = p_product_id) THEN
    RAISE EXCEPTION 'Product not found';
  END IF;
  
  IF NOT EXISTS (SELECT 1 FROM warehouses WHERE id = p_warehouse_id) THEN
    RAISE EXCEPTION 'Warehouse not found';
  END IF;
  
  -- Begin transaction
  BEGIN
    -- Get current stock
    SELECT COALESCE(quantity_on_hand, 0) INTO v_current_stock
    FROM inventory
    WHERE product_id = p_product_id AND warehouse_id = p_warehouse_id;
    
    -- Calculate new stock
    v_new_stock := v_current_stock + p_quantity;
    
    -- Prevent negative stock (except for adjustments)
    IF v_new_stock < 0 AND p_movement_type != 'adjustment' THEN
      RAISE EXCEPTION 'Insufficient stock. Current: %, Requested: %', v_current_stock, ABS(p_quantity);
    END IF;
    
    -- Generate movement ID
    v_movement_id := gen_random_uuid();
    
    -- Create stock movement record
    INSERT INTO stock_movements (
      id, product_id, warehouse_id, quantity, type, reason,
      reference_number, notes, created_by, created_at
    ) VALUES (
      v_movement_id, p_product_id, p_warehouse_id, p_quantity, p_movement_type,
      p_reason, p_reference_number, p_notes, p_created_by, NOW()
    );
    
    -- Update inventory
    INSERT INTO inventory (product_id, warehouse_id, quantity_on_hand, updated_at)
    VALUES (p_product_id, p_warehouse_id, v_new_stock, NOW())
    ON CONFLICT (product_id, warehouse_id)
    DO UPDATE SET 
      quantity_on_hand = v_new_stock,
      updated_at = NOW();
    
    -- Create audit log
    INSERT INTO audit_logs (
      table_name, record_id, action, new_values, user_id, created_at
    ) VALUES (
      'stock_movements', v_movement_id, 'INSERT',
      jsonb_build_object(
        'product_id', p_product_id,
        'warehouse_id', p_warehouse_id,
        'quantity', p_quantity,
        'type', p_movement_type,
        'previous_stock', v_current_stock,
        'new_stock', v_new_stock
      ),
      p_created_by, NOW()
    );
    
    -- Build result
    v_result := jsonb_build_object(
      'movement_id', v_movement_id,
      'previous_stock', v_current_stock,
      'new_stock', v_new_stock,
      'success', true
    );
    
    RETURN v_result;
    
  EXCEPTION
    WHEN OTHERS THEN
      -- Log the error
      INSERT INTO error_logs (
        error_message, error_context, created_at
      ) VALUES (
        SQLERRM, 
        jsonb_build_object(
          'function', 'create_stock_movement',
          'parameters', jsonb_build_object(
            'product_id', p_product_id,
            'warehouse_id', p_warehouse_id,
            'quantity', p_quantity,
            'type', p_movement_type
          )
        ),
        NOW()
      );
      
      RAISE;
  END;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

API Security Best Practices

Secure API Design Patterns

// lib/api/patterns/secure-api.ts
export class SecureAPIPatterns {
  
  // Pagination with security controls
  static validatePagination(query: URLSearchParams): { limit: number; offset: number } {
    const limit = Math.min(parseInt(query.get('limit') || '50'), 100) // Max 100 items
    const offset = Math.max(parseInt(query.get('offset') || '0'), 0) // Non-negative
    
    return { limit, offset }
  }
  
  // Filtering with whitelist approach
  static validateFilters(filters: any, allowedFields: string[]): Record<string, any> {
    const validFilters: Record<string, any> = {}
    
    for (const [key, value] of Object.entries(filters)) {
      if (allowedFields.includes(key) && value !== undefined && value !== '') {
        validFilters[key] = value
      }
    }
    
    return validFilters
  }
  
  // Sorting with security controls
  static validateSorting(sort: string, allowedFields: string[]): { field: string; direction: 'asc' | 'desc' } {
    const [field, direction = 'asc'] = sort.split(':')
    
    if (!allowedFields.includes(field)) {
      throw new Error(`Invalid sort field: ${field}`)
    }
    
    if (!['asc', 'desc'].includes(direction)) {
      throw new Error(`Invalid sort direction: ${direction}`)
    }
    
    return { field, direction: direction as 'asc' | 'desc' }
  }
  
  // Response data masking based on user permissions
  static maskResponseData(data: any, userRole: string, sensitiveFields: string[]): any {
    if (userRole === 'admin') {
      return data // Admins see everything
    }
    
    if (Array.isArray(data)) {
      return data.map(item => this.maskObject(item, userRole, sensitiveFields))
    }
    
    return this.maskObject(data, userRole, sensitiveFields)
  }
  
  private static maskObject(obj: any, userRole: string, sensitiveFields: string[]): any {
    if (!obj || typeof obj !== 'object') {
      return obj
    }
    
    const masked = { ...obj }
    
    sensitiveFields.forEach(field => {
      if (field in masked) {
        // Apply role-based masking
        if (userRole === 'viewer') {
          masked[field] = '***'
        } else if (userRole === 'employee' && field.includes('cost')) {
          masked[field] = '***'
        }
      }
    })
    
    return masked
  }
  
  // Error response standardization
  static createErrorResponse(
    error: string,
    details?: string[],
    statusCode: number = 400
  ): NextResponse {
    const response = {
      error,
      details: details || [],
      timestamp: new Date().toISOString(),
      reference: crypto.randomUUID()
    }
    
    // Don't expose sensitive error details in production
    if (process.env.NODE_ENV === 'production' && statusCode >= 500) {
      response.details = []
    }
    
    return NextResponse.json(response, { status: statusCode })
  }
  
  // Success response standardization
  static createSuccessResponse(
    data: any,
    meta?: { total?: number; page?: number; limit?: number }
  ): NextResponse {
    const response: any = {
      data,
      success: true,
      timestamp: new Date().toISOString()
    }
    
    if (meta) {
      response.meta = meta
    }
    
    return NextResponse.json(response)
  }
}

API Security Middleware Chain

// lib/api/middleware/security-chain.ts
export function createSecureAPIHandler(
  handler: (req: NextRequest, context: any) => Promise<NextResponse>,
  options: {
    requireAuth?: boolean
    requiredPermissions?: string[]
    rateLimiter?: Ratelimit
    validation?: {
      body?: z.ZodSchema
      query?: z.ZodSchema
      params?: z.ZodSchema
    }
    sensitiveFields?: string[]
  } = {}
) {
  return async function(request: NextRequest, context: any): Promise<NextResponse> {
    try {
      // 1. WAF Protection
      const wafCheck = await WebApplicationFirewall.analyzeRequest(request)
      if (wafCheck.blocked) {
        return SecureAPIPatterns.createErrorResponse(
          'Request blocked by security policy',
          wafCheck.reasons,
          403
        )
      }
      
      // 2. Rate Limiting
      if (options.rateLimiter) {
        const ip = request.headers.get('x-forwarded-for') || 'unknown'
        const rateLimitResult = await RateLimitManager.applyRateLimit(
          ip, 
          options.rateLimiter, 
          request
        )
        if (rateLimitResult) {
          return rateLimitResult
        }
      }
      
      // 3. Authentication
      let user = null
      if (options.requireAuth) {
        const authHeader = request.headers.get('authorization')
        if (!authHeader?.startsWith('Bearer ')) {
          return SecureAPIPatterns.createErrorResponse(
            'Authentication required',
            ['Missing or invalid authorization header'],
            401
          )
        }
        
        const token = authHeader.split(' ')[1]
        try {
          const payload = await APITokenManager.verifyAPIToken(token)
          user = { id: payload.sub, role: payload.role, permissions: payload.permissions }
          context.user = user
        } catch (error) {
          return SecureAPIPatterns.createErrorResponse(
            'Invalid token',
            [error.message],
            401
          )
        }
      }
      
      // 4. Authorization
      if (options.requiredPermissions && options.requiredPermissions.length > 0) {
        if (!user) {
          return SecureAPIPatterns.createErrorResponse(
            'Authentication required for this operation',
            [],
            401
          )
        }
        
        const hasPermission = options.requiredPermissions.every(
          permission => user.permissions.includes(permission)
        )
        
        if (!hasPermission) {
          return SecureAPIPatterns.createErrorResponse(
            'Insufficient permissions',
            [`Required: ${options.requiredPermissions.join(', ')}`],
            403
          )
        }
      }
      
      // 5. Input Validation
      if (options.validation) {
        if (options.validation.body) {
          const bodyValidation = withValidation(options.validation.body, { validateBody: true })
          const validationResult = await bodyValidation(handler)(request, context)
          if (validationResult.status === 400) {
            return validationResult
          }
        }
        
        if (options.validation.query) {
          const queryParams = Object.fromEntries(request.nextUrl.searchParams)
          const queryValidation = await InputValidator.validateAPIInput(
            options.validation.query,
            queryParams
          )
          if (!queryValidation.success) {
            return SecureAPIPatterns.createErrorResponse(
              'Invalid query parameters',
              queryValidation.errors,
              400
            )
          }
          context.validatedQuery = queryValidation.data
        }
      }
      
      // 6. Execute handler
      const response = await handler(request, context)
      
      // 7. Response processing
      if (response.status < 400 && options.sensitiveFields && user) {
        const responseData = await response.json()
        if (responseData.data) {
          responseData.data = SecureAPIPatterns.maskResponseData(
            responseData.data,
            user.role,
            options.sensitiveFields
          )
        }
        return NextResponse.json(responseData, { status: response.status })
      }
      
      return response
      
    } catch (error) {
      console.error('API Security Chain Error:', error)
      return SecureAPIPatterns.createErrorResponse(
        'Internal server error',
        [],
        500
      )
    }
  }
}

This comprehensive API security framework ensures that all Smart Shelf APIs are protected against common attack vectors while maintaining performance and developer experience.