diff --git a/README.md b/README.md deleted file mode 100644 index 011674d..0000000 --- a/README.md +++ /dev/null @@ -1,253 +0,0 @@ -# Commerce Layer JS Auth - -A JavaScript Library wrapper that helps you use the Commerce Layer API for [Authentication](https://docs.commercelayer.io/developers/authentication). - -## What is Commerce Layer? - -[Commerce Layer](https://commercelayer.io) is a multi-market commerce API and order management system that lets you add global shopping capabilities to any website, mobile app, chatbot, wearable, voice, or IoT device, with ease. Compose your stack with the best-of-breed tools you already mastered and love. Make any experience shoppable, anywhere, through a blazing-fast, enterprise-grade, and secure API. - -## Table of contents - -- [Getting started](#getting-started) - - [Installation](#installation) - - [Using E6 import](#using-es6-import) -- [Authorization flows](#authorization-flows) -- [Use cases](#use-cases) - - [Sales channel application with client credentials flow](#sales-channel-client-credentials) - - [Sales channel application with password flow](#sales-channel-password) - - [Integration application with client credentials flow](#integration-client-credentials) - - [Webapp application with authorization code flow](#webapp-authorization-code) - - [Provisioning application](#provisioning) -- [Contributors guide](#contributors-guide) -- [Need help?](#need-help) -- [License](#license) - ---- - -## Getting started - -To get started with Commerce Layer JS Auth, you need to install it and add it to your project. - -### Installation - -Commerce Layer JS Auth is available as an npm package. - -```bash -# npm -npm install @commercelayer/js-auth - -# yarn -yarn add @commercelayer/js-auth - -# pnpm -pnpm add @commercelayer/js-auth -``` - -## Authorization flows - -To get an access token, you need to execute an [OAuth 2.0](https://oauth.net/2/) authorization flow by using a valid application as the client. - -| Grant type | Sales channel | Integration | Webapp | -| ---------------------- | ------------- | ----------- | ------ | -| **Client credentials** | ✅ | ✅ | | -| **Password** | ✅ | | | -| **Refresh token** | ✅ | | ✅ | -| **Authorization code** | | | ✅ | - -> Remember that, for security reasons, access tokens expire after **2 hours**. Authorization codes expire after **10 minutes**. - -Check our [documentation](https://docs.commercelayer.io/developers/authentication) for further information on each single authorization flow. - -## Use cases - -Based on the authorization flow and application you want to use, you can get your access token in a few simple steps. These are the most common use cases: - -- [Sales channel application with client credentials flow](#sales-channel-client-credentials) -- [Sales channel application with password flow](#sales-channel-password) -- [Integration application with client credentials flow](#integration-client-credentials) -- [Webapp application with authorization code flow](#webapp-authorization-code) -- [Provisioning application](#provisioning) - -### Sales channel (client credentials) - -Sales channel applications use the [client credentials](https://docs.commercelayer.io/developers/authentication/client-credentials) grant type to get a "guest" access token. - -#### Steps - -1. Create a **sales channel** application on Commerce Layer and take note of your API credentials (base endpoint, client ID, and the ID of the market you want to put in scope) - -2. Use this code to get your access token: - -```ts -import { core } from '@commercelayer/js-auth' - -const token = await core.authentication('client_credentials', { - clientId: 'your-client-id', - slug: 'your-organization-slug', - scope: 'market:{id}' -}) - -console.log('My access token: ', token.accessToken) -console.log('Expiration date: ', token.expires) -``` - -### Sales channel (password) - -Sales channel applications can use the [password](https://docs.commercelayer.io/developers/authentication/password) grant type to exchange a customer credentials for an access token (i.e., to get a "logged" access token). - -#### Steps - -1. Create a **sales channel** application on Commerce Layer and take note of your API credentials (base endpoint, client ID, and the ID of the market you want to put in scope) - -2. Use this code (changing user name and password with the customer credentials) to get the access token: - -```ts -import { core } from '@commercelayer/js-auth' - -const token = await core.authentication('password', { - clientId: 'your-client-id', - slug: 'your-organization-slug', - scope: 'market:{id}', - username: 'john@example.com', - password: 'secret' -}) - -console.log('My access token: ', token.accessToken) -console.log('Expiration date: ', token.expires) -console.log('My refresh token: ', token.refreshToken) -``` - -Sales channel applications can use the [refresh token](https://docs.commercelayer.io/developers/authentication/refresh-token) grant type to refresh a customer access token with a "remember me" option: - -```ts -import { core } from '@commercelayer/js-auth' - -const newToken = await core.authentication('refresh_token', { - clientId: 'your-client-id', - slug: 'your-organization-slug', - scope: 'market:{id}', - refreshToken: 'your-refresh-token' -}) -``` - -### Integration (client credentials) - -Integration applications use the [client credentials](https://docs.commercelayer.io/developers/authentication/client-credentials) grant type to get an access token for themselves. - -#### Steps - -1. Create an **integration** application on Commerce Layer and take note of your API credentials (client ID, client secret, and base endpoint) - -2. Use this codes to get the access token: - -```ts -import { core } from '@commercelayer/js-auth' - -const token = await core.authentication('client_credentials', { - clientId: 'your-client-id', - clientSecret: 'your-client-secret', - slug: 'your-organization-slug', -}) - -console.log('My access token: ', token.accessToken) -console.log('Expiration date: ', token.expires) -``` - -### Webapp (authorization code) - -> Available only for browser applications - -Webapp applications use the [authorization code](https://docs.commercelayer.io/developers/authentication/authorization-code) grant type to exchange an authorization code for an access token. - -#### Steps - -In this case, first, you need to get an authorization code, then you can exchange it with an access token: - -1. Create a **webapp** application on Commerce Layer and take note of your API credentials (client ID, client secret, callback URL, base endpoint, and the ID of the market you want to put in scope) - -2. Use this code to authorize your webapp on Commerce Layer: - - ```bash - curl -g -X GET \ - 'https://dashboard.commercelayer.io/oauth/authorize?client_id=your-client-id&redirect_uri=https://yourdomain.com/redirect&scope=market:1234&response_type=code' \ - -H 'Accept: application/json' \ - -H 'Content-Type: application/json' - ``` - - or copy and paste this URL in your browser: - - ```bash - https://dashboard.commercelayer.io/oauth/authorize?client_id=your-client-id&redirect_uri=https://yourdomain.com/redirect&scope=market:1234&response_type=code - ``` - -3. Once you've authorized the application, you will be redirected to the callback URL: - - ![Callback URL with Authorization Code](./images/auth-code-browser.jpg) - - Use this code to get the access token: - - ```ts - import { core } from '@commercelayer/js-auth' - - const token = await core.authentication('authorization_code', { - clientId: 'your-client-id', - clientSecret: 'your-client-secret', - callbackUrl: '', - slug: 'your-organization-slug', - scope: 'market:{id}', - code: 'your-auth-code' - }) - - console.log('My access token: ', token.accessToken) - console.log('Expiration date: ', token.expires) - ``` - -### Provisioning - -Provisioning applications use a specific authentication function which implicitly uses the [client credentials](https://docs.commercelayer.io/developers/authentication/client-credentials) grant type to get an access token. - -#### Steps - -1. Access your personal [provisioning](https://dashboard.commercelayer.io/user/provisioning_api) application on Commerce Layer dashboard and take note of your Provisioning API credentials (client ID, client secret) - -2. Use this codes to get the access token: - -```ts -import { provisioning } from '@commercelayer/js-auth' - -const token = await provisioning.authentication({ - clientId: 'your-client-id', - clientSecret: 'your-client-secret' -}) - -console.log('My access token: ', token.accessToken) -console.log('Expiration date: ', token.expires) -``` - ---- - -## Contributors guide - -1. Fork [this repository](https://github.com/BolajiAyodeji/commercelayer-js-auth) (learn how to do this [here](https://help.github.com/articles/fork-a-repo)). - -2. Clone the forked repository like so: - -```bash -git clone https://github.com//commercelayer-js-auth.git && cd commercelayer-js-auth -``` - -3. Make your changes and create a pull request ([learn how to do this](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request)). - -4. Someone will attend to your pull request and provide some feedback. - -## Need help? - -1. Request an invite to join [Commerce Layer's Slack community](https://slack.commercelayer.app/). - -2. Create an [issue](https://github.com/commercelayer/commercelayer-js-auth/issues) in this repository. - -3. Ping us [on Twitter](https://twitter.com/commercelayer). - -## License - -This repository is published under the [MIT](LICENSE) license. diff --git a/README.md b/README.md new file mode 120000 index 0000000..0e3f35d --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +./packages/js-auth/README.md \ No newline at end of file diff --git a/images/auth-code-browser.jpg b/images/auth-code-browser.jpg deleted file mode 100644 index 063081d..0000000 Binary files a/images/auth-code-browser.jpg and /dev/null differ diff --git a/packages/js-auth/README.md b/packages/js-auth/README.md new file mode 100644 index 0000000..0a8baf4 --- /dev/null +++ b/packages/js-auth/README.md @@ -0,0 +1,253 @@ +# Commerce Layer JS Auth + +A JavaScript Library wrapper that helps you use the Commerce Layer API for [Authentication](https://docs.commercelayer.io/developers/authentication). + +## What is Commerce Layer? + +[Commerce Layer](https://commercelayer.io) is a multi-market commerce API and order management system that lets you add global shopping capabilities to any website, mobile app, chatbot, wearable, voice, or IoT device, with ease. Compose your stack with the best-of-breed tools you already mastered and love. Make any experience shoppable, anywhere, through a blazing-fast, enterprise-grade, and secure API. + +## Table of contents + +- [Getting started](#getting-started) + - [Installation](#installation) + - [Using E6 import](#using-es6-import) +- [Authorization flows](#authorization-flows) +- [Use cases](#use-cases) + - [Sales channel application with client credentials flow](#sales-channel-client-credentials) + - [Sales channel application with password flow](#sales-channel-password) + - [Integration application with client credentials flow](#integration-client-credentials) + - [Webapp application with authorization code flow](#webapp-authorization-code) + - [Provisioning application](#provisioning) +- [Contributors guide](#contributors-guide) +- [Need help?](#need-help) +- [License](#license) + +--- + +## Getting started + +To get started with Commerce Layer JS Auth, you need to install it and add it to your project. + +### Installation + +Commerce Layer JS Auth is available as an npm package. + +```bash +# npm +npm install @commercelayer/js-auth + +# yarn +yarn add @commercelayer/js-auth + +# pnpm +pnpm add @commercelayer/js-auth +``` + +## Authorization flows + +To get an access token, you need to execute an [OAuth 2.0](https://oauth.net/2/) authorization flow by using a valid application as the client. + +| Grant type | Sales channel | Integration | Webapp | +| ---------------------- | ------------- | ----------- | ------ | +| **Client credentials** | ✅ | ✅ | | +| **Password** | ✅ | | | +| **Refresh token** | ✅ | | ✅ | +| **Authorization code** | | | ✅ | + +> Remember that, for security reasons, access tokens expire after **2 hours**. Authorization codes expire after **10 minutes**. + +Check our [documentation](https://docs.commercelayer.io/developers/authentication) for further information on each single authorization flow. + +## Use cases + +Based on the authorization flow and application you want to use, you can get your access token in a few simple steps. These are the most common use cases: + +- [Sales channel application with client credentials flow](#sales-channel-client-credentials) +- [Sales channel application with password flow](#sales-channel-password) +- [Integration application with client credentials flow](#integration-client-credentials) +- [Webapp application with authorization code flow](#webapp-authorization-code) +- [Provisioning application](#provisioning) + +### Sales channel (client credentials) + +Sales channel applications use the [client credentials](https://docs.commercelayer.io/developers/authentication/client-credentials) grant type to get a "guest" access token. + +#### Steps + +1. Create a **sales channel** application on Commerce Layer and take note of your API credentials (base endpoint, client ID, and the ID of the market you want to put in scope) + +2. Use this code to get your access token: + +```ts +import { core } from '@commercelayer/js-auth' + +const token = await core.authentication('client_credentials', { + clientId: 'your-client-id', + slug: 'your-organization-slug', + scope: 'market:{id}' +}) + +console.log('My access token: ', token.accessToken) +console.log('Expiration date: ', token.expires) +``` + +### Sales channel (password) + +Sales channel applications can use the [password](https://docs.commercelayer.io/developers/authentication/password) grant type to exchange a customer credentials for an access token (i.e., to get a "logged" access token). + +#### Steps + +1. Create a **sales channel** application on Commerce Layer and take note of your API credentials (base endpoint, client ID, and the ID of the market you want to put in scope) + +2. Use this code (changing user name and password with the customer credentials) to get the access token: + +```ts +import { core } from '@commercelayer/js-auth' + +const token = await core.authentication('password', { + clientId: 'your-client-id', + slug: 'your-organization-slug', + scope: 'market:{id}', + username: 'john@example.com', + password: 'secret' +}) + +console.log('My access token: ', token.accessToken) +console.log('Expiration date: ', token.expires) +console.log('My refresh token: ', token.refreshToken) +``` + +Sales channel applications can use the [refresh token](https://docs.commercelayer.io/developers/authentication/refresh-token) grant type to refresh a customer access token with a "remember me" option: + +```ts +import { core } from '@commercelayer/js-auth' + +const newToken = await core.authentication('refresh_token', { + clientId: 'your-client-id', + slug: 'your-organization-slug', + scope: 'market:{id}', + refreshToken: 'your-refresh-token' +}) +``` + +### Integration (client credentials) + +Integration applications use the [client credentials](https://docs.commercelayer.io/developers/authentication/client-credentials) grant type to get an access token for themselves. + +#### Steps + +1. Create an **integration** application on Commerce Layer and take note of your API credentials (client ID, client secret, and base endpoint) + +2. Use this codes to get the access token: + +```ts +import { core } from '@commercelayer/js-auth' + +const token = await core.authentication('client_credentials', { + clientId: 'your-client-id', + clientSecret: 'your-client-secret', + slug: 'your-organization-slug', +}) + +console.log('My access token: ', token.accessToken) +console.log('Expiration date: ', token.expires) +``` + +### Webapp (authorization code) + +> Available only for browser applications + +Webapp applications use the [authorization code](https://docs.commercelayer.io/developers/authentication/authorization-code) grant type to exchange an authorization code for an access token. + +#### Steps + +In this case, first, you need to get an authorization code, then you can exchange it with an access token: + +1. Create a **webapp** application on Commerce Layer and take note of your API credentials (client ID, client secret, callback URL, base endpoint, and the ID of the market you want to put in scope) + +2. Use this code to authorize your webapp on Commerce Layer: + + ```bash + curl -g -X GET \ + 'https://dashboard.commercelayer.io/oauth/authorize?client_id=your-client-id&redirect_uri=https://yourdomain.com/redirect&scope=market:1234&response_type=code' \ + -H 'Accept: application/json' \ + -H 'Content-Type: application/json' + ``` + + or copy and paste this URL in your browser: + + ```bash + https://dashboard.commercelayer.io/oauth/authorize?client_id=your-client-id&redirect_uri=https://yourdomain.com/redirect&scope=market:1234&response_type=code + ``` + +3. Once you've authorized the application, you will be redirected to the callback URL: + + ![Callback URL with Authorization Code](https://github.com/commercelayer/commercelayer-js-auth/assets/1681269/2a167283-a3a9-4d16-bb8f-a962a5fe8900) + + Use this code to get the access token: + + ```ts + import { core } from '@commercelayer/js-auth' + + const token = await core.authentication('authorization_code', { + clientId: 'your-client-id', + clientSecret: 'your-client-secret', + callbackUrl: '', + slug: 'your-organization-slug', + scope: 'market:{id}', + code: 'your-auth-code' + }) + + console.log('My access token: ', token.accessToken) + console.log('Expiration date: ', token.expires) + ``` + +### Provisioning + +Provisioning applications use a specific authentication function which implicitly uses the [client credentials](https://docs.commercelayer.io/developers/authentication/client-credentials) grant type to get an access token. + +#### Steps + +1. Access your personal [provisioning](https://dashboard.commercelayer.io/user/provisioning_api) application on Commerce Layer dashboard and take note of your Provisioning API credentials (client ID, client secret) + +2. Use this codes to get the access token: + +```ts +import { provisioning } from '@commercelayer/js-auth' + +const token = await provisioning.authentication({ + clientId: 'your-client-id', + clientSecret: 'your-client-secret' +}) + +console.log('My access token: ', token.accessToken) +console.log('Expiration date: ', token.expires) +``` + +--- + +## Contributors guide + +1. Fork [this repository](https://github.com/BolajiAyodeji/commercelayer-js-auth) (learn how to do this [here](https://help.github.com/articles/fork-a-repo)). + +2. Clone the forked repository like so: + +```bash +git clone https://github.com//commercelayer-js-auth.git && cd commercelayer-js-auth +``` + +3. Make your changes and create a pull request ([learn how to do this](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request)). + +4. Someone will attend to your pull request and provide some feedback. + +## Need help? + +1. Request an invite to join [Commerce Layer's Slack community](https://slack.commercelayer.app/). + +2. Create an [issue](https://github.com/commercelayer/commercelayer-js-auth/issues) in this repository. + +3. Ping us [on Twitter](https://twitter.com/commercelayer). + +## License + +This repository is published under the [MIT](LICENSE) license. diff --git a/packages/js-auth/specs/provisioning.spec.ts b/packages/js-auth/specs/provisioning.spec.ts index 3cba66b..71b3f63 100644 --- a/packages/js-auth/specs/provisioning.spec.ts +++ b/packages/js-auth/specs/provisioning.spec.ts @@ -11,7 +11,7 @@ describe('Provisioning', () => { clientId, clientSecret }) - + expect(res).toHaveProperty('accessToken') expect(res).toHaveProperty('tokenType') expect(res).toHaveProperty('expiresIn') diff --git a/packages/js-auth/src/core.ts b/packages/js-auth/src/core.ts index 5548122..d7b7c12 100644 --- a/packages/js-auth/src/core.ts +++ b/packages/js-auth/src/core.ts @@ -1,37 +1,17 @@ -import type { TReturn, GrantType, TOptions } from '#types/index.js' -import { camelCaseToSnake } from '#utils/camelCaseToSnake.js' -import { snakeToCamelCase } from '#utils/snakeToCamelCase.js' -import type { TokenJson } from './provisioning.js' +import type { GrantType, TOptions, TReturn } from '#types/index.js' +import { doRequest } from '#utils/doRequest.js' async function authentication( grantType: G, { domain = 'commercelayer.io', slug, headers, ...options }: TOptions ): Promise> { - const attributes = { - grant_type: grantType, - ...options - } - const body = Object.keys(attributes).reduce((acc: any, key) => { - const camelKey = camelCaseToSnake(key) - acc[camelKey] = attributes[key as keyof typeof attributes] - return acc - }, {}) - return await fetch(`https://${slug}.${domain}/oauth/token`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - ...headers + return await doRequest({ + attributes: { + grant_type: grantType, + ...options }, - body: JSON.stringify(body) - }).then(async (response) => { - const json: TokenJson = await response.json() - json.expires = new Date(Date.now() + json.expires_in * 1000) - return Object.keys(json).reduce((acc: any, key) => { - const camelKey = snakeToCamelCase(key) - acc[camelKey] = json[key] - return acc - }, {}) as TReturn + endpoint: `https://${slug}.${domain}/oauth/token`, + headers }) } diff --git a/packages/js-auth/src/provisioning.ts b/packages/js-auth/src/provisioning.ts index 639e65c..ef7a42d 100644 --- a/packages/js-auth/src/provisioning.ts +++ b/packages/js-auth/src/provisioning.ts @@ -1,49 +1,23 @@ import type { TClientCredentials } from '#types/clientCredentials.js' -import { camelCaseToSnake } from '#utils/camelCaseToSnake.js' -import { snakeToCamelCase } from '#utils/snakeToCamelCase.js' import type { TBaseReturn } from '#types/index.js' +import { doRequest } from '#utils/doRequest.js' export type TProvisioningOptions = Omit export type TProvisioningReturn = TBaseReturn -export interface TokenJson { - expires: Date - expires_in: number - [key: string]: string | number | Date -} async function authentication({ domain = 'commercelayer.io', headers, ...options }: TProvisioningOptions): Promise { - const attributes = { - grant_type: 'client_credentials', - scope: 'provisioning-api', - ...options - } - - const body = Object.keys(attributes).reduce((acc: any, key) => { - const camelKey = camelCaseToSnake(key) - acc[camelKey] = attributes[key as keyof typeof attributes] - return acc - }, {}) - - return await fetch(`https://auth.${domain}/oauth/token`, { - method: 'POST', - headers: { - 'Content-Type': 'application/vnd.api+json', - Accept: 'application/vnd.api+json', - ...headers + return await doRequest({ + attributes: { + grant_type: 'client_credentials', + scope: 'provisioning-api', + ...options }, - body: JSON.stringify(body) - }).then(async (response) => { - const json: TokenJson = await response.json() - json.expires = new Date(Date.now() + json.expires_in * 1000) - return Object.keys(json).reduce((acc: any, key) => { - const camelKey = snakeToCamelCase(key) - acc[camelKey] = json[key] - return acc - }, {}) as TProvisioningReturn + endpoint: `https://auth.${domain}/oauth/token`, + headers }) } diff --git a/packages/js-auth/src/utils/doRequest.ts b/packages/js-auth/src/utils/doRequest.ts new file mode 100644 index 0000000..9672932 --- /dev/null +++ b/packages/js-auth/src/utils/doRequest.ts @@ -0,0 +1,42 @@ +import { camelCaseToSnake } from './camelCaseToSnake.js' +import { snakeToCamelCase } from './snakeToCamelCase.js' + +export interface TokenJson { + expires: Date + expires_in: number + [key: string]: string | number | Date +} + +export async function doRequest({ + attributes, + headers, + endpoint +}: { + attributes: Record + headers?: HeadersInit + endpoint: string +}): Promise { + const body = Object.keys(attributes).reduce((acc: any, key) => { + const camelKey = camelCaseToSnake(key) + acc[camelKey] = attributes[key] + return acc + }, {}) + + return await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + ...headers + }, + body: JSON.stringify(body) + }).then(async (response) => { + const json: TokenJson = await response.json() + json.expires = new Date(Date.now() + json.expires_in * 1000) + return Object.keys(json).reduce((acc: any, key) => { + const camelKey = snakeToCamelCase(key) + acc[camelKey] = json[key] + return acc + }, {}) + }) +} diff --git a/packages/js-auth/tsup.config.js b/packages/js-auth/tsup.config.js index be19e21..be675a0 100644 --- a/packages/js-auth/tsup.config.js +++ b/packages/js-auth/tsup.config.js @@ -3,11 +3,11 @@ import { defineConfig } from 'tsup' const env = process.env.NODE_ENV export default defineConfig(() => ({ - sourcemap: env === 'production', // source map is only available in prod + sourcemap: true, // source map is only available in prod clean: true, // clean dist before build dts: true, // generate dts file for main module format: ['cjs', 'esm'], // generate cjs and esm files - minify: env === 'production', + minify: true, bundle: true, watch: env === 'development', target: 'es2020',