@dvsa/appdev-api-common
Version:
Utils library for common API functionality
109 lines (108 loc) • 4.09 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClientCredentials = void 0;
const node_querystring_1 = require("node:querystring");
const jose_1 = require("jose");
class ClientCredentials {
tokenUrl;
clientId;
clientSecret;
scope;
resource;
options;
static accessToken;
static grantType = "client_credentials";
/**
* Create a new instance of the ClientCredentials class
* @param tokenUrl - The URL to fetch the access token from
* @param clientId - The client id
* @param clientSecret - The client secret
* @param scope - The scope of the access token
* @param resource
* @param options
*/
constructor(tokenUrl, clientId, clientSecret, scope, resource, options = {
debugMode: false,
forceFreshAuth: false,
expirySkewSeconds: 30,
}) {
this.tokenUrl = tokenUrl;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.scope = scope;
this.resource = resource;
this.options = options;
}
/**
* Helper method to perform the client credentials flow and return the access token
* This method will check for the existence of the token and if it is expired, it will fetch a new one
* @returns {Promise<string>} - The access token
*/
async getAccessToken() {
if (this.options?.forceFreshAuth ||
!ClientCredentials.accessToken ||
ClientCredentials.isAccessTokenExpired(this.options.expirySkewSeconds ?? 30)) {
const { access_token } = await this.fetchClientCredentials();
if (this.options?.debugMode)
console.log("[DEBUG] New access token fetched:", access_token);
ClientCredentials.accessToken = access_token;
}
else if (this.options?.debugMode) {
console.log("[DEBUG] Using existing access token:", ClientCredentials.accessToken);
}
return ClientCredentials.accessToken;
}
/**
* Fetch the client credentials from the token URL
* @returns {Promise<ClientCredentialsResponse>} - The response from the token URL
* @private
*/
async fetchClientCredentials() {
const response = await fetch(this.tokenUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: this.resource
? (0, node_querystring_1.stringify)({
grant_type: ClientCredentials.grantType,
client_id: this.clientId,
client_secret: this.clientSecret,
scope: this.scope,
resource: this.resource,
})
: (0, node_querystring_1.stringify)({
grant_type: ClientCredentials.grantType,
client_id: this.clientId,
client_secret: this.clientSecret,
scope: this.scope,
}),
});
if (!response.ok) {
const errorBody = await response.text();
console.error("Error fetching client credentials", response.status, errorBody);
throw new Error("Failed to fetch client credentials");
}
return (await response.json());
}
/**
* Check if the access token is expired
* @returns {boolean} - Whether the access token is expired
* @private
*/
static isAccessTokenExpired(skewSeconds) {
let decodedAccessToken;
try {
decodedAccessToken = (0, jose_1.decodeJwt)(ClientCredentials.accessToken);
}
catch (err) {
console.error("Error decoding access token:", err);
return true;
}
const currentTime = Math.floor(Date.now() / 1000);
const exp = decodedAccessToken?.exp;
if (!exp)
return true;
// treat as expired if we're within the skew window
return currentTime >= exp - skewSeconds;
}
}
exports.ClientCredentials = ClientCredentials;