@commercelayer/js-auth
Version:
A JavaScript library designed to simplify authentication when interacting with the Commerce Layer API.
1 lines • 34.7 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/utils/camelCaseToSnakeCase.ts","../src/utils/mapKeys.ts","../src/utils/snakeCaseToCamelCase.ts","../src/authenticate.ts","../src/errors/TokenError.ts","../src/errors/InvalidTokenError.ts","../src/utils/base64.ts","../src/jwtDecode.ts","../src/utils/extractIssuer.ts","../src/revoke.ts","../src/errors/TokenExpiredError.ts","../src/jwtVerify.ts","../src/jwtEncode.ts","../src/getCoreApiBaseEndpoint.ts","../src/getProvisioningApiBaseEndpoint.ts"],"sourcesContent":["export { authenticate } from \"./authenticate.js\"\nexport { revoke } from \"./revoke.js\"\n\nexport {\n jwtDecode,\n jwtIsDashboard,\n jwtIsIntegration,\n jwtIsSalesChannel,\n jwtIsUser,\n jwtIsWebApp,\n type JWTDashboard,\n type JWTIntegration,\n type JWTSalesChannel,\n type JWTUser,\n type JWTWebApp,\n} from \"./jwtDecode.js\"\n\nexport { jwtVerify } from \"./jwtVerify.js\"\n\nexport { createAssertion } from \"./jwtEncode.js\"\n\nexport type {\n AuthenticateOptions,\n AuthenticateReturn,\n GrantType,\n RevokeOptions,\n RevokeReturn,\n} from \"./types/index.js\"\n\nexport { getCoreApiBaseEndpoint } from \"./getCoreApiBaseEndpoint.js\"\nexport { getProvisioningApiBaseEndpoint } from \"./getProvisioningApiBaseEndpoint.js\"\n\nexport { InvalidTokenError } from \"./errors/InvalidTokenError.js\"\nexport { TokenError } from \"./errors/TokenError.js\"\nexport { TokenExpiredError } from \"./errors/TokenExpiredError.js\"\n","export type CamelCaseToSnakeCase<\n T extends string,\n P extends string = \"\",\n> = string extends T\n ? string\n : T extends `${infer C}${infer R}`\n ? CamelCaseToSnakeCase<\n R,\n `${P}${C extends Lowercase<C> ? \"\" : \"_\"}${Lowercase<C>}`\n >\n : P\n\nexport function camelCaseToSnakeCase<S extends string>(\n str: S,\n): CamelCaseToSnakeCase<S> {\n return str.replace(\n /[A-Z]/g,\n (letter) => `_${letter.toLowerCase()}`,\n ) as CamelCaseToSnakeCase<S>\n}\n","export function mapKeys(\n obj: Record<string, unknown>,\n fn: (key: string) => string,\n): Record<string, unknown> {\n return Object.keys(obj).reduce((acc: Record<string, unknown>, key) => {\n const camelKey = fn(key)\n acc[camelKey] = obj[key]\n return acc\n }, {})\n}\n","export type SnakeCaseToCamelCase<S extends string> =\n S extends `${infer T}_${infer U}`\n ? `${Lowercase<T>}${Capitalize<SnakeCaseToCamelCase<U>>}`\n : S\n\nexport function snakeCaseToCamelCase<S extends string>(\n str: S,\n): SnakeCaseToCamelCase<S> {\n return str.replace(/([-_][a-z])/g, (group) =>\n group.toUpperCase().replace(\"-\", \"\").replace(\"_\", \"\"),\n ) as SnakeCaseToCamelCase<S>\n}\n","import type {\n AuthenticateOptions,\n AuthenticateReturn,\n GrantType,\n} from \"./types/index.js\"\n\nimport { camelCaseToSnakeCase } from \"./utils/camelCaseToSnakeCase.js\"\nimport { mapKeys } from \"./utils/mapKeys.js\"\nimport { snakeCaseToCamelCase } from \"./utils/snakeCaseToCamelCase.js\"\n\ninterface TokenJson {\n errors?: unknown\n expires: Date\n expires_in: number\n [key: string]: unknown\n}\n\n/**\n * Authenticate helper used to get the access token.\n *\n * _Please note that the authentication endpoint is subject to a [rate limit](https://docs.commercelayer.io/core/rate-limits)\n * of **max 30 reqs / 1 min** both in live and test mode._\n * @param grantType The type of OAuth 2.0 grant being used for authentication.\n * @param options Authenticate options\n * @returns\n * @example\n * ```ts\n * import { authenticate } from '@commercelayer/js-auth'\n *\n * const auth = await authenticate('client_credentials', {\n * clientId: '{{ clientId }}',\n * scope: 'market:id:DGzAouppwn'\n * })\n *\n * console.log(auth.accessToken)\n * ```\n */\nexport async function authenticate<TGrantType extends GrantType>(\n grantType: TGrantType,\n {\n domain = \"commercelayer.io\",\n headers,\n ...options\n }: AuthenticateOptions<TGrantType>,\n): Promise<AuthenticateReturn<TGrantType>> {\n const body = mapKeys(\n {\n grant_type: grantType,\n ...options,\n },\n camelCaseToSnakeCase,\n )\n\n const response = await fetch(`https://auth.${domain}/oauth/token`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...headers,\n },\n body: JSON.stringify(body),\n })\n\n const json: TokenJson = await response.json()\n\n if (json.errors == null) {\n json.expires = new Date(Date.now() + json.expires_in * 1000)\n }\n\n return mapKeys(\n json,\n snakeCaseToCamelCase,\n ) as unknown as AuthenticateReturn<TGrantType>\n}\n","/**\n * A token error occurred.\n */\nexport class TokenError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"TokenError\"\n }\n}\n","import { TokenError } from \"./TokenError.js\"\n\n/**\n * The token is not valid.\n */\nexport class InvalidTokenError extends TokenError {\n constructor(message: string) {\n super(message)\n this.name = \"InvalidTokenError\"\n }\n}\n","/**\n * Creates a [Base64](https://developer.mozilla.org/en-US/docs/Glossary/Base64) URL safe encoded [ASCII](https://developer.mozilla.org/en-US/docs/Glossary/ASCII)\n * string from a _binary string_ (i.e., a string in which each character in the string is treated as a byte of binary data).\n *\n * The \"Base64 URL safe\" omits the padding `=` and replaces `+/` with `-_` to avoid characters that might cause problems in URL path segments or query parameters.\n *\n * This method works both in Node.js and browsers.\n *\n * @param stringToEncode The binary string to encode.\n * @returns An ASCII string containing the Base64 URL safe representation of `stringToEncode`.\n */\nexport function encodeBase64URLSafe(\n stringToEncode: string,\n encoding: \"utf-8\" | \"binary\",\n): string {\n if (typeof btoa !== \"undefined\") {\n let utf8String = stringToEncode\n\n if (encoding === \"utf-8\") {\n // Encode the string as UTF-8\n const utf8Bytes = new TextEncoder().encode(stringToEncode)\n\n // Convert the UTF-8 bytes to a Base64 string\n utf8String = String.fromCharCode(...utf8Bytes)\n }\n\n return (\n btoa(utf8String)\n // Remove padding equal characters\n .replaceAll(\"=\", \"\")\n // Replace characters according to base64url specifications\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\")\n )\n }\n\n return Buffer.from(stringToEncode, encoding).toString(\"base64url\")\n}\n\n/**\n * Decodes a string of data\n * which has been encoded using [Base64](https://developer.mozilla.org/en-US/docs/Glossary/Base64) URL safe encoding.\n *\n * The \"Base64 URL safe\" omits the padding `=` and replaces `+/` with `-_` to avoid characters that might cause problems in URL path segments or query parameters.\n *\n * This method works both in Node.js and browsers.\n *\n * @param encodedData A binary string (i.e., a string in which each character in the string is treated as a byte of binary data) containing Base64 URL safe -encoded data.\n * @returns An ASCII string containing decoded data from `encodedData`.\n */\nexport function decodeBase64URLSafe(\n encodedData: string,\n encoding: \"utf-8\" | \"binary\",\n): string {\n if (typeof atob !== \"undefined\") {\n const decoded = atob(\n encodedData\n // Replace characters according to base64url specifications\n .replaceAll(\"-\", \"+\")\n .replaceAll(\"_\", \"/\")\n // Add padding if necessary\n .padEnd(encodedData.length + ((4 - (encodedData.length % 4)) % 4), \"=\"),\n )\n\n if (encoding === \"utf-8\") {\n // Decode the Base64 string into bytes\n const byteArray = new Uint8Array(\n [...decoded].map((char) => char.charCodeAt(0)),\n )\n\n // Convert the bytes back to a UTF-8 string\n return new TextDecoder().decode(byteArray)\n }\n\n return decoded\n }\n\n return Buffer.from(encodedData, \"base64url\").toString(encoding)\n}\n","import { InvalidTokenError } from \"./errors/InvalidTokenError.js\"\nimport { decodeBase64URLSafe } from \"./utils/base64.js\"\n\n/**\n * Decode a Commerce Layer access token without verifying if the signature is valid.\n *\n * _You should not use this for untrusted messages, since this helper method does not verify whether the signature is valid.\n * If you need to verify the access token before decoding, you can use `jwtVerify` instead._\n */\nexport function jwtDecode(accessToken: string): CommerceLayerJWT {\n const [encodedHeader, encodedPayload, signature] = `${accessToken}`.split(\".\")\n\n if (encodedHeader == null || encodedPayload == null || signature == null) {\n throw new InvalidTokenError(\"Invalid token format\")\n }\n\n return {\n header: JSON.parse(decodeBase64URLSafe(encodedHeader, \"binary\")),\n payload: JSON.parse(decodeBase64URLSafe(encodedPayload, \"utf-8\")),\n signature,\n }\n}\n\nexport interface CommerceLayerJWT {\n /** The header typically consists of two parts: the type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 or RSA. */\n header: {\n /** Signing algorithm being used (e.g. `HMAC`, `SHA256`, `RSA`, `RS512`). */\n alg: string\n /** Type of the token (usually `JWT`). */\n typ?: string\n /** Key ID */\n kid: string\n }\n\n payload: Payload\n\n signature: string\n}\n\ntype Payload =\n | JWTDashboard\n | JWTUser\n | JWTSalesChannel\n | JWTIntegration\n | JWTWebApp\n\ninterface JWTBase {\n /** The type of credentials you're using to authenticate to the APIs. */\n application: {\n id: string\n public: boolean\n client_id: string\n }\n\n /** Scope used to restrict access to a specific active market and/or stock location. */\n scope: string\n /** The token expiration time, expressed as an [epoch](https://www.epoch101.com/). */\n exp: number\n /** The environment type (true for test mode, false for live mode). */\n test: boolean\n /** A randomly generated number, less than one. */\n rand: number\n /** Issued at (seconds since Unix epoch). */\n iat: number\n /** Who created and signed this token (e.g. `\"https://auth.commercelayer.io\"`). */\n iss: string\n}\n\n/**\n * A JWT payload that represents a `user`.\n */\nexport type JWTUser = JWTBase & {\n /** The type of credentials you're using to authenticate to the APIs. */\n application: {\n kind: \"user\"\n }\n /** The authenticated user. */\n user: {\n id: string\n }\n}\n\n/**\n * A JWT payload that represents a `dashboard`.\n */\nexport type JWTDashboard = JWTBase & {\n /** The type of credentials you're using to authenticate to the APIs. */\n application: {\n kind: \"dashboard\"\n }\n /** The authenticated user. */\n user: {\n id: string\n }\n}\n\ntype JWTOrganizationBase = JWTBase & {\n /** The organization in scope. */\n organization: {\n id: string\n slug: string\n enterprise: boolean\n region: string\n }\n /** The owner (if any) authenticating to the APIs. */\n owner?: {\n id: string\n type: \"Customer\" | \"User\"\n }\n /**\n * Any other information (key/value pairs) you want to enrich the token with,\n * when using the [JWT Bearer flow](https://docs.commercelayer.io/core/authentication/jwt-bearer).\n */\n custom_claim?: Record<string, string>\n /**\n * The market(s) in scope.\n * This is available only when the scope is defined in the request.\n */\n market?: {\n id: string[]\n stock_location_ids: string[]\n geocoder_id: string | null\n allows_external_prices: boolean\n }\n}\n\n/**\n * A JWT payload that represents a `webapp`.\n */\nexport type JWTWebApp = SetRequired<JWTOrganizationBase, \"owner\"> & {\n /** The type of credentials you're using to authenticate to the APIs. */\n application: {\n kind: \"webapp\"\n }\n}\n\n/** Create a type that makes the given keys required. The remaining keys are kept as is. */\ntype SetRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }\n\n/**\n * A JWT payload that represents a `sales_channel`.\n */\nexport type JWTSalesChannel = JWTOrganizationBase & {\n /** The type of credentials you're using to authenticate to the APIs. */\n application: {\n kind: \"sales_channel\"\n }\n}\n\n/**\n * A JWT payload that represents an `integration`.\n */\nexport type JWTIntegration = JWTOrganizationBase & {\n /** The type of credentials you're using to authenticate to the APIs. */\n application: {\n kind: \"integration\"\n }\n}\n\n/**\n * Checks if the provided payload represents a `user`.\n * @param payload The payload to be checked.\n * @returns\n */\nexport function jwtIsUser(payload: Payload): payload is JWTUser {\n return payload.application.kind === \"user\"\n}\n\n/**\n * Checks if the provided payload represents a `dashboard`.\n * @param payload The payload to be checked.\n * @returns\n */\nexport function jwtIsDashboard(payload: Payload): payload is JWTDashboard {\n return payload.application.kind === \"dashboard\"\n}\n\n/**\n * Checks if the provided payload represents an `integration`.\n * @param payload The payload to be checked.\n * @returns\n */\nexport function jwtIsIntegration(payload: Payload): payload is JWTIntegration {\n return payload.application.kind === \"integration\"\n}\n\n/**\n * Checks if the provided payload represents a `sales_channel`.\n * @param payload The payload to be checked.\n * @returns\n */\nexport function jwtIsSalesChannel(\n payload: Payload,\n): payload is JWTSalesChannel {\n return payload.application.kind === \"sales_channel\"\n}\n\n/**\n * Checks if the provided payload represents a `webapp`.\n * @param payload The payload to be checked.\n * @returns\n */\nexport function jwtIsWebApp(payload: Payload): payload is JWTWebApp {\n return payload.application.kind === \"webapp\"\n}\n","import type { CommerceLayerJWT } from \"src/jwtDecode.js\"\n\n/**\n * Extract the `iss` from the decoded JWT.\n *\n * This is not as simple as `decodedJWT.payload.iss` because:\n * - at the beginning the `iss` was not required.\n * - the value can be `https://commercelayer.io` is old tokens.\n */\nexport function extractIssuer(\n decodedJWT: CommerceLayerJWT,\n): `https://auth.${string}` {\n return decodedJWT?.payload?.iss?.startsWith(\"https://auth.\")\n ? (decodedJWT.payload.iss as `https://auth.${string}`)\n : \"https://auth.commercelayer.io\"\n}\n","import { jwtDecode } from \"./jwtDecode.js\"\nimport type { RevokeOptions, RevokeReturn } from \"./types/index.js\"\n\nimport { camelCaseToSnakeCase } from \"./utils/camelCaseToSnakeCase.js\"\nimport { extractIssuer } from \"./utils/extractIssuer.js\"\nimport { mapKeys } from \"./utils/mapKeys.js\"\n\n/**\n * Revoke a previously generated access token (refresh tokens included) before its natural expiration date.\n *\n * @param options Revoke options\n * @returns\n * @example\n * ```ts\n * await revoke({\n * clientId: '{{ integrationClientId }}',\n * clientSecret: '{{ integrationClientSecret }}',\n * token: authenticateResponse.accessToken\n * })\n * ```\n */\nexport async function revoke(options: RevokeOptions): Promise<RevokeReturn> {\n const body = mapKeys(options, camelCaseToSnakeCase)\n const decodedJWT = jwtDecode(options.token)\n const issuer = extractIssuer(decodedJWT)\n\n const response = await fetch(`${issuer}/oauth/revoke`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(body),\n })\n\n return (await response.json()) as RevokeReturn\n}\n","import { TokenError } from \"./TokenError.js\"\n\n/**\n * The token expired.\n */\nexport class TokenExpiredError extends TokenError {\n constructor() {\n super(\"Token expired\")\n this.name = \"TokenExpiredError\"\n }\n}\n","import { InvalidTokenError } from \"./errors/InvalidTokenError.js\"\nimport { TokenError } from \"./errors/TokenError.js\"\nimport { TokenExpiredError } from \"./errors/TokenExpiredError.js\"\nimport { type CommerceLayerJWT, jwtDecode } from \"./jwtDecode.js\"\nimport { decodeBase64URLSafe } from \"./utils/base64.js\"\nimport { extractIssuer } from \"./utils/extractIssuer.js\"\n\n/**\n * Verify a Commerce Layer access token.\n * When the verification succeeds, it resolves to the decoded access token, it rejects otherwise.\n */\nexport async function jwtVerify(\n accessToken: string,\n { ignoreExpiration = false, jwk }: JwtVerifyOptions = {},\n): Promise<CommerceLayerJWT> {\n const decodedJWT = jwtDecode(accessToken)\n\n const jsonWebKey = jwk ?? (await getJsonWebKey(decodedJWT))\n\n if (jsonWebKey == null || jsonWebKey.kid !== decodedJWT.header.kid) {\n throw new InvalidTokenError('Invalid token \"kid\"')\n }\n\n if (!ignoreExpiration && Date.now() >= decodedJWT.payload.exp * 1000) {\n throw new TokenExpiredError()\n }\n\n const algorithm: RsaHashedImportParams = {\n name: \"RSASSA-PKCS1-v1_5\",\n hash: \"SHA-512\",\n }\n\n const publicKey = await crypto.subtle.importKey(\n \"jwk\",\n jsonWebKey,\n algorithm,\n true,\n [\"verify\"],\n )\n\n const rawSignature = new Uint8Array(\n Array.from(decodeBase64URLSafe(decodedJWT.signature, \"binary\"), (c) =>\n c.charCodeAt(0),\n ),\n )\n\n const rawData = new TextEncoder().encode(\n accessToken.split(\".\").slice(0, 2).join(\".\"),\n )\n\n const isValid = await crypto.subtle.verify(\n algorithm,\n publicKey,\n rawSignature,\n rawData,\n )\n\n if (!isValid) {\n throw new InvalidTokenError(\"Invalid signature\")\n }\n\n return decodedJWT\n}\n\ntype CommerceLayerJsonWebKey = JsonWebKey & { kid: string }\n\ninterface JwtVerifyOptions {\n /**\n * Do not validate the token expiration when set to `true`.\n * @default false\n */\n ignoreExpiration?: boolean\n\n /**\n * Json Web Key used to verify the signature.\n *\n * The `kid` must match the `kid` from decoded accessToken.\n *\n * By default, we pick the jwk from https://auth.commercelayer.io/.well-known/jwks.json using the `kid` from the accessToken.\n */\n jwk?: CommerceLayerJsonWebKey\n}\n\n/** JWKS in-memory cache. */\nconst JWKSCache: Record<string, CommerceLayerJsonWebKey | undefined> = {}\n\n/**\n * Get the `JsonWebKey` given a key identifier.\n * @param kid Key identifier.\n * @returns\n */\nasync function getJsonWebKey(\n jwt: CommerceLayerJWT,\n): Promise<CommerceLayerJsonWebKey | undefined> {\n const { kid } = jwt.header\n\n if (JWKSCache[kid] != null) {\n return JWKSCache[kid]\n }\n\n const keys = await getJsonWebKeys(jwt)\n\n JWKSCache[kid] = keys.find((key) => key.kid === kid)\n\n return JWKSCache[kid]\n}\n\n/**\n * Retrieve RSA public keys from our JWKS (JSON Web Key Set) endpoint.\n * @returns\n */\nasync function getJsonWebKeys(\n jwt: CommerceLayerJWT,\n): Promise<CommerceLayerJsonWebKey[]> {\n const jwksUrl = `${extractIssuer(jwt)}/.well-known/jwks.json`\n\n const response = await fetch(jwksUrl).then<{\n keys: CommerceLayerJsonWebKey[] | undefined\n }>(async (res) => await res.json())\n\n if (response.keys == null) {\n throw new TokenError(\n `Invalid jwks response from \"${jwksUrl}\": ${JSON.stringify(response)}`,\n )\n }\n\n return response.keys\n}\n","import { encodeBase64URLSafe } from \"./utils/base64.js\"\n\ninterface Owner {\n type: \"User\" | \"Customer\"\n id: string\n}\n\n/**\n * Create a JWT assertion as the first step of the [JWT bearer token authorization grant flow](https://docs.commercelayer.io/core/authentication/jwt-bearer).\n *\n * The JWT assertion is a digitally signed JSON object containing information\n * about the client and the user on whose behalf the access token is being requested.\n *\n * This JWT assertion can include information such as the issuer (typically the client),\n * the owner (the user on whose behalf the request is made), and any other relevant claims.\n *\n * @example\n * ```ts\n * const assertion = await createAssertion({\n * payload: {\n * 'https://commercelayer.io/claims': {\n * owner: {\n * type: 'Customer',\n * id: '4tepftJsT2'\n * },\n * custom_claim: {\n * customer: {\n * first_name: 'John',\n * last_name: 'Doe'\n * }\n * }\n * }\n * }\n * })\n * ```\n */\nexport async function createAssertion({ payload }: Assertion): Promise<string> {\n return await jwtEncode(payload, \"cl\")\n}\n\n/** RequireAtLeastOne helps create a type where at least one of the properties of an interface (can be any property) is required to exist. */\ntype RequireAtLeastOne<T> = {\n [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>\n}[keyof T]\n\ninterface Assertion {\n /** Assertion payload. */\n payload: {\n /** At least one of `owner` or `custom_claim` is required. You cannot use an empty object. */\n \"https://commercelayer.io/claims\": RequireAtLeastOne<{\n /** The customer or user you want to make the calls on behalf of. */\n owner?: Owner\n /** Any other information (key/value pairs) you want to enrich the token with. */\n custom_claim?: Record<string, unknown>\n }>\n }\n}\n\nasync function jwtEncode(\n payload: Record<string, unknown>,\n secret: string,\n): Promise<string> {\n const header = { alg: \"HS512\", typ: \"JWT\" }\n\n const encodedHeader = encodeBase64URLSafe(JSON.stringify(header), \"binary\")\n\n const encodedPayload = encodeBase64URLSafe(\n JSON.stringify({\n ...payload,\n iat: Math.floor(new Date().getTime() / 1000),\n }),\n \"utf-8\",\n )\n\n const unsignedToken = `${encodedHeader}.${encodedPayload}`\n\n const signature = await createSignature(unsignedToken, secret)\n\n return `${unsignedToken}.${signature}`\n}\n\nasync function createSignature(data: string, secret: string): Promise<string> {\n const enc = new TextEncoder()\n const algorithm = { name: \"HMAC\", hash: \"SHA-512\" }\n\n const key = await crypto.subtle.importKey(\n \"raw\",\n enc.encode(secret),\n algorithm,\n false,\n [\"sign\", \"verify\"],\n )\n\n const signature = await crypto.subtle.sign(\n algorithm.name,\n key,\n enc.encode(data),\n )\n\n return encodeBase64URLSafe(\n String.fromCharCode(...new Uint8Array(signature)),\n \"binary\",\n )\n}\n","import { InvalidTokenError } from \"./errors/InvalidTokenError.js\"\nimport { jwtDecode } from \"./jwtDecode.js\"\nimport { extractIssuer } from \"./utils/extractIssuer.js\"\n\n/**\n * Derives the [Core API base endpoint](https://docs.commercelayer.io/core/api-specification#base-endpoint) given a valid access token.\n *\n * @example\n * ```ts\n * getCoreApiBaseEndpoint('eyJhbGciOiJS...') //= \"https://yourdomain.commercelayer.io\"\n * ```\n *\n * The method requires a valid access token with an `organization` in the payload.\n *\n * @param accessToken - The access token to decode.\n * @param options - An options object to configure behavior.\n * @returns The core API base endpoint as a string, or `null` if the token is invalid and `shouldThrow` is `false`.\n * @throws InvalidTokenError - If the token is invalid and `shouldThrow` is true.\n */\nexport function getCoreApiBaseEndpoint(\n accessToken: string,\n options?: {\n /**\n * Whether to throw an error if the token is invalid.\n * @default true\n */\n shouldThrow?: true\n },\n): string\n\n/**\n * Derives the [Core API base endpoint](https://docs.commercelayer.io/core/api-specification#base-endpoint) given a valid access token.\n *\n * @example\n * ```ts\n * getCoreApiBaseEndpoint('eyJhbGciOiJS...') //= \"https://yourdomain.commercelayer.io\"\n * ```\n *\n * The method requires a valid access token with an `organization` in the payload.\n *\n * @param accessToken - The access token to decode.\n * @param options - An options object to configure behavior.\n * @returns The core API base endpoint as a string, or `null` if the token is invalid and `shouldThrow` is `false`.\n * @throws InvalidTokenError - If the token is invalid and `shouldThrow` is true.\n */\nexport function getCoreApiBaseEndpoint(\n accessToken: string,\n options: {\n /**\n * Whether to throw an error if the token is invalid.\n * @default true\n */\n shouldThrow: false\n },\n): string | null\n\nexport function getCoreApiBaseEndpoint(\n accessToken: string,\n options: {\n shouldThrow?: boolean\n } = {},\n): string | null {\n const { shouldThrow = true } = options\n const decodedJWT = jwtDecode(accessToken)\n\n if (!(\"organization\" in decodedJWT.payload)) {\n if (shouldThrow) {\n throw new InvalidTokenError(\"Invalid token format\")\n }\n\n return null\n }\n\n return extractIssuer(decodedJWT).replace(\n \"auth\",\n decodedJWT.payload.organization.slug,\n )\n}\n","import { InvalidTokenError } from \"./errors/InvalidTokenError.js\"\nimport { jwtDecode } from \"./jwtDecode.js\"\nimport { extractIssuer } from \"./utils/extractIssuer.js\"\n\n/**\n * Returns the [Provisioning API base endpoint](https://docs.commercelayer.io/provisioning/getting-started/api-specification#base-endpoint) given a valid access token.\n *\n * @example\n * ```ts\n * getProvisioningApiBaseEndpoint('eyJhbGciOiJS...') //= \"https://provisioning.commercelayer.io\"\n * ```\n *\n * The method requires a valid access token for Provisioning API.\n *\n * @param accessToken - The access token to decode.\n * @param options - An options object to configure behavior.\n * @returns The provisioning API base endpoint as a string, or `null` if the token is invalid and `shouldThrow` is `false`.\n * @throws InvalidTokenError - If the token is invalid and `shouldThrow` is true.\n */\nexport function getProvisioningApiBaseEndpoint(\n accessToken: string,\n options?: {\n /**\n * Whether to throw an error if the token is invalid.\n * @default true\n */\n shouldThrow?: true\n },\n): string\n\n/**\n * Returns the [Provisioning API base endpoint](https://docs.commercelayer.io/provisioning/getting-started/api-specification#base-endpoint) given a valid access token.\n *\n * @example\n * ```ts\n * getProvisioningApiBaseEndpoint('eyJhbGciOiJS...') //= \"https://provisioning.commercelayer.io\"\n * ```\n *\n * The method requires a valid access token for Provisioning API.\n *\n * @param accessToken - The access token to decode.\n * @param options - An options object to configure behavior.\n * @returns The provisioning API base endpoint as a string, or `null` if the token is invalid and `shouldThrow` is `false`.\n * @throws InvalidTokenError - If the token is invalid and `shouldThrow` is true.\n */\nexport function getProvisioningApiBaseEndpoint(\n accessToken: string,\n options: {\n /**\n * Whether to throw an error if the token is invalid.\n * @default true\n */\n shouldThrow: false\n },\n): string | null\n\nexport function getProvisioningApiBaseEndpoint(\n accessToken: string,\n options: {\n shouldThrow?: boolean\n } = {},\n): string | null {\n const { shouldThrow = true } = options\n const decodedJWT = jwtDecode(accessToken)\n\n if (!decodedJWT?.payload?.scope?.includes(\"provisioning-api\")) {\n if (shouldThrow) {\n throw new InvalidTokenError(\"Invalid token format\")\n }\n\n return null\n }\n\n return extractIssuer(decodedJWT).replace(\"auth\", \"provisioning\")\n}\n"],"mappings":"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","names":["index_exports","__export","InvalidTokenError","TokenError","TokenExpiredError","authenticate","createAssertion","getCoreApiBaseEndpoint","getProvisioningApiBaseEndpoint","jwtDecode","jwtIsDashboard","jwtIsIntegration","jwtIsSalesChannel","jwtIsUser","jwtIsWebApp","jwtVerify","revoke","__toCommonJS","camelCaseToSnakeCase","str","letter","mapKeys","obj","fn","acc","key","camelKey","snakeCaseToCamelCase","str","group","authenticate","grantType","domain","headers","options","body","mapKeys","camelCaseToSnakeCase","json","snakeCaseToCamelCase","TokenError","message","InvalidTokenError","TokenError","message","encodeBase64URLSafe","stringToEncode","encoding","utf8String","utf8Bytes","decodeBase64URLSafe","encodedData","decoded","byteArray","char","jwtDecode","accessToken","encodedHeader","encodedPayload","signature","InvalidTokenError","decodeBase64URLSafe","jwtIsUser","payload","jwtIsDashboard","jwtIsIntegration","jwtIsSalesChannel","jwtIsWebApp","extractIssuer","decodedJWT","revoke","options","body","mapKeys","camelCaseToSnakeCase","decodedJWT","jwtDecode","issuer","extractIssuer","TokenExpiredError","TokenError","jwtVerify","accessToken","ignoreExpiration","jwk","decodedJWT","jwtDecode","jsonWebKey","getJsonWebKey","InvalidTokenError","TokenExpiredError","algorithm","publicKey","rawSignature","decodeBase64URLSafe","c","rawData","JWKSCache","jwt","kid","keys","getJsonWebKeys","key","jwksUrl","extractIssuer","response","res","TokenError","createAssertion","payload","jwtEncode","secret","encodedHeader","encodeBase64URLSafe","encodedPayload","unsignedToken","signature","createSignature","data","enc","algorithm","key","getCoreApiBaseEndpoint","accessToken","options","shouldThrow","decodedJWT","jwtDecode","InvalidTokenError","extractIssuer","getProvisioningApiBaseEndpoint","accessToken","options","shouldThrow","decodedJWT","jwtDecode","InvalidTokenError","extractIssuer"]}