{"version":3,"file":"csrf.mjs","names":[],"sources":["../src/csrf.ts"],"sourcesContent":["import {\n  CSRF_STRATEGY_HEADER,\n  CSRF_TOKEN_HEADER,\n  DEFAULT_CONFIG,\n  DEFAULT_NONCE_LENGTH,\n  ORIGIN_CHECK_NONCE_LENGTH,\n  SAFE_METHODS,\n  SERVER_CSRF_COOKIE_SUFFIX,\n} from './constants.js';\nimport {\n  generateNonce,\n  generateSecureSecret,\n  generateSignedToken,\n  parseSignedToken,\n  signUnsignedToken,\n  timingSafeEqual,\n  verifySignedToken,\n} from './crypto.js';\nimport type {\n  CsrfAdapter,\n  CsrfConfig,\n  CsrfRequest,\n  CsrfResponse,\n  RequiredCookieOptions,\n  RequiredCsrfConfig,\n} from './types.js';\nimport { validateRequest } from './validation.js';\n\n/**\n * Extracts the pathname from a URL string for path-based exclusion matching.\n *\n * Handles both absolute URLs and relative paths safely. If URL parsing fails,\n * falls back to manual parsing by finding the query string delimiter.\n *\n * @param url - URL string to extract pathname from\n * @returns The pathname portion of the URL\n *\n * @internal\n */\nfunction extractPathname(url: string): string {\n  try {\n    // Always return the full pathname for accurate excludePaths matching\n    return new URL(url).pathname;\n  } catch {\n    const questionMarkIndex = url.indexOf('?');\n    if (questionMarkIndex !== -1) {\n      return url.substring(0, questionMarkIndex);\n    }\n    return url;\n  }\n}\n\n/**\n * Normalizes request headers to a consistent Map format.\n *\n * Converts various header formats (Map, Headers object, plain object) into\n * a standardized Map<string, string> for consistent processing throughout\n * the CSRF protection system.\n *\n * @param rawHeaders - Headers in various formats from the request\n * @returns Normalized headers as a Map\n *\n * @internal\n */\nfunction processHeaders(\n  rawHeaders: CsrfRequest['headers']\n): Map<string, string> {\n  if (rawHeaders instanceof Map) {\n    return rawHeaders;\n  }\n\n  return new Map(Object.entries(rawHeaders));\n}\n\n/**\n * Merges user configuration with default CSRF configuration values.\n *\n * Creates a complete configuration object by combining user-provided options\n * with secure defaults. Ensures all required fields are present and properly\n * typed for the CSRF protection system.\n *\n * @param defaultConfig - Default configuration values\n * @param userConfig - User-provided configuration overrides\n * @returns Complete CSRF configuration with all required fields\n *\n * @internal\n */\nfunction mergeConfig(\n  defaultConfig: CsrfConfig,\n  userConfig?: CsrfConfig\n): RequiredCsrfConfig {\n  const merged = {\n    ...defaultConfig,\n    ...userConfig,\n    cookie: {\n      ...defaultConfig.cookie,\n      ...userConfig?.cookie,\n    },\n    token: {\n      ...defaultConfig.token,\n      ...userConfig?.token,\n    },\n  };\n\n  // Ensure all required properties are present\n  const config: RequiredCsrfConfig = {\n    strategy: userConfig?.strategy ?? defaultConfig.strategy ?? 'hybrid',\n    secret:\n      userConfig?.secret ?? defaultConfig.secret ?? generateSecureSecret(),\n    token: {\n      expiry: merged.token?.expiry ?? 3600,\n      headerName: merged.token?.headerName ?? 'X-CSRF-Token',\n      fieldName: merged.token?.fieldName ?? 'csrf_token',\n      reissueThreshold: merged.token?.reissueThreshold ?? 300,\n    },\n    cookie: {\n      name: merged.cookie?.name ?? 'csrf-token',\n      secure: merged.cookie?.secure ?? true,\n      httpOnly: merged.cookie?.httpOnly ?? false,\n      sameSite: merged.cookie?.sameSite ?? 'lax',\n      path: merged.cookie?.path ?? '/',\n    },\n    allowedOrigins: merged.allowedOrigins ?? [],\n    excludePaths: merged.excludePaths ?? [],\n    skipContentTypes: merged.skipContentTypes ?? [],\n  };\n\n  // Add optional properties if they exist\n  if (merged.cookie?.domain) {\n    config.cookie.domain = merged.cookie.domain;\n  }\n  if (merged.cookie?.maxAge) {\n    config.cookie.maxAge = merged.cookie.maxAge;\n  }\n\n  return config;\n}\n\n/**\n * Core CSRF protection engine that provides framework-agnostic CSRF security.\n *\n * This class implements multiple CSRF protection strategies and works with\n * framework-specific adapters to provide comprehensive protection against\n * Cross-Site Request Forgery attacks. It supports various strategies including\n * double-submit cookies, signed tokens, origin validation, and hybrid approaches.\n *\n * **Features:**\n * - Multiple CSRF protection strategies (double-submit, signed-double-submit, etc.)\n * - Framework-agnostic design with adapter pattern\n * - Configurable token expiration and validation\n * - Origin-based validation with whitelist support\n * - Path exclusion for public endpoints\n * - Content-type based skipping for certain request types\n * - Secure cryptographic operations using Web Crypto API\n * - Timing-safe token comparisons to prevent timing attacks\n *\n * **Available Strategies:**\n * - `double-submit`: Classic double-submit cookie pattern\n * - `signed-double-submit`: Enhanced double-submit with cryptographic signatures\n * - `signed-token`: Server-side token validation with signing\n * - `origin-check`: Validates request origin against allowed domains\n * - `hybrid`: Combines multiple strategies for maximum security\n *\n * @template TRequest - Framework-specific request type\n * @template TResponse - Framework-specific response type\n * @public\n *\n * @example\n * ```typescript\n * import { CsrfProtection } from '@csrf-armor/core';\n * import { ExpressAdapter } from '@csrf-armor/express';\n *\n * // Create CSRF protection with Express adapter\n * const csrf = new CsrfProtection(new ExpressAdapter(), {\n *   strategy: 'signed-double-submit',\n *   secret: 'your-secret-key',\n *   token: {\n *     expiry: 3600, // 1 hour\n *     headerName: 'X-CSRF-Token',\n *     fieldName: 'csrf_token'\n *   },\n *   cookie: {\n *     name: 'csrf-token',\n *     secure: true,\n *     httpOnly: false,\n *     sameSite: 'strict'\n *   },\n *   allowedOrigins: ['https://yourdomain.com'],\n *   excludePaths: ['/api/public', '/health']\n * });\n *\n * // Use in middleware\n * app.use(async (req, res, next) => {\n *   try {\n *     const result = await csrf.protect(req, res);\n *     if (result.success) {\n *       req.csrfToken = result.token;\n *       next();\n *     } else {\n *       res.status(403).json({ error: result.reason });\n *     }\n *   } catch (error) {\n *     next(error);\n *   }\n * });\n * ```\n * // Basic setup with Express\n * import { CsrfProtection } from '@csrf-armor/core';\n * import { ExpressAdapter } from '@csrf-armor/express';\n *\n * const csrf = new CsrfProtection(new ExpressAdapter(), {\n *   secret: 'your-32-character-secret-key-here',\n *   strategy: 'signed-double-submit',\n *   allowedOrigins: ['https://yourdomain.com'],\n *   excludePaths: ['/api/public']\n * });\n *\n * // In middleware\n * app.use(async (req, res, next) => {\n *   const result = await csrf.protect(req, res);\n *   if (!result.success) {\n *     return res.status(403).json({ error: 'CSRF validation failed' });\n *   }\n *   next();\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Advanced configuration\n * const csrf = new CsrfProtection(adapter, {\n *   strategy: 'hybrid',\n *   secret: process.env.CSRF_SECRET,\n *   token: {\n *     expiry: 7200, // 2 hours\n *     headerName: 'X-Custom-CSRF-Token',\n *     fieldName: 'custom_csrf_token'\n *   },\n *   cookie: {\n *     name: 'custom-csrf',\n *     secure: true,\n *     httpOnly: true,\n *     sameSite: 'strict'\n *   },\n *   excludePaths: ['/health', '/api/webhook'],\n *   skipContentTypes: ['application/json']\n * });\n * ```\n */\n\n/**\n * Interface for token data used throughout the CSRF protection process.\n * @internal\n */\ninterface TokenData {\n  clientToken: string;\n  cookieToken: string;\n  serverCookieToken?: string;\n  cookieOptions: RequiredCookieOptions;\n}\n\nexport class CsrfProtection<TRequest = unknown, TResponse = unknown> {\n  private readonly config: RequiredCsrfConfig;\n  private readonly adapter: CsrfAdapter<TRequest, TResponse>;\n\n  /**\n   * Creates a new CSRF protection instance.\n   *\n   * @param adapter - Framework-specific adapter for request/response handling\n   * @param userConfig - Optional configuration overrides\n   */\n  constructor(\n    adapter: CsrfAdapter<TRequest, TResponse>,\n    userConfig?: CsrfConfig\n  ) {\n    this.adapter = adapter;\n    this.config = mergeConfig(DEFAULT_CONFIG, userConfig);\n  }\n\n  /**\n   * Checks if a request should be excluded from CSRF protection.\n   *\n   * @param request - The CSRF request to check\n   * @returns true if the request should be skipped\n   * @internal\n   */\n  private shouldSkipProtection(request: CsrfRequest): boolean {\n    const pathname = extractPathname(request.url);\n    if (this.config.excludePaths.some((path) => pathname.startsWith(path))) {\n      return true;\n    }\n\n    const headers = processHeaders(request.headers);\n    const contentType = headers.get('content-type') ?? '';\n    return this.config.skipContentTypes.some((type) =>\n      contentType.includes(type)\n    );\n  }\n\n  /**\n   * Attempts to reuse existing CSRF tokens if they are still valid.\n   *\n   * @param request - The CSRF request containing potential existing tokens\n   * @returns Token data if reuse is possible, null otherwise\n   * @internal\n   */\n  private async attemptTokenReuse(\n    request: CsrfRequest\n  ): Promise<TokenData | null> {\n    if (!SAFE_METHODS.includes(request.method as never)) {\n      return null;\n    }\n\n    const cookies =\n      request.cookies instanceof Map\n        ? request.cookies\n        : new Map(Object.entries(request.cookies));\n\n    const clientTokenFromRequest = cookies.get(this.config.cookie.name);\n    const serverCookieTokenFromRequest = cookies.get(\n      this.config.cookie.name + SERVER_CSRF_COOKIE_SUFFIX\n    );\n\n    if (!clientTokenFromRequest) {\n      return null;\n    }\n\n    const currentTime = Math.floor(Date.now() / 1000);\n    const reissueThreshold = this.config.token.reissueThreshold;\n\n    try {\n      switch (this.config.strategy) {\n        case 'signed-token':\n        case 'hybrid': {\n          const payload = await parseSignedToken(\n            clientTokenFromRequest,\n            this.config.secret\n          );\n          if (payload.exp > currentTime + reissueThreshold) {\n            return {\n              clientToken: clientTokenFromRequest,\n              cookieToken: clientTokenFromRequest,\n              cookieOptions: { ...this.config.cookie, httpOnly: false },\n            };\n          }\n          break;\n        }\n        case 'signed-double-submit': {\n          if (serverCookieTokenFromRequest && clientTokenFromRequest) {\n            try {\n              const verifiedToken = await verifySignedToken(\n                serverCookieTokenFromRequest,\n                this.config.secret\n              );\n              if (timingSafeEqual(verifiedToken, clientTokenFromRequest)) {\n                return {\n                  clientToken: clientTokenFromRequest,\n                  cookieToken: clientTokenFromRequest,\n                  serverCookieToken: serverCookieTokenFromRequest,\n                  cookieOptions: { ...this.config.cookie, httpOnly: false },\n                };\n              }\n            } catch {\n              // Invalid signature, fall through to generate new tokens\n            }\n          }\n          break;\n        }\n      }\n    } catch (error) {\n      // Token invalid or expired, return null to generate new tokens\n      return null;\n    }\n\n    return null;\n  }\n\n  /**\n   * Builds the CSRF response with headers and cookies.\n   *\n   * @param tokenData - The token data to include in the response\n   * @returns The CSRF response object\n   * @internal\n   */\n  private buildCsrfResponse(tokenData: TokenData): CsrfResponse {\n    const cookies = new Map([\n      [\n        this.config.cookie.name,\n        {\n          value: tokenData.cookieToken,\n          options: tokenData.cookieOptions,\n        },\n      ],\n    ]);\n\n    if (tokenData.serverCookieToken) {\n      cookies.set(`${this.config.cookie.name}-server`, {\n        value: tokenData.serverCookieToken,\n        options: {\n          ...tokenData.cookieOptions,\n          httpOnly: true,\n        },\n      });\n    }\n\n    return {\n      headers: new Map([\n        [CSRF_TOKEN_HEADER, tokenData.clientToken],\n        [CSRF_STRATEGY_HEADER, this.config.strategy],\n      ]),\n      cookies,\n    };\n  }\n\n  /**\n   * Protects a request/response pair against CSRF attacks.\n   *\n   * This is the main method that applies CSRF protection to incoming requests.\n   * It handles both token generation for safe methods (GET, HEAD, OPTIONS) and\n   * token validation for state-changing methods (POST, PUT, DELETE, etc.).\n   *\n   * The method:\n   * 1. Extracts request data using the framework adapter\n   * 2. Checks if the request should be excluded or skipped\n   * 3. For safe methods: generates and sets new CSRF tokens\n   * 4. For unsafe methods: validates existing tokens using the configured strategy\n   * 5. Applies response data (headers, cookies) using the adapter\n   *\n   * @param request - Framework-specific request object\n   * @param response - Framework-specific response object\n   * @returns Promise resolving to protection result with success status and modified response\n   *\n   * @example\n   * ```typescript\n   * // Basic usage in middleware\n   * const result = await csrf.protect(req, res);\n   * if (!result.success) {\n   *   return res.status(403).json({\n   *     error: 'CSRF validation failed',\n   *     reason: result.reason\n   *   });\n   * }\n   *\n   * // Token is available for safe methods\n   * if (result.token) {\n   *   console.log('Generated CSRF token:', result.token);\n   * }\n   *\n   * // Continue with the modified response\n   * return result.response;\n   * ```\n   *\n   * @example\n   * ```typescript\n   * // Error handling with specific reasons\n   * const result = await csrf.protect(req, res);\n   * if (!result.success) {\n   *   switch (result.reason) {\n   *     case 'Invalid token':\n   *       return res.status(403).json({ error: 'CSRF token is invalid' });\n   *     case 'Token expired':\n   *       return res.status(403).json({ error: 'CSRF token has expired' });\n   *     case 'Origin mismatch':\n   *       return res.status(403).json({ error: 'Request origin not allowed' });\n   *     default:\n   *       return res.status(403).json({ error: 'CSRF validation failed' });\n   *   }\n   * }\n   * ```\n   */\n  async protect(\n    request: TRequest,\n    response: TResponse\n  ): Promise<{\n    success: boolean;\n    response: TResponse;\n    token?: string;\n    reason?: string;\n  }> {\n    const csrfRequest = this.adapter.extractRequest(request);\n\n    // Check if request should be skipped\n    if (this.shouldSkipProtection(csrfRequest)) {\n      return { success: true, response };\n    }\n\n    // Attempt to reuse existing tokens or generate new ones\n    let tokenData = await this.attemptTokenReuse(csrfRequest);\n    tokenData ??= await this.generateTokensForStrategy();\n\n    // Build CSRF response\n    const csrfResponse = this.buildCsrfResponse(tokenData);\n\n    // Apply response modifications\n    const modifiedResponse = this.adapter.applyResponse(response, csrfResponse);\n\n    // Skip validation for safe methods\n    if (\n      SAFE_METHODS.includes(csrfRequest.method as (typeof SAFE_METHODS)[number])\n    ) {\n      return {\n        success: true,\n        response: modifiedResponse,\n        token: tokenData.clientToken,\n      };\n    }\n\n    // Validate based on strategy\n    const validationResult = await validateRequest(\n      csrfRequest,\n      this.config,\n      this.adapter.getTokenFromRequest\n    );\n\n    if (!validationResult.isValid) {\n      return {\n        success: false,\n        response: modifiedResponse,\n        reason: validationResult.reason ?? 'CSRF Validation failed',\n      };\n    }\n\n    return {\n      success: true,\n      response: modifiedResponse,\n      token: tokenData.clientToken,\n    };\n  }\n\n  private async generateTokensForStrategy(): Promise<TokenData> {\n    const baseOptions = this.config.cookie;\n\n    switch (this.config.strategy) {\n      case 'double-submit': {\n        const token = generateNonce(DEFAULT_NONCE_LENGTH);\n        if (!token) {\n          throw new Error(\n            'CSRF Error: Failed to generate nonce for strategy \"double-submit\".'\n          );\n        }\n        return {\n          clientToken: token,\n          cookieToken: token,\n          cookieOptions: { ...baseOptions, httpOnly: false },\n        };\n      }\n\n      case 'signed-double-submit': {\n        const unsignedToken = generateNonce(DEFAULT_NONCE_LENGTH);\n        if (!unsignedToken) {\n          throw new Error(\n            'CSRF Error: Failed to generate nonce for strategy \"signed-double-submit\".'\n          );\n        }\n        const signedToken = await signUnsignedToken(\n          unsignedToken,\n          this.config.secret\n        );\n        return {\n          clientToken: unsignedToken,\n          cookieToken: unsignedToken,\n          serverCookieToken: signedToken,\n          cookieOptions: { ...baseOptions, httpOnly: false },\n        };\n      }\n\n      case 'signed-token':\n      case 'hybrid': {\n        const signedToken = await generateSignedToken(\n          this.config.secret,\n          this.config.token.expiry\n        );\n        return {\n          clientToken: signedToken,\n          cookieToken: signedToken,\n          cookieOptions: { ...baseOptions, httpOnly: false },\n        };\n      }\n\n      case 'origin-check': {\n        const nonce = generateNonce(ORIGIN_CHECK_NONCE_LENGTH);\n        if (!nonce) {\n          throw new Error(\n            'CSRF Error: Failed to generate nonce for strategy \"origin-check\".'\n          );\n        }\n        return {\n          clientToken: nonce,\n          cookieToken: nonce,\n          cookieOptions: { ...baseOptions, httpOnly: false },\n        };\n      }\n\n      default: {\n        throw new Error(`Unknown CSRF strategy: ${this.config.strategy}`);\n      }\n    }\n  }\n}\n\n/**\n * Factory function to create a CSRF protection instance.\n *\n * Convenient alternative to using the CsrfProtection constructor directly.\n * This function is the recommended way to create CSRF protection instances\n * as it provides better type inference and a cleaner API.\n *\n * @public\n * @template TRequest - Framework-specific request type\n * @template TResponse - Framework-specific response type\n * @param adapter - Framework adapter implementing CsrfAdapter interface\n * @param config - Optional CSRF configuration (uses secure defaults if not provided)\n * @returns Configured CSRF protection instance ready for use\n *\n * @example\n * ```typescript\n * import { createCsrfProtection } from '@csrf-armor/core';\n * import { ExpressAdapter } from '@csrf-armor/express';\n *\n * // Basic setup with defaults\n * const csrf = createCsrfProtection(new ExpressAdapter());\n *\n * // Custom configuration\n * const csrf = createCsrfProtection(new ExpressAdapter(), {\n *   strategy: 'signed-double-submit',\n *   secret: process.env.CSRF_SECRET,\n *   token: {\n *     expiry: 7200, // 2 hours\n *     fieldName: 'authenticity_token'\n *   },\n *   excludePaths: ['/api/public'],\n *   allowedOrigins: ['https://yourdomain.com']\n * });\n *\n * // Use in middleware\n * app.use(async (req, res, next) => {\n *   const result = await csrf.protect(req, res);\n *   if (result.success) {\n *     next();\n *   } else {\n *     res.status(403).json({ error: result.reason });\n *   }\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Framework-specific usage\n *\n * // Express.js\n * import { ExpressAdapter } from '@csrf-armor/express';\n * const expressCsrf = createCsrfProtection(new ExpressAdapter(), config);\n *\n * // Next.js\n * import { NextjsAdapter } from '@csrf-armor/nextjs';\n * const nextCsrf = createCsrfProtection(new NextjsAdapter(), config);\n *\n * // Custom framework\n * class MyAdapter implements CsrfAdapter<MyRequest, MyResponse> {\n *   // Implementation...\n * }\n * const customCsrf = createCsrfProtection(new MyAdapter(), config);\n * ```\n */\nexport function createCsrfProtection<TRequest = unknown, TResponse = unknown>(\n  adapter: CsrfAdapter<TRequest, TResponse>,\n  config?: CsrfConfig\n): CsrfProtection<TRequest, TResponse> {\n  return new CsrfProtection(adapter, config);\n}\n"],"mappings":"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"}