{"version":3,"file":"csrf-protection.mjs","sources":["../../../src/lib/security/csrf-protection.ts"],"sourcesContent":["/**\n * @fileoverview CSRF Protection System\n * @module @/lib/security/csrf-protection\n *\n * Comprehensive Cross-Site Request Forgery (CSRF) protection for the\n * Harbor React Framework. Implements multiple protection strategies\n * including Synchronizer Token Pattern, Double-Submit Cookie Pattern,\n * and Origin validation.\n *\n * @see https://owasp.org/www-community/attacks/csrf\n * @see https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html\n *\n * @author Harbor Security Team\n * @version 1.0.0\n */\n\nimport type React from 'react';\nimport type { CSRFConfig, CSRFRequestInterceptor, CSRFToken, CSRFValidationResult, ValidatedCSRFToken, } from './types';\nimport { csrfConfig, isAllowedOrigin, isCSRFExcludedPath, requiresCSRFProtection, } from '@/config/security.config';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * Token entropy in bytes (256 bits)\n */\nconst TOKEN_ENTROPY_BYTES = 32;\n\n// ============================================================================\n// Cryptographic Utilities\n// ============================================================================\n\n/**\n * Generate cryptographically secure random bytes\n */\nfunction getRandomBytes(length: number): Uint8Array {\n  const bytes = new Uint8Array(length);\n  crypto.getRandomValues(bytes);\n  return bytes;\n}\n\n/**\n * Convert bytes to base64url string (URL-safe base64)\n */\nfunction bytesToBase64Url(bytes: Uint8Array): string {\n  const binString = Array.from(bytes, (byte) =>\n    String.fromCharCode(byte)\n  ).join('');\n  return btoa(binString)\n    .replace(/\\+/g, '-')\n    .replace(/\\//g, '_')\n    .replace(/=+$/, '');\n}\n\n/**\n * Generate a cryptographically secure CSRF token\n */\nfunction generateTokenValue(): string {\n  const bytes = getRandomBytes(TOKEN_ENTROPY_BYTES);\n  return bytesToBase64Url(bytes);\n}\n\n/**\n * Constant-time string comparison to prevent timing attacks\n */\nfunction constantTimeCompare(a: string, b: string): boolean {\n  if (a.length !== b.length) {\n    return false;\n  }\n\n  let result = 0;\n  for (let i = 0; i < a.length; i++) {\n    result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n  }\n  return result === 0;\n}\n\n// ============================================================================\n// Cookie Utilities\n// ============================================================================\n\n/**\n * Get a cookie value by name\n */\nfunction getCookie(name: string): string | null {\n  if (typeof document === 'undefined') {\n    return null;\n  }\n\n  const value = `; ${document.cookie}`;\n  const parts = value.split(`; ${name}=`);\n\n  if (parts.length === 2) {\n    const cookieValue = parts.pop()?.split(';').shift();\n    return cookieValue ?? null;\n  }\n\n  return null;\n}\n\n/**\n * Set a cookie with security options\n */\nfunction setCookie(\n  name: string,\n  value: string,\n  options: {\n    maxAge?: number;\n    path?: string;\n    secure?: boolean;\n    sameSite?: 'strict' | 'lax' | 'none';\n    httpOnly?: boolean;\n  } = {}\n): void {\n  if (typeof document === 'undefined') {\n    return;\n  }\n\n  const {\n    maxAge = 86400, // 1 day\n    path = '/',\n    secure = true,\n    sameSite = 'strict',\n  } = options;\n\n  let cookie = `${name}=${value}; Path=${path}`;\n\n  if (maxAge) {\n    cookie += `; Max-Age=${maxAge}`;\n  }\n\n  if (secure) {\n    cookie += '; Secure';\n  }\n\n  cookie += `; SameSite=${sameSite}`;\n\n  // Note: httpOnly cannot be set from JavaScript\n  // Server must set the cookie with httpOnly flag\n\n  document.cookie = cookie;\n}\n\n/**\n * Delete a cookie\n */\nfunction deleteCookie(name: string, path = '/'): void {\n  if (typeof document === 'undefined') {\n    return;\n  }\n\n  document.cookie = `${name}=; Path=${path}; Expires=Thu, 01 Jan 1970 00:00:00 GMT`;\n}\n\n// ============================================================================\n// CSRF Protection Manager\n// ============================================================================\n\n/**\n * CSRF Protection Manager class\n *\n * Provides comprehensive CSRF protection including:\n * - Token generation and validation\n * - Double-submit cookie pattern implementation\n * - Origin/Referer header validation\n * - Automatic request interceptor for fetch/XHR\n *\n * @example\n * ```typescript\n * const csrf = CSRFProtection.getInstance();\n *\n * // Get token for form submission\n * const token = csrf.getToken();\n *\n * // Validate an incoming token\n * const result = csrf.validateToken(requestToken);\n *\n * // Create request interceptor\n * const interceptor = csrf.createRequestInterceptor();\n * ```\n */\nclass CSRFProtectionClass {\n  private static instance: CSRFProtectionClass | null = null;\n\n  private config: CSRFConfig;\n  private currentToken: CSRFToken | null = null;\n  private initialized = false;\n\n  private constructor(config: CSRFConfig = csrfConfig) {\n    this.config = { ...config };\n  }\n\n  /**\n   * Get singleton instance\n   */\n  static getInstance(config?: CSRFConfig): CSRFProtectionClass {\n    CSRFProtectionClass.instance ??= new CSRFProtectionClass(config);\n    return CSRFProtectionClass.instance;\n  }\n\n  /**\n   * Reset singleton instance (for testing)\n   */\n  static resetInstance(): void {\n    if (CSRFProtectionClass.instance) {\n      CSRFProtectionClass.instance.cleanup();\n      CSRFProtectionClass.instance = null;\n    }\n  }\n\n  // ==========================================================================\n  // Initialization\n  // ==========================================================================\n\n  /**\n   * Initialize CSRF protection\n   * Generates initial token and sets up cookie if using double-submit pattern\n   */\n  initialize(): void {\n    if (this.initialized) {\n      return;\n    }\n\n    // Try to load existing token from cookie\n    const existingToken = this.loadTokenFromCookie();\n\n    if (existingToken != null && this.isTokenValid(existingToken)) {\n      this.currentToken = existingToken;\n    } else {\n      // Generate new token\n      this.regenerateToken();\n    }\n\n    this.initialized = true;\n  }\n\n  /**\n   * Cleanup resources\n   */\n  cleanup(): void {\n    if (this.config.mode === 'double-submit-cookie') {\n      deleteCookie(this.config.cookieName);\n    }\n    this.currentToken = null;\n    this.initialized = false;\n  }\n\n  // ==========================================================================\n  // Token Management\n  // ==========================================================================\n\n  /**\n   * Generate a new CSRF token\n   */\n  regenerateToken(): ValidatedCSRFToken {\n    const token: CSRFToken = {\n      token: generateTokenValue(),\n      createdAt: Date.now(),\n      ttl: this.config.tokenTtl,\n      used: false,\n    };\n\n    this.currentToken = token;\n\n    // Set cookie for double-submit pattern\n    if (this.config.mode === 'double-submit-cookie') {\n      this.setTokenCookie(token);\n    }\n\n    return token.token as ValidatedCSRFToken;\n  }\n\n  /**\n   * Get the current CSRF token\n   * Regenerates if expired\n   */\n  getToken(): ValidatedCSRFToken {\n    if (this.currentToken == null || !this.isTokenValid(this.currentToken)) {\n      return this.regenerateToken();\n    }\n\n    return this.currentToken.token as ValidatedCSRFToken;\n  }\n\n  /**\n   * Get the current token synchronously (may be stale)\n   */\n  getTokenSync(): string {\n    return this.currentToken?.token ?? '';\n  }\n\n  /**\n   * Validate a CSRF token from a request\n   */\n  validateToken(requestToken: string): CSRFValidationResult {\n    // Check if we have a current token\n    if (!this.currentToken) {\n      return {\n        isValid: false,\n        reason: 'No active CSRF token',\n        shouldRotate: true,\n      };\n    }\n\n    // Check token expiration\n    if (!this.isTokenValid(this.currentToken)) {\n      return {\n        isValid: false,\n        reason: 'CSRF token expired',\n        shouldRotate: true,\n      };\n    }\n\n    // Constant-time comparison to prevent timing attacks\n    const isMatch = constantTimeCompare(requestToken, this.currentToken.token);\n\n    if (!isMatch) {\n      return {\n        isValid: false,\n        reason: 'CSRF token mismatch',\n        token: requestToken,\n        shouldRotate: false,\n      };\n    }\n\n    // Mark token as used\n    if (this.config.rotateAfterUse) {\n      this.currentToken.used = true;\n    }\n\n    return {\n      isValid: true,\n      token: requestToken,\n      shouldRotate: this.config.rotateAfterUse,\n    };\n  }\n\n  /**\n   * Validate double-submit cookie pattern\n   */\n  validateDoubleSubmit(\n    headerToken: string,\n    cookieToken?: string\n  ): CSRFValidationResult {\n    // Get cookie token if not provided\n    const cookie = cookieToken ?? getCookie(this.config.cookieName);\n\n    if (cookie === null || cookie === undefined || cookie === '') {\n      return {\n        isValid: false,\n        reason: 'Missing CSRF cookie',\n        shouldRotate: true,\n      };\n    }\n\n    // Parse cookie value (token:timestamp format)\n    const [expectedToken] = cookie.split(':');\n\n    if (expectedToken === undefined || expectedToken === null || expectedToken === '') {\n      return {\n        isValid: false,\n        reason: 'Invalid CSRF cookie format',\n        shouldRotate: true,\n      };\n    }\n\n    // Compare header token with cookie token\n    const isMatch = constantTimeCompare(headerToken, expectedToken);\n\n    if (!isMatch) {\n      return {\n        isValid: false,\n        reason: 'CSRF header/cookie mismatch',\n        token: headerToken,\n        shouldRotate: false,\n      };\n    }\n\n    return {\n      isValid: true,\n      token: headerToken,\n      shouldRotate: this.config.rotateAfterUse,\n    };\n  }\n\n  /**\n   * Validate Origin/Referer header\n   */\n  validateOrigin(\n    origin: string | null,\n    referer: string | null\n  ): CSRFValidationResult {\n    if (!this.config.validateOrigin) {\n      return { isValid: true, shouldRotate: false };\n    }\n\n    // Get the origin to validate\n    let requestOrigin = origin;\n\n    if ((requestOrigin === null || requestOrigin === undefined || requestOrigin === '') && (referer !== null && referer !== undefined && referer !== '')) {\n      try {\n        const url = new URL(referer);\n        requestOrigin = url.origin;\n      } catch {\n        requestOrigin = null;\n      }\n    }\n\n    // If no origin available, fail closed (secure default)\n    if (requestOrigin === null || requestOrigin === undefined || requestOrigin === '') {\n      return {\n        isValid: false,\n        reason: 'Missing Origin/Referer header',\n        shouldRotate: false,\n      };\n    }\n\n    // Check against allowed origins\n    if (!isAllowedOrigin(requestOrigin)) {\n      return {\n        isValid: false,\n        reason: `Origin not allowed: ${requestOrigin}`,\n        shouldRotate: false,\n      };\n    }\n\n    return { isValid: true, shouldRotate: false };\n  }\n\n  // ==========================================================================\n  // Token Validation\n  // ==========================================================================\n\n  /**\n   * Create a request interceptor that adds CSRF token to requests\n   */\n  createRequestInterceptor(): CSRFRequestInterceptor {\n    return async (config: RequestInit): Promise<RequestInit> => {\n      // Get the method\n      const method = (config.method ?? 'GET').toUpperCase();\n\n      // Skip if method doesn't require CSRF protection\n      if (!requiresCSRFProtection(method)) {\n        return Promise.resolve(config);\n      }\n\n      // Get current token\n      const token = this.getToken();\n\n      // Add token to headers\n      const headers = new Headers(config.headers);\n      headers.set(this.config.headerName, token);\n\n      return Promise.resolve({\n        ...config,\n        headers,\n      });\n    };\n  }\n\n  /**\n   * Create a fetch wrapper with CSRF protection\n   *\n   * Note: This method intentionally returns a raw fetch wrapper because:\n   * 1. This IS the CSRF security layer that wraps fetch\n   * 2. It provides a drop-in fetch replacement with CSRF token injection\n   * 3. The apiClient uses this wrapper internally for CSRF protection\n   *\n   * For application API calls, prefer using apiClient which has CSRF built-in.\n   * @see {@link @/lib/api/api-client} for the recommended API client\n   */\n  createSecureFetch(): typeof fetch {\n    const interceptor = this.createRequestInterceptor();\n\n    return async (\n      input: RequestInfo | URL,\n      init?: RequestInit\n    ): Promise<Response> => {\n      // Get the URL for path checking\n      let url: string;\n      if (typeof input === 'string') {\n        url = input;\n      } else if (input instanceof URL) {\n        url = input.pathname;\n      } else {\n        const { url: requestUrl } = input;\n        url = requestUrl;\n      }\n\n      // Skip CSRF for excluded paths\n      // Raw fetch is intentional - this IS the CSRF wrapper\n      try {\n        const {pathname} = new URL(url, window.location.origin);\n        if (isCSRFExcludedPath(pathname)) {\n          return fetch(input, init);\n        }\n      } catch {\n        // If URL parsing fails, apply CSRF protection anyway\n      }\n\n      // Apply interceptor\n      const interceptedInit = await interceptor(init ?? {});\n\n      // Raw fetch is intentional - this wrapper applies CSRF tokens\n      return fetch(input, interceptedInit);\n    };\n  }\n\n  /**\n   * Get hidden input props for forms\n   */\n  getFormInputProps(): {\n    type: 'hidden';\n    name: string;\n    value: string;\n  } {\n    return {\n      type: 'hidden',\n      name: this.config.fieldName,\n      value: this.getTokenSync(),\n    };\n  }\n\n  // ==========================================================================\n  // Request Interception\n  // ==========================================================================\n\n  /**\n   * Get headers object with CSRF token\n   */\n  async getHeaders(): Promise<Record<string, string>> {\n    const token = this.getToken();\n    return Promise.resolve({\n      [this.config.headerName]: token,\n    });\n  }\n\n  /**\n   * Get headers synchronously (may have stale token)\n   */\n  getHeadersSync(): Record<string, string> {\n    return {\n      [this.config.headerName]: this.getTokenSync(),\n    };\n  }\n\n  // ==========================================================================\n  // Form Helpers\n  // ==========================================================================\n\n  /**\n   * Get current configuration\n   */\n  getConfig(): Readonly<CSRFConfig> {\n    return { ...this.config };\n  }\n\n  /**\n   * Update configuration\n   */\n  updateConfig(updates: Partial<CSRFConfig>): void {\n    this.config = {\n      ...this.config,\n      ...updates,\n    };\n  }\n\n  /**\n   * Check if CSRF protection is initialized\n   */\n  isInitialized(): boolean {\n    return this.initialized;\n  }\n\n  // ==========================================================================\n  // Configuration\n  // ==========================================================================\n\n  /**\n   * Get header name for CSRF token\n   */\n  getHeaderName(): string {\n    return this.config.headerName;\n  }\n\n  /**\n   * Get field name for CSRF token in forms\n   */\n  getFieldName(): string {\n    return this.config.fieldName;\n  }\n\n  /**\n   * Check if a token is valid (not expired)\n   */\n  private isTokenValid(token: CSRFToken): boolean {\n    const age = Date.now() - token.createdAt;\n    return age < token.ttl;\n  }\n\n  /**\n   * Load token from cookie (for double-submit pattern)\n   */\n  private loadTokenFromCookie(): CSRFToken | null {\n    if (this.config.mode !== 'double-submit-cookie') {\n      return null;\n    }\n\n    const cookieValue = getCookie(this.config.cookieName);\n    if (cookieValue == null || cookieValue === '') {\n      return null;\n    }\n\n    try {\n      // Cookie contains token:timestamp format\n      const [token, timestampStr] = cookieValue.split(':');\n      const timestamp = parseInt(timestampStr ?? '0', 10);\n\n      if ((token == null || token === '') || isNaN(timestamp)) {\n        return null;\n      }\n\n      return {\n        token,\n        createdAt: timestamp,\n        ttl: this.config.tokenTtl,\n        used: false,\n      };\n    } catch {\n      return null;\n    }\n  }\n\n  /**\n   * Set token cookie for double-submit pattern\n   */\n  private setTokenCookie(token: CSRFToken): void {\n    const cookieValue = `${token.token}:${token.createdAt}`;\n    const maxAge = Math.floor(token.ttl / 1000);\n\n    setCookie(this.config.cookieName, cookieValue, {\n      maxAge,\n      secure: this.config.secureCookies,\n      sameSite: this.config.sameSite,\n      path: '/',\n    });\n  }\n}\n\n// ============================================================================\n// Singleton Export\n// ============================================================================\n\n/**\n * CSRF Protection singleton instance\n */\nexport const CSRFProtection = CSRFProtectionClass.getInstance();\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Generate a one-time CSRF token\n * For scenarios where token rotation is required\n */\nexport function generateOneTimeToken(): CSRFToken {\n  return {\n    token: generateTokenValue(),\n    createdAt: Date.now(),\n    ttl: csrfConfig.tokenTtl,\n    used: false,\n  };\n}\n\n/**\n * Create a request init object with CSRF token\n */\nexport async function createSecureRequestInit(\n  method: string,\n  body?: BodyInit | null,\n  additionalHeaders?: Record<string, string>\n): Promise<RequestInit> {\n  const headers: Record<string, string> = {\n    ...additionalHeaders,\n  };\n\n  // Add CSRF token for protected methods\n  if (requiresCSRFProtection(method)) {\n\n    headers[csrfConfig.headerName] = CSRFProtection.getToken();\n  }\n\n  return Promise.resolve({\n    method,\n    headers,\n    body,\n    credentials: 'same-origin',\n  });\n}\n\n/**\n * Create a protected form action handler\n */\nexport function createProtectedFormHandler(\n  action: (formData: FormData) => Promise<void>\n): (event: React.FormEvent<HTMLFormElement>) => Promise<void> {\n  return async (event: React.FormEvent<HTMLFormElement>) => {\n    event.preventDefault();\n\n    const form = event.currentTarget;\n    const formData = new FormData(form);\n\n    // Validate CSRF token from form\n    const token = formData.get(csrfConfig.fieldName);\n\n    if (typeof token !== 'string') {\n      throw new Error('Missing CSRF token in form');\n    }\n\n    const validation = CSRFProtection.validateToken(token);\n\n    if (!validation.isValid) {\n      throw new Error(`CSRF validation failed: ${validation.reason}`);\n    }\n\n    // Token is valid, proceed with action\n    await action(formData);\n\n    // Rotate token if needed\n    if (validation.shouldRotate) {\n      CSRFProtection.regenerateToken();\n    }\n  };\n}\n\n/**\n * Validate a request for CSRF protection\n * Comprehensive validation using configured mode\n */\nexport function validateRequest(\n  request: {\n    method: string;\n    headers: Headers | Record<string, string>;\n    url: string;\n  }\n): CSRFValidationResult {\n  const { method, headers, url } = request;\n\n  // Skip non-protected methods\n  if (!requiresCSRFProtection(method)) {\n    return { isValid: true, shouldRotate: false };\n  }\n\n  // Skip excluded paths\n  try {\n    const {pathname} = new URL(url, window.location.origin);\n    if (isCSRFExcludedPath(pathname)) {\n      return { isValid: true, shouldRotate: false };\n    }\n  } catch {\n    // If URL parsing fails, continue with validation\n  }\n\n  const config = CSRFProtection.getConfig();\n  const headerValue =\n    headers instanceof Headers\n      ? headers.get(config.headerName)\n      : headers[config.headerName];\n\n  switch (config.mode) {\n    case 'synchronizer-token':\n      if (headerValue === null || headerValue === undefined || headerValue === '') {\n        return {\n          isValid: false,\n          reason: `Missing ${config.headerName} header`,\n          shouldRotate: false,\n        };\n      }\n      return CSRFProtection.validateToken(headerValue);\n\n    case 'double-submit-cookie':\n      if (headerValue === null || headerValue === undefined || headerValue === '') {\n        return {\n          isValid: false,\n          reason: `Missing ${config.headerName} header`,\n          shouldRotate: false,\n        };\n      }\n      return CSRFProtection.validateDoubleSubmit(headerValue);\n\n    case 'origin-check': {\n      const origin = headers instanceof Headers\n        ? headers.get('Origin')\n        : headers['Origin'];\n      const referer = headers instanceof Headers\n        ? headers.get('Referer')\n        : headers['Referer'];\n      return CSRFProtection.validateOrigin(origin ?? null, referer ?? null);\n    }\n\n    case 'custom-header':\n      // Just check that the custom header exists\n      if (headerValue === null || headerValue === undefined || headerValue === '') {\n        return {\n          isValid: false,\n          reason: `Missing required header: ${config.headerName}`,\n          shouldRotate: false,\n        };\n      }\n      return { isValid: true, shouldRotate: false };\n\n    default:\n      return { isValid: false, reason: 'Unknown CSRF mode', shouldRotate: false };\n  }\n}\n\n// ============================================================================\n// React Utilities\n// ============================================================================\n\n/**\n * Create CSRF hidden input element props\n */\nexport async function createCSRFInputProps(): Promise<{\n  type: 'hidden';\n  name: string;\n  value: string;\n}> {\n  const token = CSRFProtection.getToken();\n  return Promise.resolve({\n    type: 'hidden',\n    name: csrfConfig.fieldName,\n    value: token,\n  });\n}\n\n/**\n * Create meta tag props for CSRF token\n * Useful for JavaScript access to token\n */\nexport async function createCSRFMetaProps(): Promise<{\n  name: string;\n  content: string;\n}> {\n  const token = CSRFProtection.getToken();\n  return Promise.resolve({\n    name: 'csrf-token',\n    content: token,\n  });\n}\n\n// ============================================================================\n// Type Exports\n// ============================================================================\n\nexport type { CSRFProtectionClass };\nexport type {\n  CSRFToken,\n  CSRFConfig,\n  CSRFValidationResult,\n  CSRFRequestInterceptor,\n  ValidatedCSRFToken,\n};\n"],"names":["TOKEN_ENTROPY_BYTES","getRandomBytes","length","bytes","bytesToBase64Url","binString","byte","generateTokenValue","constantTimeCompare","a","b","result","i","getCookie","name","parts","setCookie","value","options","maxAge","path","secure","sameSite","cookie","deleteCookie","CSRFProtectionClass","config","csrfConfig","existingToken","token","requestToken","headerToken","cookieToken","expectedToken","origin","referer","requestOrigin","isAllowedOrigin","method","requiresCSRFProtection","headers","interceptor","input","init","url","requestUrl","pathname","isCSRFExcludedPath","interceptedInit","updates","cookieValue","timestampStr","timestamp","CSRFProtection","generateOneTimeToken","createSecureRequestInit","body","additionalHeaders","createProtectedFormHandler","action","event","form","formData","validation","validateRequest","request","headerValue","createCSRFInputProps","createCSRFMetaProps"],"mappings":"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"}