azure.js 4.1 KB

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