You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
4.1 KiB
JavaScript
114 lines
4.1 KiB
JavaScript
11 months ago
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.loadAzureCredentials = exports.fetchAzureKMSToken = exports.prepareRequest = exports.tokenCache = exports.AzureCredentialCache = void 0;
|
||
|
const errors_1 = require("../errors");
|
||
|
const utils_1 = require("./utils");
|
||
|
const MINIMUM_TOKEN_REFRESH_IN_MILLISECONDS = 6000;
|
||
|
/**
|
||
|
* @internal
|
||
|
*/
|
||
|
class AzureCredentialCache {
|
||
|
constructor() {
|
||
|
this.cachedToken = null;
|
||
|
}
|
||
|
async getToken() {
|
||
|
if (this.cachedToken == null || this.needsRefresh(this.cachedToken)) {
|
||
|
this.cachedToken = await this._getToken();
|
||
|
}
|
||
|
return { accessToken: this.cachedToken.accessToken };
|
||
|
}
|
||
|
needsRefresh(token) {
|
||
|
const timeUntilExpirationMS = token.expiresOnTimestamp - Date.now();
|
||
|
return timeUntilExpirationMS <= MINIMUM_TOKEN_REFRESH_IN_MILLISECONDS;
|
||
|
}
|
||
|
/**
|
||
|
* exposed for testing
|
||
|
*/
|
||
|
resetCache() {
|
||
|
this.cachedToken = null;
|
||
|
}
|
||
|
/**
|
||
|
* exposed for testing
|
||
|
*/
|
||
|
_getToken() {
|
||
|
return fetchAzureKMSToken();
|
||
|
}
|
||
|
}
|
||
|
exports.AzureCredentialCache = AzureCredentialCache;
|
||
|
/** @internal */
|
||
|
exports.tokenCache = new AzureCredentialCache();
|
||
|
/** @internal */
|
||
|
async function parseResponse(response) {
|
||
|
const { status, body: rawBody } = response;
|
||
|
const body = (() => {
|
||
|
try {
|
||
|
return JSON.parse(rawBody);
|
||
|
}
|
||
|
catch {
|
||
|
throw new errors_1.MongoCryptAzureKMSRequestError('Malformed JSON body in GET request.');
|
||
|
}
|
||
|
})();
|
||
|
if (status !== 200) {
|
||
|
throw new errors_1.MongoCryptAzureKMSRequestError('Unable to complete request.', body);
|
||
|
}
|
||
|
if (!body.access_token) {
|
||
|
throw new errors_1.MongoCryptAzureKMSRequestError('Malformed response body - missing field `access_token`.');
|
||
|
}
|
||
|
if (!body.expires_in) {
|
||
|
throw new errors_1.MongoCryptAzureKMSRequestError('Malformed response body - missing field `expires_in`.');
|
||
|
}
|
||
|
const expiresInMS = Number(body.expires_in) * 1000;
|
||
|
if (Number.isNaN(expiresInMS)) {
|
||
|
throw new errors_1.MongoCryptAzureKMSRequestError('Malformed response body - unable to parse int from `expires_in` field.');
|
||
|
}
|
||
|
return {
|
||
|
accessToken: body.access_token,
|
||
|
expiresOnTimestamp: Date.now() + expiresInMS
|
||
|
};
|
||
|
}
|
||
|
/**
|
||
|
* @internal
|
||
|
*
|
||
|
* parses any options provided by prose tests to `fetchAzureKMSToken` and merges them with
|
||
|
* the default values for headers and the request url.
|
||
|
*/
|
||
|
function prepareRequest(options) {
|
||
|
const url = new URL(options.url?.toString() ?? 'http://169.254.169.254/metadata/identity/oauth2/token');
|
||
|
url.searchParams.append('api-version', '2018-02-01');
|
||
|
url.searchParams.append('resource', 'https://vault.azure.net');
|
||
|
const headers = { ...options.headers, 'Content-Type': 'application/json', Metadata: true };
|
||
|
return { headers, url };
|
||
|
}
|
||
|
exports.prepareRequest = prepareRequest;
|
||
|
/**
|
||
|
* @internal
|
||
|
*
|
||
|
* `AzureKMSRequestOptions` allows prose tests to modify the http request sent to the idms
|
||
|
* servers. This is required to simulate different server conditions. No options are expected to
|
||
|
* be set outside of tests.
|
||
|
*
|
||
|
* exposed for CSFLE
|
||
|
* [prose test 18](https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#azure-imds-credentials)
|
||
|
*/
|
||
|
async function fetchAzureKMSToken(options = {}) {
|
||
|
const { headers, url } = prepareRequest(options);
|
||
|
const response = await (0, utils_1.get)(url, { headers }).catch(error => {
|
||
|
if (error instanceof errors_1.MongoCryptKMSRequestNetworkTimeoutError) {
|
||
|
throw new errors_1.MongoCryptAzureKMSRequestError(`[Azure KMS] ${error.message}`);
|
||
|
}
|
||
|
throw error;
|
||
|
});
|
||
|
return parseResponse(response);
|
||
|
}
|
||
|
exports.fetchAzureKMSToken = fetchAzureKMSToken;
|
||
|
/**
|
||
|
* @internal
|
||
|
*
|
||
|
* @throws Will reject with a `MongoCryptError` if the http request fails or the http response is malformed.
|
||
|
*/
|
||
|
async function loadAzureCredentials(kmsProviders) {
|
||
|
const azure = await exports.tokenCache.getToken();
|
||
|
return { ...kmsProviders, azure };
|
||
|
}
|
||
|
exports.loadAzureCredentials = loadAzureCredentials;
|
||
|
//# sourceMappingURL=azure.js.map
|