UNPKG

@commercelayer/js-auth

Version:

A JavaScript library designed to simplify authentication when interacting with the Commerce Layer API.

585 lines (567 loc) 21.6 kB
interface TBaseOptions { /** * The application's client_id. */ clientId: string; /** * [The access token scope](https://docs.commercelayer.io/core/authentication#authorization-scopes) (market, store, and/or stock location). * * The access token scope is a string that can be composed in two ways: * * by **ID** — `{{resource_name}}:id:{{resource_id}}`\ * Where `{{resource_name}}` can be one of market, store, or stock_location, and `{{resource_id}}` is the id of the resource (e.g. `market:id:xYZkjABcde`, `store:id:bGvCXzYgNB`, `stock_location:id:WLgbSXqyoZ`).\ * * * by **code** — `{{resource_name}}:code:{{resource_code}}` * Where `{{resource_name}}` can be one of market, store, or stock_location, and `{{resource_code}}` is the code of the resource (e.g. `market:code:europe`, `store:code:outlet_ny`, `stock_location:code:eu_warehouse`). */ scope?: string; /** * The Commerce Layer's domain. */ domain?: string; /** * An optional object containing the HTTP request headers to be sent with the request. */ headers?: { /** * Optional header for backend authentication. This key is mandatory for using the `x-true-client-ip` header * to forward the client IP address, which helps in managing rate limits effectively by identifying * unique client requests. * * **Note: This is an enterprise feature.** */ "x-backend-auth"?: string; /** * Optional header to forward the client IP address in server-side requests. Its use requires the presence of * the `x-backend-auth` header for authentication. This approach helps to differentiate between client requests * and manage rate limits based on individual client IP addresses rather than the server's IP address alone. * * **Note: This is an enterprise feature.** */ "x-true-client-ip"?: string; /** * Allows for additional headers as needed, where each key is the header name and the value is the header content. * Header values should be strings, or `undefined` if the header is not set. */ [key: string]: string | undefined; }; } type TBaseReturn = { /** * The access token. */ accessToken: string; /** * The token type. */ tokenType: "bearer"; /** * The access token expiration time in seconds. */ expiresIn: number; /** * The access token expiration date. */ expires: Date; /** * [The access token scope](https://docs.commercelayer.io/core/authentication#authorization-scopes) (market, store, and/or stock location). */ scope: string; /** * The creation date of the access token. */ createdAt: number; } & TError; interface TError { /** * The list of errors when something goes wrong. */ errors?: Array<{ code: string; detail: string; meta: Record<string, unknown>; status: 401; title: string; }>; } /** * The password grant type is used by first-party clients to exchange a user's credentials for an access token. * @see https://docs.commercelayer.io/core/authentication/password */ interface TPasswordOptions extends TBaseOptions { /** The customer's email address. */ username: string; /** The customer's password */ password: string; } /** * The password grant type is used by first-party clients to exchange a user's credentials for an access token. * @see https://docs.commercelayer.io/core/authentication/password */ interface TPasswordReturn extends TBaseReturn { ownerId: string; ownerType: "customer"; refreshToken: string; } /** * The authorization code grant type is used by clients to exchange an authorization code for an access token. * @see https://docs.commercelayer.io/core/authentication/authorization-code#getting-an-access-token */ interface TAuthorizationCodeOptions extends TBaseOptions { /** * The authorization code that [you got](https://docs.commercelayer.io/core/authentication/authorization-code#getting-an-authorization-code) from the redirect URI query string. */ code: string; /** * Your application's redirect URI. */ redirectUri: string; /** * Your application's client secret. */ clientSecret: string; } interface TAuthorizationCodeReturn extends Omit<TPasswordReturn, "ownerType"> { ownerType: "user"; } /** * The client credentials grant type is used by clients to obtain an access token outside of the context of a user. * @see https://docs.commercelayer.io/core/authentication/client-credentials */ interface TClientCredentialsOptions extends TBaseOptions { /** * Your application's client secret * (required for [confidential](https://docs.commercelayer.io/core/authentication/client-credentials#integration) API credentials). */ clientSecret?: string; } /** * Commerce Layer, through OAuth2, provides the support of token exchange in the on-behalf-of (delegation) scenario which allows, * for example, to make calls on behalf of a user and get an access token of the requesting user without direct user interaction. * Sales channels and webapps can accomplish it by leveraging the JWT Bearer flow, * which allows a client application to obtain an access token using a JSON Web Token (JWT) assertion. * @see https://docs.commercelayer.io/core/authentication/jwt-bearer */ interface TJwtBearerOptions extends TBaseOptions { /** Your application's client secret. */ clientSecret: string; /** * A single JSON Web Token ([learn more](https://docs.commercelayer.io/core/authentication/jwt-bearer#creating-the-jwt-assertion)). * Max size is 4KB. * * **You can use the `createAssertion` helper method**. * * @example * ```ts * import { createAssertion } from '@commercelayer/js-auth' * ``` */ assertion: string; } interface TJwtBearerReturn extends Omit<TPasswordReturn, "ownerType"> { ownerType: "user" | "customer"; } /** * The refresh token grant type is used by clients to exchange a refresh token for an access token when the access token has expired. * @see https://docs.commercelayer.io/core/authentication/refresh-token */ interface TRefreshTokenOptions extends TBaseOptions { /** * A valid `refresh_token`. */ refreshToken: string; /** * Your application's client secret * (required for confidential API credentials — i.e. in case of [authorization code flow](https://docs.commercelayer.io/core/authentication/refresh-token#webapp-application-with-authorization-code-flow)). */ clientSecret?: string; } /** * The type of OAuth 2.0 grant being used for authentication. */ type GrantType = "password" | "refresh_token" | "client_credentials" | "authorization_code" | "urn:ietf:params:oauth:grant-type:jwt-bearer"; /** The options type for the `authenticate` helper. */ type AuthenticateOptions<TGrantType extends GrantType> = TGrantType extends "urn:ietf:params:oauth:grant-type:jwt-bearer" ? TJwtBearerOptions : TGrantType extends "password" ? TPasswordOptions : TGrantType extends "refresh_token" ? TRefreshTokenOptions : TGrantType extends "client_credentials" ? TClientCredentialsOptions : TGrantType extends "authorization_code" ? TAuthorizationCodeOptions : never; /** The return type of the `authenticate` helper. */ type AuthenticateReturn<TGrantType extends GrantType> = TGrantType extends "urn:ietf:params:oauth:grant-type:jwt-bearer" ? TJwtBearerReturn : TGrantType extends "password" ? TPasswordReturn : TGrantType extends "refresh_token" ? TPasswordReturn : TGrantType extends "client_credentials" ? TBaseReturn : TGrantType extends "authorization_code" ? TAuthorizationCodeReturn : never; /** The options type for the `revoke` helper. */ type RevokeOptions = Pick<TBaseOptions, "clientId"> & { /** Your application's client secret (required for confidential API credentials and non-confidential API credentials without a customer or a user in the JWT only). */ clientSecret?: string; /** A valid access or refresh token. */ token: string; }; /** The return type of the `revoke` helper. */ type RevokeReturn = Pick<TError, "errors">; /** * Authenticate helper used to get the access token. * * _Please note that the authentication endpoint is subject to a [rate limit](https://docs.commercelayer.io/core/rate-limits) * of **max 30 reqs / 1 min** both in live and test mode._ * @param grantType The type of OAuth 2.0 grant being used for authentication. * @param options Authenticate options * @returns * @example * ```ts * import { authenticate } from '@commercelayer/js-auth' * * const auth = await authenticate('client_credentials', { * clientId: '{{ clientId }}', * scope: 'market:id:DGzAouppwn' * }) * * console.log(auth.accessToken) * ``` */ declare function authenticate<TGrantType extends GrantType>(grantType: TGrantType, { domain, headers, ...options }: AuthenticateOptions<TGrantType>): Promise<AuthenticateReturn<TGrantType>>; /** * Revoke a previously generated access token (refresh tokens included) before its natural expiration date. * * @param options Revoke options * @returns * @example * ```ts * await revoke({ * clientId: '{{ integrationClientId }}', * clientSecret: '{{ integrationClientSecret }}', * token: authenticateResponse.accessToken * }) * ``` */ declare function revoke(options: RevokeOptions): Promise<RevokeReturn>; /** * Decode a Commerce Layer access token without verifying if the signature is valid. * * _You should not use this for untrusted messages, since this helper method does not verify whether the signature is valid. * If you need to verify the access token before decoding, you can use `jwtVerify` instead._ */ declare function jwtDecode(accessToken: string): CommerceLayerJWT; interface CommerceLayerJWT { /** 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. */ header: { /** Signing algorithm being used (e.g. `HMAC`, `SHA256`, `RSA`, `RS512`). */ alg: string; /** Type of the token (usually `JWT`). */ typ?: string; /** Key ID */ kid: string; }; payload: Payload; signature: string; } type Payload = JWTDashboard | JWTUser | JWTSalesChannel | JWTIntegration | JWTWebApp; interface JWTBase { /** The type of credentials you're using to authenticate to the APIs. */ application: { id: string; public: boolean; client_id: string; }; /** Scope used to restrict access to a specific active market and/or stock location. */ scope: string; /** The token expiration time, expressed as an [epoch](https://www.epoch101.com/). */ exp: number; /** The environment type (true for test mode, false for live mode). */ test: boolean; /** A randomly generated number, less than one. */ rand: number; /** Issued at (seconds since Unix epoch). */ iat: number; /** Who created and signed this token (e.g. `"https://auth.commercelayer.io"`). */ iss: string; } /** * A JWT payload that represents a `user`. */ type JWTUser = JWTBase & { /** The type of credentials you're using to authenticate to the APIs. */ application: { kind: "user"; }; /** The authenticated user. */ user: { id: string; }; }; /** * A JWT payload that represents a `dashboard`. */ type JWTDashboard = JWTBase & { /** The type of credentials you're using to authenticate to the APIs. */ application: { kind: "dashboard"; }; /** The authenticated user. */ user: { id: string; }; }; type JWTOrganizationBase = JWTBase & { /** The organization in scope. */ organization: { id: string; slug: string; enterprise: boolean; region: string; }; /** The owner (if any) authenticating to the APIs. */ owner?: { id: string; type: "Customer" | "User"; }; /** * Any other information (key/value pairs) you want to enrich the token with, * when using the [JWT Bearer flow](https://docs.commercelayer.io/core/authentication/jwt-bearer). */ custom_claim?: Record<string, string>; /** * The market(s) in scope. * This is available only when the scope is defined in the request. */ market?: { id: string[]; stock_location_ids: string[]; geocoder_id: string | null; allows_external_prices: boolean; }; }; /** * A JWT payload that represents a `webapp`. */ type JWTWebApp = SetRequired<JWTOrganizationBase, "owner"> & { /** The type of credentials you're using to authenticate to the APIs. */ application: { kind: "webapp"; }; }; /** Create a type that makes the given keys required. The remaining keys are kept as is. */ type SetRequired<T, K extends keyof T> = T & { [P in K]-?: T[P]; }; /** * A JWT payload that represents a `sales_channel`. */ type JWTSalesChannel = JWTOrganizationBase & { /** The type of credentials you're using to authenticate to the APIs. */ application: { kind: "sales_channel"; }; }; /** * A JWT payload that represents an `integration`. */ type JWTIntegration = JWTOrganizationBase & { /** The type of credentials you're using to authenticate to the APIs. */ application: { kind: "integration"; }; }; /** * Checks if the provided payload represents a `user`. * @param payload The payload to be checked. * @returns */ declare function jwtIsUser(payload: Payload): payload is JWTUser; /** * Checks if the provided payload represents a `dashboard`. * @param payload The payload to be checked. * @returns */ declare function jwtIsDashboard(payload: Payload): payload is JWTDashboard; /** * Checks if the provided payload represents an `integration`. * @param payload The payload to be checked. * @returns */ declare function jwtIsIntegration(payload: Payload): payload is JWTIntegration; /** * Checks if the provided payload represents a `sales_channel`. * @param payload The payload to be checked. * @returns */ declare function jwtIsSalesChannel(payload: Payload): payload is JWTSalesChannel; /** * Checks if the provided payload represents a `webapp`. * @param payload The payload to be checked. * @returns */ declare function jwtIsWebApp(payload: Payload): payload is JWTWebApp; /** * Verify a Commerce Layer access token. * When the verification succeeds, it resolves to the decoded access token, it rejects otherwise. */ declare function jwtVerify(accessToken: string, { ignoreExpiration, jwk }?: JwtVerifyOptions): Promise<CommerceLayerJWT>; type CommerceLayerJsonWebKey = JsonWebKey & { kid: string; }; interface JwtVerifyOptions { /** * Do not validate the token expiration when set to `true`. * @default false */ ignoreExpiration?: boolean; /** * Json Web Key used to verify the signature. * * The `kid` must match the `kid` from decoded accessToken. * * By default, we pick the jwk from https://auth.commercelayer.io/.well-known/jwks.json using the `kid` from the accessToken. */ jwk?: CommerceLayerJsonWebKey; } interface Owner { type: "User" | "Customer"; id: string; } /** * Create a JWT assertion as the first step of the [JWT bearer token authorization grant flow](https://docs.commercelayer.io/core/authentication/jwt-bearer). * * The JWT assertion is a digitally signed JSON object containing information * about the client and the user on whose behalf the access token is being requested. * * This JWT assertion can include information such as the issuer (typically the client), * the owner (the user on whose behalf the request is made), and any other relevant claims. * * @example * ```ts * const assertion = await createAssertion({ * payload: { * 'https://commercelayer.io/claims': { * owner: { * type: 'Customer', * id: '4tepftJsT2' * }, * custom_claim: { * customer: { * first_name: 'John', * last_name: 'Doe' * } * } * } * } * }) * ``` */ declare function createAssertion({ payload }: Assertion): Promise<string>; /** RequireAtLeastOne helps create a type where at least one of the properties of an interface (can be any property) is required to exist. */ type RequireAtLeastOne<T> = { [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>; }[keyof T]; interface Assertion { /** Assertion payload. */ payload: { /** At least one of `owner` or `custom_claim` is required. You cannot use an empty object. */ "https://commercelayer.io/claims": RequireAtLeastOne<{ /** The customer or user you want to make the calls on behalf of. */ owner?: Owner; /** Any other information (key/value pairs) you want to enrich the token with. */ custom_claim?: Record<string, unknown>; }>; }; } /** * Derives the [Core API base endpoint](https://docs.commercelayer.io/core/api-specification#base-endpoint) given a valid access token. * * @example * ```ts * getCoreApiBaseEndpoint('eyJhbGciOiJS...') //= "https://yourdomain.commercelayer.io" * ``` * * The method requires a valid access token with an `organization` in the payload. * * @param accessToken - The access token to decode. * @param options - An options object to configure behavior. * @returns The core API base endpoint as a string, or `null` if the token is invalid and `shouldThrow` is `false`. * @throws InvalidTokenError - If the token is invalid and `shouldThrow` is true. */ declare function getCoreApiBaseEndpoint(accessToken: string, options?: { /** * Whether to throw an error if the token is invalid. * @default true */ shouldThrow?: true; }): string; /** * Derives the [Core API base endpoint](https://docs.commercelayer.io/core/api-specification#base-endpoint) given a valid access token. * * @example * ```ts * getCoreApiBaseEndpoint('eyJhbGciOiJS...') //= "https://yourdomain.commercelayer.io" * ``` * * The method requires a valid access token with an `organization` in the payload. * * @param accessToken - The access token to decode. * @param options - An options object to configure behavior. * @returns The core API base endpoint as a string, or `null` if the token is invalid and `shouldThrow` is `false`. * @throws InvalidTokenError - If the token is invalid and `shouldThrow` is true. */ declare function getCoreApiBaseEndpoint(accessToken: string, options: { /** * Whether to throw an error if the token is invalid. * @default true */ shouldThrow: false; }): string | null; /** * Returns the [Provisioning API base endpoint](https://docs.commercelayer.io/provisioning/getting-started/api-specification#base-endpoint) given a valid access token. * * @example * ```ts * getProvisioningApiBaseEndpoint('eyJhbGciOiJS...') //= "https://provisioning.commercelayer.io" * ``` * * The method requires a valid access token for Provisioning API. * * @param accessToken - The access token to decode. * @param options - An options object to configure behavior. * @returns The provisioning API base endpoint as a string, or `null` if the token is invalid and `shouldThrow` is `false`. * @throws InvalidTokenError - If the token is invalid and `shouldThrow` is true. */ declare function getProvisioningApiBaseEndpoint(accessToken: string, options?: { /** * Whether to throw an error if the token is invalid. * @default true */ shouldThrow?: true; }): string; /** * Returns the [Provisioning API base endpoint](https://docs.commercelayer.io/provisioning/getting-started/api-specification#base-endpoint) given a valid access token. * * @example * ```ts * getProvisioningApiBaseEndpoint('eyJhbGciOiJS...') //= "https://provisioning.commercelayer.io" * ``` * * The method requires a valid access token for Provisioning API. * * @param accessToken - The access token to decode. * @param options - An options object to configure behavior. * @returns The provisioning API base endpoint as a string, or `null` if the token is invalid and `shouldThrow` is `false`. * @throws InvalidTokenError - If the token is invalid and `shouldThrow` is true. */ declare function getProvisioningApiBaseEndpoint(accessToken: string, options: { /** * Whether to throw an error if the token is invalid. * @default true */ shouldThrow: false; }): string | null; /** * A token error occurred. */ declare class TokenError extends Error { constructor(message: string); } /** * The token is not valid. */ declare class InvalidTokenError extends TokenError { constructor(message: string); } /** * The token expired. */ declare class TokenExpiredError extends TokenError { constructor(); } export { type AuthenticateOptions, type AuthenticateReturn, type GrantType, InvalidTokenError, type JWTDashboard, type JWTIntegration, type JWTSalesChannel, type JWTUser, type JWTWebApp, type RevokeOptions, type RevokeReturn, TokenError, TokenExpiredError, authenticate, createAssertion, getCoreApiBaseEndpoint, getProvisioningApiBaseEndpoint, jwtDecode, jwtIsDashboard, jwtIsIntegration, jwtIsSalesChannel, jwtIsUser, jwtIsWebApp, jwtVerify, revoke };