{"version":3,"sources":["../../src/auth/web3-signed.ts"],"sourcesContent":["/**\n * Web3Signed Authorization header parsing and verification.\n *\n * @remarks\n * Header format: `\"Web3Signed {base64url(payload)}.{signature}\"`.\n * Payload is JSON with fields `aud`, `method`, `uri`, `bodyHash`, `iat`, `exp`,\n * and optional `grantId`. The signature is EIP-191 over the base64url-encoded\n * payload string.\n *\n * Ported from `personal-server-ts` (`packages/core/src/auth/web3-signed.ts`).\n * Adjusted to be isomorphic (no `Buffer`) and to use SDK-local error types.\n *\n * @category Auth\n */\n\nimport { recoverMessageAddress } from \"viem\";\nimport { fromBase64 } from \"../utils/encoding\";\nimport {\n  MissingAuthError,\n  InvalidSignatureError,\n  ExpiredTokenError,\n} from \"./errors\";\nimport { computeBodyHash } from \"./web3-signed-builder\";\n\nexport interface Web3SignedPayload {\n  aud: string;\n  method: string;\n  uri: string;\n  bodyHash: string;\n  iat: number;\n  exp: number;\n  grantId?: string;\n}\n\nexport interface VerifiedAuth {\n  signer: `0x${string}`;\n  payload: Web3SignedPayload;\n}\n\nconst WEB3_SIGNED_PREFIX = \"Web3Signed \";\nconst CLOCK_SKEW_SECONDS = 60;\n\n/** Decode a base64url string (no padding) to UTF-8. */\nfunction base64urlDecode(input: string): string {\n  let base64 = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n  const padLength = (4 - (base64.length % 4)) % 4;\n  base64 += \"=\".repeat(padLength);\n  return new TextDecoder().decode(fromBase64(base64));\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n  return typeof value === \"number\" && Number.isFinite(value);\n}\n\nfunction parsePayload(value: unknown): Web3SignedPayload {\n  if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n    throw new InvalidSignatureError({ reason: \"Invalid payload shape\" });\n  }\n\n  const payload = value as Record<string, unknown>;\n  if (\n    typeof payload[\"aud\"] !== \"string\" ||\n    typeof payload[\"method\"] !== \"string\" ||\n    typeof payload[\"uri\"] !== \"string\" ||\n    typeof payload[\"bodyHash\"] !== \"string\" ||\n    !isFiniteNumber(payload[\"iat\"]) ||\n    !isFiniteNumber(payload[\"exp\"])\n  ) {\n    throw new InvalidSignatureError({ reason: \"Invalid payload claims\" });\n  }\n\n  if (\n    payload[\"grantId\"] !== undefined &&\n    typeof payload[\"grantId\"] !== \"string\"\n  ) {\n    throw new InvalidSignatureError({ reason: \"Invalid grantId claim\" });\n  }\n\n  return {\n    aud: payload[\"aud\"],\n    method: payload[\"method\"],\n    uri: payload[\"uri\"],\n    bodyHash: payload[\"bodyHash\"],\n    iat: payload[\"iat\"],\n    exp: payload[\"exp\"],\n    grantId: payload[\"grantId\"],\n  };\n}\n\n/**\n * Parse a `\"Web3Signed <base64url>.<signature>\"` header value.\n *\n * @throws {MissingAuthError} If the header is missing or empty.\n * @throws {InvalidSignatureError} If the format is invalid.\n */\nexport function parseWeb3SignedHeader(headerValue: string | undefined): {\n  payloadBase64: string;\n  payload: Web3SignedPayload;\n  signature: `0x${string}`;\n} {\n  if (!headerValue) {\n    throw new MissingAuthError();\n  }\n\n  if (!headerValue.startsWith(WEB3_SIGNED_PREFIX)) {\n    throw new InvalidSignatureError({ reason: \"Missing Web3Signed prefix\" });\n  }\n\n  const value = headerValue.slice(WEB3_SIGNED_PREFIX.length);\n  const dotIndex = value.indexOf(\".\");\n  if (dotIndex === -1 || dotIndex === 0 || dotIndex === value.length - 1) {\n    throw new InvalidSignatureError({ reason: \"Invalid header format\" });\n  }\n\n  const payloadBase64 = value.slice(0, dotIndex);\n  const signatureStr = value.slice(dotIndex + 1);\n\n  if (!signatureStr.startsWith(\"0x\")) {\n    throw new InvalidSignatureError({ reason: \"Invalid signature format\" });\n  }\n\n  let payload: Web3SignedPayload;\n  try {\n    const decoded = base64urlDecode(payloadBase64);\n    payload = parsePayload(JSON.parse(decoded));\n  } catch (err) {\n    if (err instanceof InvalidSignatureError) throw err;\n    throw new InvalidSignatureError({ reason: \"Invalid payload encoding\" });\n  }\n\n  return {\n    payloadBase64,\n    payload,\n    signature: signatureStr as `0x${string}`,\n  };\n}\n\n/**\n * Full verification: parse header, recover signer via EIP-191, check claims.\n *\n * @remarks\n * Steps:\n * 1. Parse header to base64url + signature.\n * 2. Recover signer via {@link recoverMessageAddress} (EIP-191) over the base64url payload string.\n * 3. Check `aud === expectedOrigin`, `method === expectedMethod`, `uri === expectedPath`.\n * 4. Optionally check `bodyHash` against `bodyBytes`.\n * 5. Check `iat`/`exp` within a 60s clock skew.\n *\n * @returns The recovered signer address and parsed payload.\n */\nexport async function verifyWeb3Signed(params: {\n  headerValue: string | undefined;\n  expectedOrigin: string;\n  expectedMethod: string;\n  expectedPath: string;\n  bodyBytes?: Uint8Array;\n  now?: number;\n}): Promise<VerifiedAuth> {\n  const { payloadBase64, payload, signature } = parseWeb3SignedHeader(\n    params.headerValue,\n  );\n\n  let signer: `0x${string}`;\n  try {\n    signer = await recoverMessageAddress({\n      message: payloadBase64,\n      signature,\n    });\n  } catch {\n    throw new InvalidSignatureError({ reason: \"Signature recovery failed\" });\n  }\n\n  if (payload.aud !== params.expectedOrigin) {\n    throw new InvalidSignatureError({\n      reason: \"Audience mismatch\",\n      expected: params.expectedOrigin,\n      actual: payload.aud,\n    });\n  }\n\n  if (payload.method !== params.expectedMethod) {\n    throw new InvalidSignatureError({\n      reason: \"Method mismatch\",\n      expected: params.expectedMethod,\n      actual: payload.method,\n    });\n  }\n\n  if (payload.uri !== params.expectedPath) {\n    throw new InvalidSignatureError({\n      reason: \"URI mismatch\",\n      expected: params.expectedPath,\n      actual: payload.uri,\n    });\n  }\n\n  if (params.bodyBytes !== undefined) {\n    const expectedBodyHash = computeBodyHash(params.bodyBytes);\n    if (payload.bodyHash !== expectedBodyHash) {\n      throw new InvalidSignatureError({\n        reason: \"Body hash mismatch\",\n        expected: expectedBodyHash,\n        actual: payload.bodyHash,\n      });\n    }\n  }\n\n  const now = params.now ?? Math.floor(Date.now() / 1000);\n\n  if (payload.exp < now - CLOCK_SKEW_SECONDS) {\n    throw new ExpiredTokenError({ reason: \"Token expired\" });\n  }\n\n  if (payload.iat > now + CLOCK_SKEW_SECONDS) {\n    throw new ExpiredTokenError({ reason: \"Token issued in the future\" });\n  }\n\n  return { signer, payload };\n}\n"],"mappings":"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","names":[]}