{"version":3,"file":"credential-scope.cjs","names":[],"sources":["../../src/http/credential-scope.ts"],"sourcesContent":["import { isDevBuild } from \"../utils/dev-mode\";\n\n/**\n * Origins already reported by {@link reportSuppressedCredential}, so a chunked\n * upload warns once instead of once per chunk.\n */\nconst reported = new Set<string>();\n\n/**\n * The origin a URL resolves to, or `null` when it has none to compare.\n *\n * Relative values resolve against the document, because that is what they mean\n * everywhere else in the SDK: an `endpoint` of `\"/api/uploads\"` and a `baseURL`\n * of `\"/api\"` are the shape you get behind a dev-server or a reverse proxy, and\n * both name the page's own origin. Comparing a resolved absolute target against\n * an unresolved relative reference is how the first draft of this function\n * withheld the credential from every client configured that way.\n *\n * `null` is reserved for the case with genuinely nothing to compare: a relative\n * URL outside a browsing context, where there is no document to resolve against.\n *\n * @param url - Absolute or relative URL.\n * @returns The origin, or `null` when it cannot be resolved.\n */\nfunction originOf(url: string): string | null {\n    try {\n        return new URL(url, globalThis.location?.href).origin;\n    } catch {\n        return null;\n    }\n}\n\n/**\n * Whether a request to `target` may carry the caller's credential.\n *\n * The SDK writes `Authorization: Bearer` in three places, and until 0.66.0 none\n * of them looked at where the request was going. That is only safe while every\n * target URL is written by the app: a resumable upload takes its URL from the\n * server's `Location` header, and `createApiClient` lets an absolute path\n * override `baseURL` entirely, so a URL arriving over the network could decide\n * which origin received the token — and the file bytes with it.\n *\n * Handing an upload off to object storage on another host is ordinary `tus`\n * deployment, not an attack, which is exactly why the credential must be scoped\n * rather than the request blocked: the upload still goes through, it just goes\n * without the API's bearer token.\n *\n * Both sides are resolved against the document first, so a relative `baseURL`\n * or `endpoint` — the shape behind a dev-server or reverse proxy — compares\n * equal to the absolute URL the request actually goes to. Outside a browsing\n * context a relative target resolves to nothing and is allowed: there is no\n * origin for it to cross.\n *\n * @param target - The URL the request is about to go to.\n * @param reference - The origin the credential belongs to, normally the\n *     client's `baseURL` or the upload `endpoint`.\n * @param trustedOrigins - Extra origins the app declared, for the legitimate\n *     second host: a CDN, a signed upload endpoint.\n * @returns Whether the credential may be attached.\n *\n * @example\n * isTrustedCredentialTarget(\"https://api.acme.com/me\", \"https://api.acme.com\");\n * // true\n * isTrustedCredentialTarget(\"https://cdn.other/u/1\", \"https://api.acme.com\");\n * // false\n */\nexport function isTrustedCredentialTarget(\n    target: string,\n    reference: string,\n    trustedOrigins: readonly string[] = [],\n): boolean {\n    const targetOrigin = originOf(target);\n    if (targetOrigin === null) return true;\n    if (targetOrigin === originOf(reference)) return true;\n    return trustedOrigins.some((origin) => originOf(origin) === targetOrigin);\n}\n\n/**\n * Say once, in a development build, that a credential was withheld.\n *\n * Without it the symptom is a `401` from a host the developer never typed, and\n * nothing in the app's code mentions the origin that produced it. Keyed by\n * target origin so a hundred-chunk upload reports one line.\n *\n * Silent in production for the same reason {@link parseResponse} is: the origin\n * an app talks to is not something to print into a user's console or ship to an\n * error tracker.\n *\n * @param target - The URL whose request went out unauthenticated.\n * @param reference - The origin the credential was scoped to.\n */\nexport function reportSuppressedCredential(target: string, reference: string): void {\n    if (!isDevBuild()) return;\n    const targetOrigin = originOf(target);\n    if (targetOrigin === null || reported.has(targetOrigin)) return;\n    reported.add(targetOrigin);\n    console.warn(\n        `[tempest-react-sdk] Authorization was not sent to ${targetOrigin}: the credential is scoped to ${originOf(reference) ?? reference}. Add the origin to trustedOrigins if it should receive the token.`,\n    );\n}\n\n/**\n * Forget which origins were already reported.\n *\n * Exported for tests, which assert the once-per-origin behaviour and would\n * otherwise leak state between cases.\n */\nexport function resetSuppressedCredentialReports(): void {\n    reported.clear();\n}\n"],"mappings":"yCAMA,IAAM,EAAW,IAAI,IAkBrB,SAAS,EAAS,EAA4B,CAC1C,GAAI,CACA,OAAO,IAAI,IAAI,EAAK,WAAW,UAAU,IAAI,CAAC,CAAC,MACnD,MAAQ,CACJ,OAAO,IACX,CACJ,CAoCA,SAAgB,EACZ,EACA,EACA,EAAoC,CAAC,EAC9B,CACP,IAAM,EAAe,EAAS,CAAM,EAGpC,OAFI,IAAiB,MACjB,IAAiB,EAAS,CAAS,GAChC,EAAe,KAAM,GAAW,EAAS,CAAM,IAAM,CAAY,CAC5E,CAgBA,SAAgB,EAA2B,EAAgB,EAAyB,CAChF,GAAI,CAAC,EAAA,WAAW,EAAG,OACnB,IAAM,EAAe,EAAS,CAAM,EAChC,IAAiB,MAAQ,EAAS,IAAI,CAAY,IACtD,EAAS,IAAI,CAAY,EACzB,QAAQ,KACJ,qDAAqD,EAAa,gCAAgC,EAAS,CAAS,GAAK,EAAU,mEACvI,EACJ"}