Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@sp-api-sdk/auth

Package Overview
Dependencies
Maintainers
1
Versions
118
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sp-api-sdk/auth - npm Package Compare versions

Comparing version
2.2.23
to
2.2.24
+7
-0
dist/cjs/error.js

@@ -5,3 +5,10 @@ "use strict";

const axios_1 = require("axios");
/**
* Error thrown when an LWA token request fails.
*
* Wraps the underlying Axios error with a human-readable message that includes
* the HTTP status code (or "No response" for network errors).
*/
class SellingPartnerApiAuthError extends axios_1.AxiosError {
/** The original error message from the failed HTTP request. */
innerMessage;

@@ -8,0 +15,0 @@ constructor(error) {

+53
-30

@@ -12,3 +12,7 @@ "use strict";

/**
* Class for simplify auth with Selling Partner API
* Handles Login with Amazon (LWA) OAuth token management for the Selling Partner API.
*
* Supports both refresh-token and grantless (scope-based) authentication flows.
* Tokens are cached and automatically refreshed when expired. Concurrent calls
* to {@link getAccessToken} are deduplicated into a single request.
*/

@@ -22,2 +26,3 @@ class SellingPartnerApiAuth {

#accessTokenExpiration;
#pendingTokenRequest;
constructor(parameters) {

@@ -42,38 +47,56 @@ const clientId = parameters.clientId ?? node_process_1.default.env.LWA_CLIENT_ID;

/**
* Get access token
* Returns a valid LWA access token, refreshing it if expired.
*
* Concurrent calls while a refresh is in progress share the same request.
*
* @returns The access token string.
*/
async getAccessToken() {
if (!this.#accessToken ||
(this.#accessTokenExpiration && Date.now() >= this.#accessTokenExpiration.getTime())) {
const body = {
client_id: this.clientId,
client_secret: this.clientSecret,
...(this.refreshToken
? {
grant_type: 'refresh_token',
refresh_token: this.refreshToken,
}
: {
grant_type: 'client_credentials',
scope: this.scopes.join(' '),
}),
};
try {
const expiration = new Date();
const { data } = await axios_2.axios.post('/o2/token', body);
expiration.setSeconds(expiration.getSeconds() + data.expires_in);
this.#accessToken = data.access_token;
this.#accessTokenExpiration = expiration;
}
catch (error) {
if (error instanceof axios_1.AxiosError) {
throw new error_1.SellingPartnerApiAuthError(error);
if (this.#accessToken &&
(!this.#accessTokenExpiration || Date.now() < this.#accessTokenExpiration.getTime())) {
return this.#accessToken;
}
// Deduplicate concurrent calls: share the same in-flight request
if (this.#pendingTokenRequest) {
return this.#pendingTokenRequest;
}
this.#pendingTokenRequest = this.#refreshAccessToken();
try {
return await this.#pendingTokenRequest;
}
finally {
this.#pendingTokenRequest = undefined;
}
}
async #refreshAccessToken() {
const body = {
client_id: this.clientId,
client_secret: this.clientSecret,
...(this.refreshToken
? {
grant_type: 'refresh_token',
refresh_token: this.refreshToken,
}
throw error;
: {
grant_type: 'client_credentials',
scope: this.scopes.join(' '),
}),
};
try {
const expiration = new Date();
const { data } = await axios_2.axios.post('/o2/token', body);
expiration.setSeconds(expiration.getSeconds() + data.expires_in);
this.#accessToken = data.access_token;
this.#accessTokenExpiration = expiration;
return data.access_token;
}
catch (error) {
if (error instanceof axios_1.AxiosError) {
throw new error_1.SellingPartnerApiAuthError(error);
}
throw error;
}
return this.#accessToken;
}
/**
* Access token expiration date
* Expiration date of the currently cached access token, or `undefined` if no token has been fetched yet.
*/

@@ -80,0 +103,0 @@ get accessTokenExpiration() {

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthorizationScope = void 0;
/** Authorization scopes for grantless Selling Partner API operations. */
var AuthorizationScope;
(function (AuthorizationScope) {
/** Scope for the Notifications API. */
AuthorizationScope["NOTIFICATIONS"] = "sellingpartnerapi::notifications";
/** Scope for rotating application client credentials. */
AuthorizationScope["CLIENT_CREDENTIAL_ROTATION"] = "sellingpartnerapi::client_credential:rotation";
})(AuthorizationScope || (exports.AuthorizationScope = AuthorizationScope = {}));

@@ -11,2 +11,3 @@ "use strict";

baseURL: 'https://api.amazon.com/auth/',
timeout: 30_000,
headers: {

@@ -13,0 +14,0 @@ 'user-agent': `${package_1.packageJson.name}/${package_1.packageJson.version}`,

import { AxiosError } from 'axios';
/**
* Error thrown when an LWA token request fails.
*
* Wraps the underlying Axios error with a human-readable message that includes
* the HTTP status code (or "No response" for network errors).
*/
export class SellingPartnerApiAuthError extends AxiosError {
/** The original error message from the failed HTTP request. */
innerMessage;

@@ -4,0 +11,0 @@ constructor(error) {

@@ -6,3 +6,7 @@ import process from 'node:process';

/**
* Class for simplify auth with Selling Partner API
* Handles Login with Amazon (LWA) OAuth token management for the Selling Partner API.
*
* Supports both refresh-token and grantless (scope-based) authentication flows.
* Tokens are cached and automatically refreshed when expired. Concurrent calls
* to {@link getAccessToken} are deduplicated into a single request.
*/

@@ -16,2 +20,3 @@ export class SellingPartnerApiAuth {

#accessTokenExpiration;
#pendingTokenRequest;
constructor(parameters) {

@@ -36,38 +41,56 @@ const clientId = parameters.clientId ?? process.env.LWA_CLIENT_ID;

/**
* Get access token
* Returns a valid LWA access token, refreshing it if expired.
*
* Concurrent calls while a refresh is in progress share the same request.
*
* @returns The access token string.
*/
async getAccessToken() {
if (!this.#accessToken ||
(this.#accessTokenExpiration && Date.now() >= this.#accessTokenExpiration.getTime())) {
const body = {
client_id: this.clientId,
client_secret: this.clientSecret,
...(this.refreshToken
? {
grant_type: 'refresh_token',
refresh_token: this.refreshToken,
}
: {
grant_type: 'client_credentials',
scope: this.scopes.join(' '),
}),
};
try {
const expiration = new Date();
const { data } = await axios.post('/o2/token', body);
expiration.setSeconds(expiration.getSeconds() + data.expires_in);
this.#accessToken = data.access_token;
this.#accessTokenExpiration = expiration;
}
catch (error) {
if (error instanceof AxiosError) {
throw new SellingPartnerApiAuthError(error);
if (this.#accessToken &&
(!this.#accessTokenExpiration || Date.now() < this.#accessTokenExpiration.getTime())) {
return this.#accessToken;
}
// Deduplicate concurrent calls: share the same in-flight request
if (this.#pendingTokenRequest) {
return this.#pendingTokenRequest;
}
this.#pendingTokenRequest = this.#refreshAccessToken();
try {
return await this.#pendingTokenRequest;
}
finally {
this.#pendingTokenRequest = undefined;
}
}
async #refreshAccessToken() {
const body = {
client_id: this.clientId,
client_secret: this.clientSecret,
...(this.refreshToken
? {
grant_type: 'refresh_token',
refresh_token: this.refreshToken,
}
throw error;
: {
grant_type: 'client_credentials',
scope: this.scopes.join(' '),
}),
};
try {
const expiration = new Date();
const { data } = await axios.post('/o2/token', body);
expiration.setSeconds(expiration.getSeconds() + data.expires_in);
this.#accessToken = data.access_token;
this.#accessTokenExpiration = expiration;
return data.access_token;
}
catch (error) {
if (error instanceof AxiosError) {
throw new SellingPartnerApiAuthError(error);
}
throw error;
}
return this.#accessToken;
}
/**
* Access token expiration date
* Expiration date of the currently cached access token, or `undefined` if no token has been fetched yet.
*/

@@ -74,0 +97,0 @@ get accessTokenExpiration() {

@@ -0,5 +1,8 @@

/** Authorization scopes for grantless Selling Partner API operations. */
export var AuthorizationScope;
(function (AuthorizationScope) {
/** Scope for the Notifications API. */
AuthorizationScope["NOTIFICATIONS"] = "sellingpartnerapi::notifications";
/** Scope for rotating application client credentials. */
AuthorizationScope["CLIENT_CREDENTIAL_ROTATION"] = "sellingpartnerapi::client_credential:rotation";
})(AuthorizationScope || (AuthorizationScope = {}));

@@ -5,2 +5,3 @@ import globalAxios from 'axios';

baseURL: 'https://api.amazon.com/auth/',
timeout: 30_000,
headers: {

@@ -7,0 +8,0 @@ 'user-agent': `${packageJson.name}/${packageJson.version}`,

import { AxiosError } from 'axios';
import type { AccessTokenData, AccessTokenQuery } from './types/access-token';
/**
* Error thrown when an LWA token request fails.
*
* Wraps the underlying Axios error with a human-readable message that includes
* the HTTP status code (or "No response" for network errors).
*/
export declare class SellingPartnerApiAuthError extends AxiosError<AccessTokenData, AccessTokenQuery> {
/** The original error message from the failed HTTP request. */
readonly innerMessage: string;
constructor(error: AxiosError<AccessTokenData, AccessTokenQuery>);
}
import { type RequireExactlyOne } from 'type-fest';
import { type AuthorizationScope } from './types/scope';
/**
* Configuration parameters for Selling Partner API authentication.
*
* Both `clientId` and `clientSecret` fall back to the `LWA_CLIENT_ID` and
* `LWA_CLIENT_SECRET` environment variables when omitted.
* `refreshToken` falls back to `LWA_REFRESH_TOKEN`.
*/
export interface SellingPartnerAuthParameters {
/** LWA client identifier. Defaults to the `LWA_CLIENT_ID` environment variable. */
clientId?: string;
/** LWA client secret. Defaults to the `LWA_CLIENT_SECRET` environment variable. */
clientSecret?: string;
/** LWA refresh token. Defaults to the `LWA_REFRESH_TOKEN` environment variable. Mutually exclusive with `scopes`. */
refreshToken?: string;
/** Authorization scopes for grantless operations. Mutually exclusive with `refreshToken`. */
scopes?: AuthorizationScope[];
}
/**
* Class for simplify auth with Selling Partner API
* Handles Login with Amazon (LWA) OAuth token management for the Selling Partner API.
*
* Supports both refresh-token and grantless (scope-based) authentication flows.
* Tokens are cached and automatically refreshed when expired. Concurrent calls
* to {@link getAccessToken} are deduplicated into a single request.
*/

@@ -20,7 +35,11 @@ export declare class SellingPartnerApiAuth {

/**
* Get access token
* Returns a valid LWA access token, refreshing it if expired.
*
* Concurrent calls while a refresh is in progress share the same request.
*
* @returns The access token string.
*/
getAccessToken(): Promise<string>;
/**
* Access token expiration date
* Expiration date of the currently cached access token, or `undefined` if no token has been fetched yet.
*/

@@ -27,0 +46,0 @@ protected get accessTokenExpiration(): Date | undefined;

@@ -13,6 +13,8 @@ interface BaseAccessTokenQuery {

} & BaseAccessTokenQuery;
/** Request body for the LWA token endpoint. */
export type AccessTokenQuery = RefreshTokenAccessTokenQuery | ClientCredentialsAccessTokenQuery;
/** Response body from the LWA token endpoint. */
export interface AccessTokenData {
access_token: string;
refresh_token: string;
refresh_token?: string;
token_type: string;

@@ -19,0 +21,0 @@ expires_in: number;

@@ -0,4 +1,7 @@

/** Authorization scopes for grantless Selling Partner API operations. */
export declare enum AuthorizationScope {
/** Scope for the Notifications API. */
NOTIFICATIONS = "sellingpartnerapi::notifications",
/** Scope for rotating application client credentials. */
CLIENT_CREDENTIAL_ROTATION = "sellingpartnerapi::client_credential:rotation"
}

@@ -5,3 +5,3 @@ {

"description": "Amazon Selling Partner API authentication package",
"version": "2.2.23",
"version": "2.2.24",
"main": "dist/cjs/index.js",

@@ -22,3 +22,3 @@ "module": "dist/es/index.js",

"dependencies": {
"axios": "^1.13.5",
"axios": "^1.13.6",
"read-pkg-up": "^7.0.1"

@@ -45,3 +45,3 @@ },

],
"gitHead": "2c1fe783fb7c2204e7e19d4f85fa2bdf822e4593"
"gitHead": "ed62de76baf24107227aacb576cd494b2ecbf0b5"
}
+54
-12

@@ -16,2 +16,18 @@ # `@sp-api-sdk/auth`

## Usage
The `SellingPartnerApiAuth` class handles OAuth token acquisition from Login with Amazon (LWA). You must provide exactly one of `refreshToken` or `scopes`.
```javascript
import { SellingPartnerApiAuth } from "@sp-api-sdk/auth";
const auth = new SellingPartnerApiAuth({
clientId: process.env.LWA_CLIENT_ID,
clientSecret: process.env.LWA_CLIENT_SECRET,
refreshToken: "Atzr|…",
});
const accessToken = await auth.getAccessToken();
```
## Default values from the environment

@@ -21,11 +37,11 @@

| Property Name | Environement variable |
| -------------- | --------------------- |
| `clientId` | LWA_CLIENT_ID |
| `clientSecret` | LWA_CLIENT_SECRET |
| `refreshToken` | LWA_REFRESH_TOKEN |
| Property Name | Environment variable |
| -------------- | -------------------- |
| `clientId` | `LWA_CLIENT_ID` |
| `clientSecret` | `LWA_CLIENT_SECRET` |
| `refreshToken` | `LWA_REFRESH_TOKEN` |
## Grantless APIs support
Some APIs require grantless authentication, which is done by passing scopes, instead of a refresh token.
Some APIs (e.g. Notifications API) require grantless authentication, which is done by passing scopes instead of a refresh token.
The available scopes are exposed in the `AuthorizationScope` enum from this library.

@@ -35,13 +51,21 @@

import { SellingPartnerApiAuth, AuthorizationScope } from "@sp-api-sdk/auth";
import { AuthorizationApiClient } from "@sp-api-sdk/authorization-api-v1";
import { NotificationsApiClient } from "@sp-api-sdk/notifications-api-v1";
const auth = new SellingPartnerApiAuth({
clientId: "",
clientSecret: "",
scopes: [AuthorizationScope.NOTIFICATIONS, AuthorizationScope.CLIENT_CREDENTIAL_ROTATION], // Or choose the only ones you need
clientId: process.env.LWA_CLIENT_ID,
clientSecret: process.env.LWA_CLIENT_SECRET,
scopes: [AuthorizationScope.NOTIFICATIONS],
});
const accessToken = await auth.getAccessToken();
const client = new NotificationsApiClient({
auth,
region: "eu",
});
```
Available scopes:
- `AuthorizationScope.NOTIFICATIONS` - For the Notifications API
- `AuthorizationScope.CLIENT_CREDENTIAL_ROTATION` - For client credential rotation
## Credentials caching

@@ -53,4 +77,6 @@

You can subclass `SellingPartnerApiAuth` to add custom logic, for example, caching the access token in a store.
You can subclass `SellingPartnerApiAuth` to add custom logic, for example, caching the access token in an external store.
The protected `accessTokenExpiration` getter provides the current token's expiration date, which is useful for setting TTLs in your cache.
```typescript

@@ -76,2 +102,18 @@ import { SellingPartnerApiAuth } from "@sp-api-sdk/auth";

## Error handling
Authentication errors are thrown as `SellingPartnerApiAuthError` instances, which extend `AxiosError`.
```javascript
import { SellingPartnerApiAuth, SellingPartnerApiAuthError } from "@sp-api-sdk/auth";
try {
const accessToken = await auth.getAccessToken();
} catch (error) {
if (error instanceof SellingPartnerApiAuthError) {
console.error(error.message); // e.g. "access-token error: Response code 401"
}
}
```
## License

@@ -78,0 +120,0 @@