UNPKG

@dvsa/appdev-api-common

Version:

Utils library for common API functionality

109 lines (108 loc) 4.09 kB
"use strict"; 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;