@confluentinc/schemaregistry
Version:
Node.js client for Confluent Schema Registry
163 lines (162 loc) • 7.53 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RestService = void 0;
const axios_1 = __importDefault(require("axios"));
const oauth_client_1 = require("./oauth/oauth-client");
const rest_error_1 = require("./rest-error");
const axios_retry_1 = __importDefault(require("axios-retry"));
const retry_helper_1 = require("./retry-helper");
const toBase64 = (str) => Buffer.from(str).toString('base64');
class RestService {
constructor(baseURLs, isForward, axiosDefaults, basicAuthCredentials, bearerAuthCredentials, maxRetries, retriesWaitMs, retriesMaxWaitMs) {
this.oauthBearer = false;
this.client = axios_1.default.create(axiosDefaults);
(0, axios_retry_1.default)(this.client, {
retries: maxRetries ?? 3,
retryDelay: (retryCount) => {
return (0, retry_helper_1.fullJitter)(retriesWaitMs ?? 1000, retriesMaxWaitMs ?? 20000, retryCount - 1);
},
retryCondition: (error) => {
return (0, retry_helper_1.isRetriable)(error.response?.status ?? 0);
}
});
this.baseURLs = baseURLs;
if (isForward) {
this.setHeaders({ 'X-Forward': 'true' });
}
this.setHeaders({ 'Content-Type': 'application/vnd.schemaregistry.v1+json' });
this.setHeaders({ 'Confluent-Accept-Unknown-Properties': 'true' });
this.handleBasicAuth(basicAuthCredentials);
this.handleBearerAuth(maxRetries ?? 2, retriesWaitMs ?? 1000, retriesMaxWaitMs ?? 20000, bearerAuthCredentials);
}
handleBasicAuth(basicAuthCredentials) {
if (basicAuthCredentials) {
switch (basicAuthCredentials.credentialsSource) {
case 'USER_INFO':
if (!basicAuthCredentials.userInfo) {
throw new Error('User info not provided');
}
this.setAuth(toBase64(basicAuthCredentials.userInfo));
break;
case 'SASL_INHERIT':
if (!basicAuthCredentials.sasl) {
throw new Error('Sasl info not provided');
}
if (basicAuthCredentials.sasl.mechanism?.toUpperCase() === 'GSSAPI') {
throw new Error('SASL_INHERIT support PLAIN and SCRAM SASL mechanisms only');
}
this.setAuth(toBase64(`${basicAuthCredentials.sasl.username}:${basicAuthCredentials.sasl.password}`));
break;
case 'URL':
if (!basicAuthCredentials.userInfo) {
throw new Error('User info not provided');
}
const basicAuthUrl = new URL(basicAuthCredentials.userInfo);
this.setAuth(toBase64(`${basicAuthUrl.username}:${basicAuthUrl.password}`));
break;
default:
throw new Error('Invalid basic auth credentials source');
}
}
}
handleBearerAuth(maxRetries, retriesWaitMs, retriesMaxWaitMs, bearerAuthCredentials) {
if (bearerAuthCredentials) {
delete this.client.defaults.auth;
const headers = ['logicalCluster', 'identityPoolId'];
const missingHeader = headers.find(header => !(header in bearerAuthCredentials));
if (missingHeader) {
throw new Error(`Bearer auth header '${missingHeader}' not provided`);
}
this.setHeaders({
'Confluent-Identity-Pool-Id': bearerAuthCredentials.identityPoolId,
'target-sr-cluster': bearerAuthCredentials.logicalCluster
});
switch (bearerAuthCredentials.credentialsSource) {
case 'STATIC_TOKEN':
if (!bearerAuthCredentials.token) {
throw new Error('Bearer token not provided');
}
this.setAuth(undefined, bearerAuthCredentials.token);
break;
case 'OAUTHBEARER':
this.oauthBearer = true;
const requiredFields = [
'clientId',
'clientSecret',
'issuerEndpointUrl',
'scope'
];
const missingField = requiredFields.find(field => !(field in bearerAuthCredentials));
if (missingField) {
throw new Error(`OAuth credential '${missingField}' not provided`);
}
const issuerEndPointUrl = new URL(bearerAuthCredentials.issuerEndpointUrl);
this.oauthClient = new oauth_client_1.OAuthClient(bearerAuthCredentials.clientId, bearerAuthCredentials.clientSecret, issuerEndPointUrl.origin, issuerEndPointUrl.pathname, bearerAuthCredentials.scope, maxRetries, retriesWaitMs, retriesMaxWaitMs);
break;
default:
throw new Error('Invalid bearer auth credentials source');
}
}
}
async handleRequest(url, method, data, // eslint-disable-line @typescript-eslint/no-explicit-any
config) {
if (this.oauthBearer) {
await this.setOAuthBearerToken();
}
for (let i = 0; i < this.baseURLs.length; i++) {
try {
this.setBaseURL(this.baseURLs[i]);
const response = await this.client.request({
url,
method,
data,
...config,
});
return response;
}
catch (error) {
if (axios_1.default.isAxiosError(error) && error.response && !(0, retry_helper_1.isSuccess)(error.response.status)) {
const data = error.response.data;
if (data.error_code && data.message) {
error = new rest_error_1.RestError(data.message, error.response.status, data.error_code);
}
else {
error = new Error(`Unknown error: ${error.message}`);
}
}
if (i === this.baseURLs.length - 1) {
throw error;
}
}
}
throw new Error('Internal HTTP retry error'); // Should never reach here
}
setHeaders(headers) {
this.client.defaults.headers.common = { ...this.client.defaults.headers.common, ...headers };
}
setAuth(basicAuth, bearerToken) {
if (basicAuth) {
this.client.defaults.headers.common['Authorization'] = `Basic ${basicAuth}`;
}
if (bearerToken) {
this.client.defaults.headers.common['Authorization'] = `Bearer ${bearerToken}`;
}
}
async setOAuthBearerToken() {
if (!this.oauthClient) {
throw new Error('OAuthClient not initialized');
}
const bearerToken = await this.oauthClient.getAccessToken();
this.setAuth(undefined, bearerToken);
}
setTimeout(timeout) {
this.client.defaults.timeout = timeout;
}
setBaseURL(baseUrl) {
this.client.defaults.baseURL = baseUrl;
}
}
exports.RestService = RestService;