{"version":3,"file":"validation.mjs","names":[],"sources":["../src/validation.ts"],"sourcesContent":["import { SAFE_METHODS } from './constants.js';\nimport {\n  parseSignedToken,\n  timingSafeEqual,\n  verifySignedToken,\n} from './crypto.js';\nimport { OriginMismatchError } from './errors.js';\nimport type {\n  CsrfRequest,\n  RequiredCsrfConfig,\n  ValidationResult,\n} from './types.js';\n\nfunction getHeaders(request: CsrfRequest): Map<string, string> {\n  if (request.headers instanceof Map) {\n    return request.headers;\n  }\n\n  return new Map(Object.entries(request.headers));\n}\n\nfunction getCookies(request: CsrfRequest): Map<string, string> {\n  if (request.cookies instanceof Map) {\n    return request.cookies;\n  }\n\n  return new Map(Object.entries(request.cookies));\n}\n\nexport async function validateSignedToken(\n  request: CsrfRequest,\n  config: RequiredCsrfConfig,\n  getTokenFromRequest: (\n    req: CsrfRequest,\n    config: RequiredCsrfConfig\n  ) => Promise<string | undefined>\n): Promise<ValidationResult> {\n  try {\n    const token = await getTokenFromRequest(request, config);\n\n    if (!token) {\n      return { isValid: false, reason: 'No CSRF token provided' };\n    }\n\n    await parseSignedToken(token, config.secret);\n    return { isValid: true };\n  } catch (error) {\n    if (error instanceof Error) {\n      return { isValid: false, reason: error.message };\n    }\n    return { isValid: false, reason: 'Unknown error' };\n  }\n}\n\nexport function validateOrigin(\n  request: CsrfRequest,\n  config: RequiredCsrfConfig\n): ValidationResult {\n  const headers = getHeaders(request);\n  const origin = headers.get('origin');\n  const referer = headers.get('referer');\n\n  if (!origin && !referer && !SAFE_METHODS.includes(request.method as never)) {\n    return { isValid: false, reason: 'Missing origin and referer headers' };\n  }\n\n  const requestOrigin = origin ?? (referer ? new URL(referer).origin : null);\n\n  if (!requestOrigin) {\n    return { isValid: false, reason: 'No origin or referer header' };\n  }\n\n  if (config.allowedOrigins.includes(requestOrigin)) {\n    return { isValid: true };\n  }\n\n  return {\n    isValid: false,\n    reason: new OriginMismatchError(requestOrigin).message,\n  };\n}\n// DO NOT USE THIS IN PRODUCTION\nexport async function validateDoubleSubmit(\n  request: CsrfRequest,\n  config: RequiredCsrfConfig,\n  getTokenFromRequest: (\n    req: CsrfRequest,\n    config: RequiredCsrfConfig\n  ) => Promise<string | undefined>\n): Promise<ValidationResult> {\n  const cookies = getCookies(request);\n  const cookieName = config.cookie.name;\n  const cookieToken = cookies.get(cookieName);\n  const submittedToken = await getTokenFromRequest(request, config);\n\n  if (!cookieToken) {\n    return { isValid: false, reason: 'No CSRF cookie found' };\n  }\n\n  if (!submittedToken) {\n    return { isValid: false, reason: 'No CSRF token submitted' };\n  }\n\n  if (!timingSafeEqual(cookieToken, submittedToken)) {\n    return { isValid: false, reason: 'Token mismatch' };\n  }\n\n  return { isValid: true };\n}\n\nexport async function validateSignedDoubleSubmit(\n  request: CsrfRequest,\n  config: RequiredCsrfConfig,\n  getTokenFromRequest: (\n    req: CsrfRequest,\n    config: RequiredCsrfConfig\n  ) => Promise<string | undefined>\n): Promise<ValidationResult> {\n  const cookies = getCookies(request);\n\n  const cookieName = config.cookie.name;\n  const unsignedCookieToken = cookies.get(cookieName);\n  const signedCookieToken = cookies.get(`${cookieName}-server`);\n  const submittedToken = await getTokenFromRequest(request, config);\n\n  if (!unsignedCookieToken || !signedCookieToken) {\n    return { isValid: false, reason: 'Missing CSRF cookies' };\n  }\n\n  if (!submittedToken) {\n    return { isValid: false, reason: 'No CSRF token submitted' };\n  }\n\n  try {\n    // 1. Verify the server cookie signature\n    const verifiedUnsignedToken = await verifySignedToken(\n      signedCookieToken,\n      config.secret\n    );\n\n    // 2. Ensure client cookie matches the verified token\n    if (!timingSafeEqual(unsignedCookieToken, verifiedUnsignedToken)) {\n      return { isValid: false, reason: 'Cookie integrity check failed' };\n    }\n\n    // 3. Ensure submitted token matches the unsigned token\n    if (!timingSafeEqual(submittedToken, unsignedCookieToken)) {\n      return { isValid: false, reason: 'Token mismatch' };\n    }\n\n    return { isValid: true };\n  } catch (error) {\n    if (error instanceof Error) {\n      return { isValid: false, reason: error.message };\n    }\n    return { isValid: false, reason: 'Token validation failed' };\n  }\n}\n\nexport async function validateRequest(\n  request: CsrfRequest,\n  config: RequiredCsrfConfig,\n  getTokenFromRequest: (\n    req: CsrfRequest,\n    config: RequiredCsrfConfig\n  ) => Promise<string | undefined>\n): Promise<ValidationResult> {\n  switch (config.strategy) {\n    case 'signed-token':\n      return await validateSignedToken(request, config, getTokenFromRequest);\n\n    case 'origin-check':\n      return validateOrigin(request, config);\n\n    case 'double-submit':\n      return await validateDoubleSubmit(request, config, getTokenFromRequest);\n\n    case 'signed-double-submit':\n      return await validateSignedDoubleSubmit(\n        request,\n        config,\n        getTokenFromRequest\n      );\n\n    case 'hybrid': {\n      const originResult = validateOrigin(request, config);\n      if (!originResult.isValid) return originResult;\n\n      return await validateSignedToken(request, config, getTokenFromRequest);\n    }\n\n    default:\n      return { isValid: false, reason: 'Invalid strategy' };\n  }\n}\n"],"mappings":"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"}