{"version":3,"sources":["../../src/hono/csrf.ts"],"sourcesContent":["import { timingSafeEqual } from 'crypto';\nimport type { MiddlewareHandler } from 'hono';\nimport { getCookie } from 'hono/cookie';\nimport { METHOD_NAME_ALL } from 'hono/router';\nimport { RegExpRouter } from 'hono/router/reg-exp-router';\nimport { SmartRouter } from 'hono/router/smart-router';\nimport { TrieRouter } from 'hono/router/trie-router';\nimport { Status } from '../error/status';\n\ntype HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';\n\nexport type CSRFIgnoreRule = string | { path: string; methods?: [HTTPMethod, ...HTTPMethod[]] };\n\nexport interface CSRFConfig {\n  /**\n   * Cookie name for CSRF token\n   * @default 'XSRF-TOKEN'\n   */\n  cookieName?: string;\n\n  /**\n   * Header name for CSRF token\n   * @default 'X-XSRF-TOKEN'\n   */\n  headerName?: string;\n\n  /**\n   * Ignore rules for specific paths and methods\n   * @example\n   * [\n   *   { path: '/api/webhook/*', methods: ['POST'] },\n   *   { path: '/auth/apple/callback' }, // ignores all methods\n   * ]\n   */\n  ignores?: CSRFIgnoreRule[];\n\n  /**\n   * Skip CSRF check for these methods\n   * @default ['GET', 'HEAD', 'OPTIONS']\n   */\n  safeMethods?: HTTPMethod[];\n\n  /**\n   * Origin allowed to bypass CSRF check\n   * @default undefined\n   */\n  origin?: string[];\n\n  /**\n   * Sec-Fetch-Site allowed to bypass CSRF check\n   * @default undefined\n   */\n  secFetchSite?: Array<'same-origin' | 'same-site' | 'none' | 'cross-origin'>;\n\n  /**\n   * Custom error message\n   * @default 'CSRF token validation failed'\n   */\n  errorMessage?: string;\n}\n\n/** use timing safe compare to prevent timing attack */\nfunction safeCompare(a: string, b: string): boolean {\n  if (typeof a !== 'string' || typeof b !== 'string') return false;\n  if (a.length === 0 || b.length === 0) return false;\n\n  const bufferA = Buffer.from(a, 'utf-8');\n  const bufferB = Buffer.from(b, 'utf-8');\n  if (bufferA.length !== bufferB.length) return false;\n  return timingSafeEqual(bufferA, bufferB);\n}\n\n/**\n * Create CSRF protection middleware\n *\n * @example\n * ```ts\n * import { Hono } from 'hono';\n * import { csrf } from '@shware/http/hono';\n *\n * const app = new Hono();\n *\n * // basic usage\n * app.use(csrf());\n *\n * // with configuration\n * app.use(csrf({\n *   cookieName: 'csrf-token',\n *   headerName: 'X-CSRF-Token',\n *   ignores: [\n *     { path: '/api/webhook/*', methods: ['POST'] },\n *     { path: '/auth/apple/callback' },\n *   ]\n * }));\n * ```\n */\nexport function csrf(config: CSRFConfig = {}): MiddlewareHandler {\n  const cookieName = config.cookieName ?? 'XSRF-TOKEN';\n  const headerName = config.headerName ?? 'X-XSRF-TOKEN';\n  const safeMethods = new Set(config.safeMethods ?? ['GET', 'HEAD', 'OPTIONS']);\n  const errorMessage = config.errorMessage ?? 'CSRF token validation failed';\n\n  // initialize router for matching ignore rules\n  const router = new SmartRouter<boolean>({\n    routers: [new RegExpRouter(), new TrieRouter()],\n  });\n\n  // register ignore rules\n  if (config.ignores) {\n    for (const rule of config.ignores) {\n      if (typeof rule === 'string') {\n        router.add(METHOD_NAME_ALL, rule, true);\n      } else if (rule.methods && rule.methods.length > 0) {\n        for (const method of rule.methods) {\n          router.add(method, rule.path, true);\n        }\n      } else {\n        // if no methods are specified, ignore all methods\n        router.add(METHOD_NAME_ALL, rule.path, true);\n      }\n    }\n  }\n\n  // return middleware\n  return async (c, next) => {\n    const method = c.req.method;\n    const path = c.req.path;\n\n    // check if the request should be ignored\n    // 1. ignore safe methods\n    if (safeMethods.has(method)) {\n      await next();\n      return;\n    }\n\n    // 2. ignore configured origin\n    if (config.origin && config.origin.includes(c.req.header('origin') ?? '')) {\n      await next();\n      return;\n    }\n\n    // 3. ignore configured secFetchSite\n    if (\n      config.secFetchSite &&\n      config.secFetchSite.includes((c.req.header('sec-fetch-site') ?? '') as never)\n    ) {\n      await next();\n      return;\n    }\n\n    // 4. ignore configured ignore rules\n    const [matched] = router.match(method, path);\n    if (matched.length > 0) {\n      await next();\n      return;\n    }\n\n    const cookieToken = getCookie(c, cookieName);\n    const headerToken = c.req.header(headerName);\n\n    if (!cookieToken || !headerToken) {\n      throw Status.permissionDenied(errorMessage).error();\n    }\n\n    if (!safeCompare(cookieToken, headerToken)) {\n      throw Status.permissionDenied(errorMessage).error();\n    }\n\n    await next();\n  };\n}\n"],"mappings":";AAAA,SAAS,uBAAuB;AAEhC,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB;AAChC,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AAuDvB,SAAS,YAAY,GAAW,GAAoB;AAClD,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SAAU,QAAO;AAC3D,MAAI,EAAE,WAAW,KAAK,EAAE,WAAW,EAAG,QAAO;AAE7C,QAAM,UAAU,OAAO,KAAK,GAAG,OAAO;AACtC,QAAM,UAAU,OAAO,KAAK,GAAG,OAAO;AACtC,MAAI,QAAQ,WAAW,QAAQ,OAAQ,QAAO;AAC9C,SAAO,gBAAgB,SAAS,OAAO;AACzC;AA0BO,SAAS,KAAK,SAAqB,CAAC,GAAsB;AAC/D,QAAM,aAAa,OAAO,cAAc;AACxC,QAAM,aAAa,OAAO,cAAc;AACxC,QAAM,cAAc,IAAI,IAAI,OAAO,eAAe,CAAC,OAAO,QAAQ,SAAS,CAAC;AAC5E,QAAM,eAAe,OAAO,gBAAgB;AAG5C,QAAM,SAAS,IAAI,YAAqB;AAAA,IACtC,SAAS,CAAC,IAAI,aAAa,GAAG,IAAI,WAAW,CAAC;AAAA,EAChD,CAAC;AAGD,MAAI,OAAO,SAAS;AAClB,eAAW,QAAQ,OAAO,SAAS;AACjC,UAAI,OAAO,SAAS,UAAU;AAC5B,eAAO,IAAI,iBAAiB,MAAM,IAAI;AAAA,MACxC,WAAW,KAAK,WAAW,KAAK,QAAQ,SAAS,GAAG;AAClD,mBAAW,UAAU,KAAK,SAAS;AACjC,iBAAO,IAAI,QAAQ,KAAK,MAAM,IAAI;AAAA,QACpC;AAAA,MACF,OAAO;AAEL,eAAO,IAAI,iBAAiB,KAAK,MAAM,IAAI;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AAGA,SAAO,OAAO,GAAG,SAAS;AACxB,UAAM,SAAS,EAAE,IAAI;AACrB,UAAM,OAAO,EAAE,IAAI;AAInB,QAAI,YAAY,IAAI,MAAM,GAAG;AAC3B,YAAM,KAAK;AACX;AAAA,IACF;AAGA,QAAI,OAAO,UAAU,OAAO,OAAO,SAAS,EAAE,IAAI,OAAO,QAAQ,KAAK,EAAE,GAAG;AACzE,YAAM,KAAK;AACX;AAAA,IACF;AAGA,QACE,OAAO,gBACP,OAAO,aAAa,SAAU,EAAE,IAAI,OAAO,gBAAgB,KAAK,EAAY,GAC5E;AACA,YAAM,KAAK;AACX;AAAA,IACF;AAGA,UAAM,CAAC,OAAO,IAAI,OAAO,MAAM,QAAQ,IAAI;AAC3C,QAAI,QAAQ,SAAS,GAAG;AACtB,YAAM,KAAK;AACX;AAAA,IACF;AAEA,UAAM,cAAc,UAAU,GAAG,UAAU;AAC3C,UAAM,cAAc,EAAE,IAAI,OAAO,UAAU;AAE3C,QAAI,CAAC,eAAe,CAAC,aAAa;AAChC,YAAM,OAAO,iBAAiB,YAAY,EAAE,MAAM;AAAA,IACpD;AAEA,QAAI,CAAC,YAAY,aAAa,WAAW,GAAG;AAC1C,YAAM,OAAO,iBAAiB,YAAY,EAAE,MAAM;AAAA,IACpD;AAEA,UAAM,KAAK;AAAA,EACb;AACF;","names":[]}