azure.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.tokenCache = exports.AzureCredentialCache = exports.AZURE_BASE_URL = void 0;
  4. exports.addAzureParams = addAzureParams;
  5. exports.prepareRequest = prepareRequest;
  6. exports.fetchAzureKMSToken = fetchAzureKMSToken;
  7. exports.loadAzureCredentials = loadAzureCredentials;
  8. const error_1 = require("../../error");
  9. const utils_1 = require("../../utils");
  10. const errors_1 = require("../errors");
  11. const MINIMUM_TOKEN_REFRESH_IN_MILLISECONDS = 6000;
  12. /** Base URL for getting Azure tokens. */
  13. exports.AZURE_BASE_URL = 'http://169.254.169.254/metadata/identity/oauth2/token?';
  14. /**
  15. * @internal
  16. */
  17. class AzureCredentialCache {
  18. constructor() {
  19. this.cachedToken = null;
  20. }
  21. async getToken() {
  22. if (this.cachedToken == null || this.needsRefresh(this.cachedToken)) {
  23. this.cachedToken = await this._getToken();
  24. }
  25. return { accessToken: this.cachedToken.accessToken };
  26. }
  27. needsRefresh(token) {
  28. const timeUntilExpirationMS = token.expiresOnTimestamp - Date.now();
  29. return timeUntilExpirationMS <= MINIMUM_TOKEN_REFRESH_IN_MILLISECONDS;
  30. }
  31. /**
  32. * exposed for testing
  33. */
  34. resetCache() {
  35. this.cachedToken = null;
  36. }
  37. /**
  38. * exposed for testing
  39. */
  40. _getToken() {
  41. return fetchAzureKMSToken();
  42. }
  43. }
  44. exports.AzureCredentialCache = AzureCredentialCache;
  45. /** @internal */
  46. exports.tokenCache = new AzureCredentialCache();
  47. /** @internal */
  48. async function parseResponse(response) {
  49. const { status, body: rawBody } = response;
  50. const body = (() => {
  51. try {
  52. return JSON.parse(rawBody);
  53. }
  54. catch {
  55. throw new errors_1.MongoCryptAzureKMSRequestError('Malformed JSON body in GET request.');
  56. }
  57. })();
  58. if (status !== 200) {
  59. throw new errors_1.MongoCryptAzureKMSRequestError('Unable to complete request.', body);
  60. }
  61. if (!body.access_token) {
  62. throw new errors_1.MongoCryptAzureKMSRequestError('Malformed response body - missing field `access_token`.');
  63. }
  64. if (!body.expires_in) {
  65. throw new errors_1.MongoCryptAzureKMSRequestError('Malformed response body - missing field `expires_in`.');
  66. }
  67. const expiresInMS = Number(body.expires_in) * 1000;
  68. if (Number.isNaN(expiresInMS)) {
  69. throw new errors_1.MongoCryptAzureKMSRequestError('Malformed response body - unable to parse int from `expires_in` field.');
  70. }
  71. return {
  72. accessToken: body.access_token,
  73. expiresOnTimestamp: Date.now() + expiresInMS
  74. };
  75. }
  76. /**
  77. * @internal
  78. * Get the Azure endpoint URL.
  79. */
  80. function addAzureParams(url, resource, username) {
  81. url.searchParams.append('api-version', '2018-02-01');
  82. url.searchParams.append('resource', resource);
  83. if (username) {
  84. url.searchParams.append('client_id', username);
  85. }
  86. return url;
  87. }
  88. /**
  89. * @internal
  90. *
  91. * parses any options provided by prose tests to `fetchAzureKMSToken` and merges them with
  92. * the default values for headers and the request url.
  93. */
  94. function prepareRequest(options) {
  95. const url = new URL(options.url?.toString() ?? exports.AZURE_BASE_URL);
  96. addAzureParams(url, 'https://vault.azure.net');
  97. const headers = { ...options.headers, 'Content-Type': 'application/json', Metadata: true };
  98. return { headers, url };
  99. }
  100. /**
  101. * @internal
  102. *
  103. * `AzureKMSRequestOptions` allows prose tests to modify the http request sent to the idms
  104. * servers. This is required to simulate different server conditions. No options are expected to
  105. * be set outside of tests.
  106. *
  107. * exposed for CSFLE
  108. * [prose test 18](https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#azure-imds-credentials)
  109. */
  110. async function fetchAzureKMSToken(options = {}) {
  111. const { headers, url } = prepareRequest(options);
  112. try {
  113. const response = await (0, utils_1.get)(url, { headers });
  114. return await parseResponse(response);
  115. }
  116. catch (error) {
  117. if (error instanceof error_1.MongoNetworkTimeoutError) {
  118. throw new errors_1.MongoCryptAzureKMSRequestError(`[Azure KMS] ${error.message}`);
  119. }
  120. throw error;
  121. }
  122. }
  123. /**
  124. * @internal
  125. *
  126. * @throws Will reject with a `MongoCryptError` if the http request fails or the http response is malformed.
  127. */
  128. async function loadAzureCredentials(kmsProviders) {
  129. const azure = await exports.tokenCache.getToken();
  130. return { ...kmsProviders, azure };
  131. }
  132. //# sourceMappingURL=azure.js.map