@openfga/sdk
Version:
JavaScript and Node.js SDK for OpenFGA
212 lines (211 loc) • 9.42 kB
JavaScript
"use strict";
/**
* JavaScript and Node.js SDK for OpenFGA
*
* API version: 1.x
* Website: https://openfga.dev
* Documentation: https://openfga.dev/docs
* Support: https://openfga.dev/community
* License: [Apache-2.0](https://github.com/openfga/js-sdk/blob/main/LICENSE)
*
* NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Credentials = void 0;
const axios_1 = require("axios");
const jose = require("jose");
const validation_1 = require("../validation");
const errors_1 = require("../errors");
const common_1 = require("../common");
const types_1 = require("./types");
const attributes_1 = require("../telemetry/attributes");
const counters_1 = require("../telemetry/counters");
const crypto_1 = require("crypto");
class Credentials {
static init(configuration, axios = axios_1.default) {
return new Credentials(configuration.credentials, axios, configuration.telemetry, configuration.baseOptions);
}
constructor(authConfig, axios = axios_1.default, telemetryConfig, baseOptions) {
this.authConfig = authConfig;
this.axios = axios;
this.telemetryConfig = telemetryConfig;
this.baseOptions = baseOptions;
this.initConfig();
this.isValid();
}
/**
* Sets the default config values
* @private
*/
initConfig() {
switch (this.authConfig?.method) {
case types_1.CredentialsMethod.ApiToken:
if (this.authConfig.config) {
if (!this.authConfig.config.headerName) {
this.authConfig.config.headerName = "Authorization";
}
if (!this.authConfig.config.headerValuePrefix) {
this.authConfig.config.headerValuePrefix = "Bearer";
}
}
break;
case types_1.CredentialsMethod.None:
default:
break;
}
}
/**
*
* @throws {FgaValidationError}
*/
isValid() {
const { authConfig } = this;
switch (authConfig?.method) {
case types_1.CredentialsMethod.None:
break;
case types_1.CredentialsMethod.ApiToken:
(0, validation_1.assertParamExists)("Credentials", "config.token", authConfig.config?.token);
(0, validation_1.assertParamExists)("Credentials", "config.headerName", authConfig.config?.headerName);
(0, validation_1.assertParamExists)("Credentials", "config.headerName", authConfig.config?.headerName);
break;
case types_1.CredentialsMethod.ClientCredentials:
(0, validation_1.assertParamExists)("Credentials", "config.clientId", authConfig.config?.clientId);
(0, validation_1.assertParamExists)("Credentials", "config.apiTokenIssuer", authConfig.config?.apiTokenIssuer);
(0, validation_1.assertParamExists)("Credentials", "config.apiAudience", authConfig.config?.apiAudience);
(0, validation_1.assertParamExists)("Credentials", "config.clientSecret or config.clientAssertionSigningKey", authConfig.config.clientSecret || authConfig.config.clientAssertionSigningKey);
if (!(0, validation_1.isWellFormedUriString)(`https://${authConfig.config?.apiTokenIssuer}`)) {
throw new errors_1.FgaValidationError(`Configuration.apiTokenIssuer does not form a valid URI (https://${authConfig.config?.apiTokenIssuer})`);
}
break;
}
}
/**
* Get access token, request a new one if not cached or expired
* @return string
*/
async getAccessTokenHeader() {
const accessTokenValue = await this.getAccessTokenValue();
switch (this.authConfig?.method) {
case types_1.CredentialsMethod.None:
return;
case types_1.CredentialsMethod.ApiToken:
return {
name: this.authConfig.config.headerName,
value: `${this.authConfig.config.headerValuePrefix ? `${this.authConfig.config.headerValuePrefix} ` : ""}${accessTokenValue}`
};
case types_1.CredentialsMethod.ClientCredentials:
return {
name: "Authorization",
value: `Bearer ${accessTokenValue}`
};
}
}
async getAccessTokenValue() {
switch (this.authConfig?.method) {
case types_1.CredentialsMethod.None:
return;
case types_1.CredentialsMethod.ApiToken:
return this.authConfig.config.token;
case types_1.CredentialsMethod.ClientCredentials:
if (this.accessToken && (!this.accessTokenExpiryDate || this.accessTokenExpiryDate > new Date())) {
return this.accessToken;
}
return this.refreshAccessToken();
}
}
/**
* Request new access token
* @return string
*/
async refreshAccessToken() {
const clientCredentials = this.authConfig?.config;
const url = `https://${clientCredentials.apiTokenIssuer}/oauth/token`;
const credentialsPayload = await this.buildClientAuthenticationPayload();
try {
const wrappedResponse = await (0, common_1.attemptHttpRequest)({
url,
method: "POST",
data: credentialsPayload,
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
}, {
maxRetry: 3,
minWaitInMs: 100,
}, this.axios);
const response = wrappedResponse?.response;
if (response) {
this.accessToken = response.data.access_token;
this.accessTokenExpiryDate = new Date(Date.now() + response.data.expires_in * 1000);
}
if (this.telemetryConfig?.metrics?.counterCredentialsRequest?.attributes) {
let attributes = {};
attributes = attributes_1.TelemetryAttributes.fromRequest({
userAgent: this.baseOptions?.headers["User-Agent"],
fgaMethod: "TokenExchange",
url,
resendCount: wrappedResponse?.retries,
httpMethod: "POST",
credentials: clientCredentials,
start: performance.now(),
attributes,
});
attributes = attributes_1.TelemetryAttributes.fromResponse({
response,
attributes,
});
attributes = attributes_1.TelemetryAttributes.prepare(attributes, this.telemetryConfig.metrics?.counterCredentialsRequest?.attributes);
this.telemetryConfig.recorder.counter(counters_1.TelemetryCounters.credentialsRequest, 1, attributes);
}
return this.accessToken;
}
catch (err) {
if (err instanceof errors_1.FgaApiError) {
err.constructor = errors_1.FgaApiAuthenticationError;
err.name = "FgaApiAuthenticationError";
err.clientId = clientCredentials.clientId;
err.audience = clientCredentials.apiAudience;
err.grantType = "client_credentials";
}
throw err;
}
}
async buildClientAuthenticationPayload() {
if (this.authConfig?.method !== types_1.CredentialsMethod.ClientCredentials) {
throw new errors_1.FgaValidationError("Credentials method is not set to ClientCredentials");
}
const config = this.authConfig.config;
if (config.clientAssertionSigningKey) {
const alg = config.clientAssertionSigningAlgorithm || "RS256";
const privateKey = await jose.importPKCS8(config.clientAssertionSigningKey, alg);
const assertion = await new jose.SignJWT({})
.setProtectedHeader({ alg })
.setIssuedAt()
.setSubject(config.clientId)
.setJti((0, crypto_1.randomUUID)())
.setIssuer(config.clientId)
.setAudience(`https://${config.apiTokenIssuer}/`)
.setExpirationTime("2m")
.sign(privateKey);
return {
...config.customClaims,
client_id: config.clientId,
client_assertion: assertion,
audience: config.apiAudience,
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
grant_type: "client_credentials",
};
}
else if (config.clientSecret) {
return {
...config.customClaims,
client_id: config.clientId,
client_secret: config.clientSecret,
audience: config.apiAudience,
grant_type: "client_credentials",
};
}
throw new errors_1.FgaValidationError("Credentials method is set to ClientCredentials, but no clientSecret or clientAssertionSigningKey is provided");
}
}
exports.Credentials = Credentials;