From 7800fbfefa3a6db66b023cadb68f764443fe0001 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Sat, 8 Jul 2023 09:22:38 -0600 Subject: [PATCH 01/11] move csfle src into driver and tests passing From bbd8855c7b82198f93919f325782782bb798ee59 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Wed, 19 Jul 2023 09:44:07 -0600 Subject: [PATCH 02/11] only convert relevant files --- src/client-side-encryption/{errors.js => errors.ts} | 0 src/client-side-encryption/providers/{aws.js => aws.ts} | 0 src/client-side-encryption/providers/{azure.js => azure.ts} | 0 src/client-side-encryption/providers/{gcp.js => gcp.ts} | 0 src/client-side-encryption/providers/{index.js => index.ts} | 0 src/client-side-encryption/providers/{utils.js => utils.ts} | 0 .../{credentialsProvider.test.js => credentialsProvider.test.ts} | 0 .../{requirements.helper.js => requirements.helper.ts} | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename src/client-side-encryption/{errors.js => errors.ts} (100%) rename src/client-side-encryption/providers/{aws.js => aws.ts} (100%) rename src/client-side-encryption/providers/{azure.js => azure.ts} (100%) rename src/client-side-encryption/providers/{gcp.js => gcp.ts} (100%) rename src/client-side-encryption/providers/{index.js => index.ts} (100%) rename src/client-side-encryption/providers/{utils.js => utils.ts} (100%) rename test/unit/client-side-encryption/providers/{credentialsProvider.test.js => credentialsProvider.test.ts} (100%) rename test/unit/client-side-encryption/{requirements.helper.js => requirements.helper.ts} (100%) diff --git a/src/client-side-encryption/errors.js b/src/client-side-encryption/errors.ts similarity index 100% rename from src/client-side-encryption/errors.js rename to src/client-side-encryption/errors.ts diff --git a/src/client-side-encryption/providers/aws.js b/src/client-side-encryption/providers/aws.ts similarity index 100% rename from src/client-side-encryption/providers/aws.js rename to src/client-side-encryption/providers/aws.ts diff --git a/src/client-side-encryption/providers/azure.js b/src/client-side-encryption/providers/azure.ts similarity index 100% rename from src/client-side-encryption/providers/azure.js rename to src/client-side-encryption/providers/azure.ts diff --git a/src/client-side-encryption/providers/gcp.js b/src/client-side-encryption/providers/gcp.ts similarity index 100% rename from src/client-side-encryption/providers/gcp.js rename to src/client-side-encryption/providers/gcp.ts diff --git a/src/client-side-encryption/providers/index.js b/src/client-side-encryption/providers/index.ts similarity index 100% rename from src/client-side-encryption/providers/index.js rename to src/client-side-encryption/providers/index.ts diff --git a/src/client-side-encryption/providers/utils.js b/src/client-side-encryption/providers/utils.ts similarity index 100% rename from src/client-side-encryption/providers/utils.js rename to src/client-side-encryption/providers/utils.ts diff --git a/test/unit/client-side-encryption/providers/credentialsProvider.test.js b/test/unit/client-side-encryption/providers/credentialsProvider.test.ts similarity index 100% rename from test/unit/client-side-encryption/providers/credentialsProvider.test.js rename to test/unit/client-side-encryption/providers/credentialsProvider.test.ts diff --git a/test/unit/client-side-encryption/requirements.helper.js b/test/unit/client-side-encryption/requirements.helper.ts similarity index 100% rename from test/unit/client-side-encryption/requirements.helper.js rename to test/unit/client-side-encryption/requirements.helper.ts From 5d6d76707c6b920c79a80514058c2d77894e18dd Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Wed, 19 Jul 2023 09:17:06 -0600 Subject: [PATCH 03/11] convert KMS providers to TS --- package-lock.json | 100 +++++++++++- package.json | 1 + src/client-side-encryption/errors.ts | 42 +++-- src/client-side-encryption/providers/aws.ts | 35 ++--- src/client-side-encryption/providers/azure.ts | 120 +++++++------- src/client-side-encryption/providers/gcp.ts | 28 ++-- src/client-side-encryption/providers/index.ts | 147 ++++++++++++++++-- src/client-side-encryption/providers/utils.ts | 20 +-- src/deps.ts | 20 +++ ...ion.prose.18.azure_kms_mock_server.test.ts | 7 +- .../providers/credentialsProvider.test.ts | 112 +++++++------ .../requirements.helper.ts | 42 ++--- 12 files changed, 453 insertions(+), 221 deletions(-) diff --git a/package-lock.json b/package-lock.json index fd9f2123793..6c1883d2d1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-tsdoc": "^0.2.17", "express": "^4.18.2", + "gcp-metadata": "^5.2.0", "js-yaml": "^4.1.0", "mocha": "^10.2.0", "mocha-sinon": "^2.1.2", @@ -3111,6 +3112,18 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -3391,6 +3404,15 @@ "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", "dev": true }, + "node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -4706,6 +4728,12 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5041,6 +5069,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "dev": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dev": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5401,6 +5457,19 @@ "integrity": "sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg==", "dev": true }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -6049,6 +6118,15 @@ "node": ">=4" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -6515,12 +6593,12 @@ } }, "node_modules/mongodb": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.4.0.tgz", - "integrity": "sha512-6GDKgO7WiYUw+ILap143VXfAou06hjxDGgYUZWGnI4hgoZfP3el0G3l69JqJF8SEQbZmC+SN/2a0aWI/aWJoxA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.7.0.tgz", + "integrity": "sha512-zm82Bq33QbqtxDf58fLWBwTjARK3NSvKYjyz997KSy6hpat0prjeX/kxjbPVyZY60XYPDNETaHkHJI2UCzSLuw==", "dev": true, "dependencies": { - "bson": "^5.2.0", + "bson": "^5.4.0", "mongodb-connection-string-url": "^2.6.0", "socks": "^2.7.1" }, @@ -6532,6 +6610,8 @@ }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.201.0", + "@mongodb-js/zstd": "^1.1.0", + "kerberos": "^2.0.1", "mongodb-client-encryption": ">=2.3.0 <3", "snappy": "^7.2.2" }, @@ -6539,6 +6619,12 @@ "@aws-sdk/credential-providers": { "optional": true }, + "@mongodb-js/zstd": { + "optional": true + }, + "kerberos": { + "optional": true + }, "mongodb-client-encryption": { "optional": true }, @@ -6548,9 +6634,9 @@ } }, "node_modules/mongodb-client-encryption": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/mongodb-client-encryption/-/mongodb-client-encryption-2.8.0.tgz", - "integrity": "sha512-wIcaETX0Acis9hJkUf2SvtPMq/F1G2gxZXgp8QAe2yJzL+cIUpii8Yv4i3LIeZVwYuYSue8F6/e4pHaE21On7A==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mongodb-client-encryption/-/mongodb-client-encryption-2.9.0.tgz", + "integrity": "sha512-OGMfTnS+JJ49ksWdExQ5048ynaQJLhPjbOi3i44PbU2sdufKH0Z4YZqn1pvd/eQ4WgLfbmSws3u9kAiFNFxpOg==", "dev": true, "hasInstallScript": true, "dependencies": { diff --git a/package.json b/package.json index 1328c34f913..299e47a61c8 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-tsdoc": "^0.2.17", "express": "^4.18.2", + "gcp-metadata": "^5.2.0", "js-yaml": "^4.1.0", "mocha": "^10.2.0", "mocha-sinon": "^2.1.2", diff --git a/src/client-side-encryption/errors.ts b/src/client-side-encryption/errors.ts index bb0a72ff625..bb4a8badcba 100644 --- a/src/client-side-encryption/errors.ts +++ b/src/client-side-encryption/errors.ts @@ -1,63 +1,77 @@ +import { type Document } from '../bson'; + /** - * @class + * @public * An error indicating that something went wrong specifically with MongoDB Client Encryption */ export class MongoCryptError extends Error { - constructor(message, options = {}) { + cause?: Error | string; + constructor(message: string, options: { cause?: Error | string } = {}) { super(message); if (options.cause != null) { this.cause = options.cause; } } - get name() { + override get name() { return 'MongoCryptError'; } } /** - * @class + * @public * An error indicating that `ClientEncryption.createEncryptedCollection()` failed to create data keys */ export class MongoCryptCreateDataKeyError extends MongoCryptError { + // TODO: type encrypted fields + encryptedFields: Document; constructor({ encryptedFields, cause }) { super(`Unable to complete creating data keys: ${cause.message}`, { cause }); this.encryptedFields = encryptedFields; } - get name() { + override get name() { return 'MongoCryptCreateDataKeyError'; } } /** - * @class + * @public * An error indicating that `ClientEncryption.createEncryptedCollection()` failed to create a collection */ export class MongoCryptCreateEncryptedCollectionError extends MongoCryptError { + // TODO: type encrypted fields + encryptedFields: Document; constructor({ encryptedFields, cause }) { super(`Unable to create collection: ${cause.message}`, { cause }); this.encryptedFields = encryptedFields; } - get name() { + override get name() { return 'MongoCryptCreateEncryptedCollectionError'; } } /** - * @class + * @public * An error indicating that mongodb-client-encryption failed to auto-refresh Azure KMS credentials. */ export class MongoCryptAzureKMSRequestError extends MongoCryptError { - /** - * @param {string} message - * @param {object | undefined} body - */ - constructor(message, body) { + /** The body of the http response that failed, if present. */ + body?: Document; + constructor(message: string, body?: Document) { super(message); this.body = body; } + + override get name(): string { + return 'MongoCryptAzureKMSRequestError'; + } } -export class MongoCryptKMSRequestNetworkTimeoutError extends MongoCryptError {} +/** @public */ +export class MongoCryptKMSRequestNetworkTimeoutError extends MongoCryptError { + override get name(): string { + return 'MongoCryptKMSRequestNetworkTimeoutError'; + } +} diff --git a/src/client-side-encryption/providers/aws.ts b/src/client-side-encryption/providers/aws.ts index 05b0fddb373..30bd201fd74 100644 --- a/src/client-side-encryption/providers/aws.ts +++ b/src/client-side-encryption/providers/aws.ts @@ -1,22 +1,21 @@ -let awsCredentialProviders = null; -/** @ignore */ -export async function loadAWSCredentials(kmsProviders) { - if (awsCredentialProviders == null) { - try { - // Ensure you always wrap an optional require in the try block NODE-3199 - awsCredentialProviders = require('@aws-sdk/credential-providers'); - // eslint-disable-next-line no-empty - } catch {} - } +import { getAwsCredentialProvider } from '../../deps'; +import { type KMSProviders } from '.'; + +/** + * @internal + */ +export async function loadAWSCredentials(kmsProviders: KMSProviders): Promise { + const credentialProvider = getAwsCredentialProvider(); - if (awsCredentialProviders != null) { - const { fromNodeProviderChain } = awsCredentialProviders; - const provider = fromNodeProviderChain(); - // The state machine is the only place calling this so it will - // catch if there is a rejection here. - const aws = await provider(); - return { ...kmsProviders, aws }; + if ('kModuleError' in credentialProvider) { + return kmsProviders; } - return kmsProviders; + const { fromNodeProviderChain } = credentialProvider; + const provider = fromNodeProviderChain(); + // The state machine is the only place calling this so it will + // catch if there is a rejection here. + const aws = await provider(); + // TODO - figure out the type mismatch here + return { ...kmsProviders, aws }; } diff --git a/src/client-side-encryption/providers/azure.ts b/src/client-side-encryption/providers/azure.ts index d6525607dd9..5d13d91d50b 100644 --- a/src/client-side-encryption/providers/azure.ts +++ b/src/client-side-encryption/providers/azure.ts @@ -1,32 +1,31 @@ -import { - MongoCryptAzureKMSRequestError, - MongoCryptKMSRequestNetworkTimeoutError -} from '../errors'; +import { type Document } from 'bson'; + +import { MongoCryptAzureKMSRequestError, MongoCryptKMSRequestNetworkTimeoutError } from '../errors'; +import { type KMSProviders } from '.'; import * as utils from './utils'; const MINIMUM_TOKEN_REFRESH_IN_MILLISECONDS = 6000; +interface AccessToken { + accessToken: string; + expiresOnTimestamp: number; +} /** - * @class - * @ignore + * @internal */ export class AzureCredentialCache { - constructor() { - /** - * @type { { accessToken: string, expiresOnTimestamp: number } | null} - */ - this.cachedToken = null; - } + /** @internal */ + cachedToken: AccessToken | null = null; - async getToken() { + async getToken(): Promise { if (this.needsRefresh(this.cachedToken)) { this.cachedToken = await this._getToken(); } - return { accessToken: this.cachedToken.accessToken }; + return { ...this.cachedToken! }; } - needsRefresh(token) { + needsRefresh(token: typeof this.cachedToken): boolean { if (token == null) { return true; } @@ -36,7 +35,7 @@ export class AzureCredentialCache { /** * exposed for testing - * @ignore + * @internal */ resetCache() { this.cachedToken = null; @@ -44,38 +43,20 @@ export class AzureCredentialCache { /** * exposed for testing - * @ignore + * @internal */ - _getToken() { + _getToken(): Promise { return fetchAzureKMSToken(); } } -/** - * @type{ AzureCredentialCache } - * @ignore - */ -export let tokenCache = new AzureCredentialCache(); -/** - * @typedef {object} KmsRequestResponsePayload - * @property {string | undefined} access_token - * @property {string | undefined} expires_in - * - * @ignore - */ +/** @internal */ +export const tokenCache = new AzureCredentialCache(); -/** - * @param { {body: string, status: number }} response - * @returns { Promise<{ accessToken: string, expiresOnTimestamp: number } >} - * @ignore - */ -export async function parseResponse(response) { +async function parseResponse(response: { body: string; status?: number }): Promise { const { status, body: rawBody } = response; - /** - * @type { KmsRequestResponsePayload } - */ - const body = (() => { + const body: { expires_in?: number; access_token?: string } = (() => { try { return JSON.parse(rawBody); } catch { @@ -113,17 +94,32 @@ export async function parseResponse(response) { } /** - * @param {object} options - * @param {object | undefined} [options.headers] - * @param {URL | undefined} [options.url] + * @internal * - * @ignore + * exposed for CSFLE + * [prose test 18](https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#azure-imds-credentials) */ -export function prepareRequest(options) { +export interface AzureKMSRequestOptions { + headers?: Document; + url?: URL | string; +} + +/** + * @internal + * + * parses any options provided by prose tests to `fetchAzureKMSToken` and merges them with + * the default values for headers and the request url. + */ +export function prepareRequest(options: AzureKMSRequestOptions): { + headers: Document; + url: URL; +} { const url = options.url == null ? new URL('http://169.254.169.254/metadata/identity/oauth2/token') - : new URL(options.url); + : // TODO - figure out why typings are messed up. + // @ts-expect-error the URL constructor supports a url parameter, but the types say it doesn't + new URL(options.url); url.searchParams.append('api-version', '2018-02-01'); url.searchParams.append('resource', 'https://vault.azure.net'); @@ -133,28 +129,16 @@ export function prepareRequest(options) { } /** - * @typedef {object} AzureKMSRequestOptions - * @property {object | undefined} headers - * @property {URL | undefined} url - * @ignore - */ - -/** - * @typedef {object} AzureKMSRequestResponse - * @property {string} accessToken - * @property {number} expiresOnTimestamp - * @ignore - */ - -/** - * exported only for testing purposes in the driver + * @internal * - * @param {AzureKMSRequestOptions} options - * @returns {Promise} + * `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. * - * @ignore + * exposed for CSFLE + * [prose test 18](https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#azure-imds-credentials) */ -export async function fetchAzureKMSToken(options = {}) { +export async function fetchAzureKMSToken(options: AzureKMSRequestOptions = {}) { const { headers, url } = prepareRequest(options); const response = await utils.get(url, { headers }).catch(error => { if (error instanceof MongoCryptKMSRequestNetworkTimeoutError) { @@ -166,9 +150,11 @@ export async function fetchAzureKMSToken(options = {}) { } /** - * @ignore + * @internal + * + * @throws Will reject with a `MongoCryptError` if the http request fails or the http response is malformed. */ -export async function loadAzureCredentials(kmsProviders) { +export async function loadAzureCredentials(kmsProviders: KMSProviders): Promise { const azure = await tokenCache.getToken(); return { ...kmsProviders, azure }; } diff --git a/src/client-side-encryption/providers/gcp.ts b/src/client-side-encryption/providers/gcp.ts index d529e62a33c..0e518914f18 100644 --- a/src/client-side-encryption/providers/gcp.ts +++ b/src/client-side-encryption/providers/gcp.ts @@ -1,20 +1,16 @@ -let gcpMetadata = null; -/** @ignore */ -export async function loadGCPCredentials(kmsProviders) { - if (gcpMetadata == null) { - try { - // Ensure you always wrap an optional require in the try block NODE-3199 - gcpMetadata = require('gcp-metadata'); - // eslint-disable-next-line no-empty - } catch {} - } +import { getGcpMetadata } from '../../deps'; +import { type KMSProviders } from '.'; + +/** @internal */ +export async function loadGCPCredentials(kmsProviders: KMSProviders): Promise { + const gcpMetadata = getGcpMetadata(); - if (gcpMetadata != null) { - const { access_token: accessToken } = await gcpMetadata.instance({ - property: 'service-accounts/default/token' - }); - return { ...kmsProviders, gcp: { accessToken } }; + if ('kModuleError' in gcpMetadata) { + return kmsProviders; } - return kmsProviders; + const { access_token: accessToken } = await gcpMetadata.instance({ + property: 'service-accounts/default/token' + }); + return { ...kmsProviders, gcp: { accessToken } }; } diff --git a/src/client-side-encryption/providers/index.ts b/src/client-side-encryption/providers/index.ts index 506f676ac6a..0d56e0c3ca9 100644 --- a/src/client-side-encryption/providers/index.ts +++ b/src/client-side-encryption/providers/index.ts @@ -1,24 +1,146 @@ -import { loadAWSCredentials } from './aws' -import { loadAzureCredentials, fetchAzureKMSToken }from './azure'; +import { loadAWSCredentials } from './aws'; +import { loadAzureCredentials } from './azure'; import { loadGCPCredentials } from './gcp'; +/** + * @public + */ +export type KMSProvider = 'aws' | 'azure' | 'gcp' | 'local'; + +/** + * @public + * Configuration options that are used by specific KMS providers during key generation, encryption, and decryption. + */ +export interface KMSProviders { + /** + * Configuration options for using 'aws' as your KMS provider + */ + aws?: + | { + /** + * The access key used for the AWS KMS provider + */ + accessKeyId: string; + + /** + * The secret access key used for the AWS KMS provider + */ + secretAccessKey: string; + + /** + * An optional AWS session token that will be used as the + * X-Amz-Security-Token header for AWS requests. + */ + sessionToken?: string; + } + | Record; + + /** + * Configuration options for using 'local' as your KMS provider + */ + local?: { + /** + * The master key used to encrypt/decrypt data keys. + * A 96-byte long Buffer or base64 encoded string. + */ + key: Buffer | string; + }; + + /** + * Configuration options for using 'kmip' as your KMS provider + */ + kmip?: { + /** + * The output endpoint string. + * The endpoint consists of a hostname and port separated by a colon. + * E.g. "example.com:123". A port is always present. + */ + endpoint?: string; + }; + + /** + * Configuration options for using 'azure' as your KMS provider + */ + azure?: + | { + /** + * The tenant ID identifies the organization for the account + */ + tenantId: string; + + /** + * The client ID to authenticate a registered application + */ + clientId: string; + + /** + * The client secret to authenticate a registered application + */ + clientSecret: string; + + /** + * If present, a host with optional port. E.g. "example.com" or "example.com:443". + * This is optional, and only needed if customer is using a non-commercial Azure instance + * (e.g. a government or China account, which use different URLs). + * Defaults to "login.microsoftonline.com" + */ + identityPlatformEndpoint?: string | undefined; + } + | { + /** + * If present, an access token to authenticate with Azure. + */ + accessToken: string; + } + | Record; + + /** + * Configuration options for using 'gcp' as your KMS provider + */ + gcp?: + | { + /** + * The service account email to authenticate + */ + email: string; + + /** + * A PKCS#8 encrypted key. This can either be a base64 string or a binary representation + */ + privateKey: string | Buffer; + + /** + * If present, a host with optional port. E.g. "example.com" or "example.com:443". + * Defaults to "oauth2.googleapis.com" + */ + endpoint?: string | undefined; + } + | { + /** + * If present, an access token to authenticate with GCP. + */ + accessToken: string; + } + | Record; +} + /** * Auto credential fetching should only occur when the provider is defined on the kmsProviders map * and the settings are an empty object. * * This is distinct from a nullish provider key. * - * @param {'aws' | 'gcp' | 'azure'} provider - * @param {object} kmsProviders - * - * @ignore + * @internal - exposed for testing purposes only */ -function isEmptyCredentials(provider, kmsProviders) { +export function isEmptyCredentials(provider: KMSProvider, kmsProviders: KMSProviders) { return ( provider in kmsProviders && kmsProviders[provider] != null && typeof kmsProviders[provider] === 'object' && - Object.keys(kmsProviders[provider]).length === 0 + // Typescript does not infer that kmsProviders[provider] is non-null, + // even though we check it two lines up. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + Object.keys(kmsProviders[provider]!).length === 0 ); } @@ -27,12 +149,9 @@ function isEmptyCredentials(provider, kmsProviders) { * Credentials will only attempt to get loaded if they do not exist * and no existing credentials will get overwritten. * - * @param {object} kmsProviders - The user provided KMS providers. - * @returns {object} The new kms providers. - * - * @ignore + * @internal */ -async function loadCredentials(kmsProviders) { +export async function loadCredentials(kmsProviders: KMSProviders): Promise { let finalKMSProviders = kmsProviders; if (isEmptyCredentials('aws', kmsProviders)) { @@ -48,5 +167,3 @@ async function loadCredentials(kmsProviders) { } return finalKMSProviders; } - -module.exports = { loadCredentials, isEmptyCredentials, fetchAzureKMSToken }; diff --git a/src/client-side-encryption/providers/utils.ts b/src/client-side-encryption/providers/utils.ts index 942851bf541..8d5362c6993 100644 --- a/src/client-side-encryption/providers/utils.ts +++ b/src/client-side-encryption/providers/utils.ts @@ -1,16 +1,18 @@ -import { MongoCryptKMSRequestNetworkTimeoutError } from '../errors'; import * as http from 'http'; +import { clearTimeout, setTimeout } from 'timers'; + +import { MongoCryptKMSRequestNetworkTimeoutError } from '../errors'; /** - * @param {URL | string} url - * @param {http.RequestOptions} options - * - * @returns { Promise<{ body: string, status: number }> } - * @ignore + * @internal */ -function get(url, options = {}) { +export function get( + url: URL | string, + options: http.RequestOptions = {} +): Promise<{ body: string; status: number | undefined }> { return new Promise((resolve, reject) => { - let timeoutId; + /* eslint-disable prefer-const */ + let timeoutId: NodeJS.Timeout; const request = http .get(url, options, response => { response.setEncoding('utf8'); @@ -33,5 +35,3 @@ function get(url, options = {}) { }, 10000); }); } - -module.exports = { get }; diff --git a/src/deps.ts b/src/deps.ts index e79e3f7fb6e..6f07d70caeb 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -97,6 +97,26 @@ export function getAwsCredentialProvider(): } } +/** @internal */ +export type GcpMetadata = + | typeof import('gcp-metadata') + | { kModuleError: MongoMissingDependencyError }; + +export function getGcpMetadata(): GcpMetadata { + try { + // Ensure you always wrap an optional require in the try block NODE-3199 + const credentialProvider = require('gcp-metadata'); + return credentialProvider; + } catch { + return makeErrorModule( + new MongoMissingDependencyError( + 'Optional module `gcp-metadata` not found.' + + ' Please install it to enable getting gcp credentials via the official sdk.' + ) + ); + } +} + /** @internal */ export type SnappyLib = { /** diff --git a/test/integration/client-side-encryption/client_side_encryption.prose.18.azure_kms_mock_server.test.ts b/test/integration/client-side-encryption/client_side_encryption.prose.18.azure_kms_mock_server.test.ts index 86f2ac216a3..c99820b6f83 100644 --- a/test/integration/client-side-encryption/client_side_encryption.prose.18.azure_kms_mock_server.test.ts +++ b/test/integration/client-side-encryption/client_side_encryption.prose.18.azure_kms_mock_server.test.ts @@ -3,11 +3,14 @@ import { expect } from 'chai'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { MongoCryptAzureKMSRequestError } from '../../../src/client-side-encryption/errors'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports -import { fetchAzureKMSToken } from '../../../src/client-side-encryption/providers/azure'; +import { + type AzureKMSRequestOptions, + fetchAzureKMSToken +} from '../../../src/client-side-encryption/providers/azure'; import { type Document } from '../../mongodb'; const BASE_URL = new URL(`http://127.0.0.1:8080/metadata/identity/oauth2/token`); -class KMSRequestOptions { +class KMSRequestOptions implements AzureKMSRequestOptions { url: URL = BASE_URL; headers: Document; constructor(testCase?: 'empty-json' | 'bad-json' | '404' | '500' | 'slow') { diff --git a/test/unit/client-side-encryption/providers/credentialsProvider.test.ts b/test/unit/client-side-encryption/providers/credentialsProvider.test.ts index e23a7b06ca8..f8f735033bb 100644 --- a/test/unit/client-side-encryption/providers/credentialsProvider.test.ts +++ b/test/unit/client-side-encryption/providers/credentialsProvider.test.ts @@ -1,16 +1,26 @@ -'use strict'; - -const { expect } = require('chai'); -const http = require('http'); -const requirements = require('../requirements.helper'); -const { loadCredentials, isEmptyCredentials } = require('../../../../src/client-side-encryption/providers'); -const { tokenCache, fetchAzureKMSToken } = require('../../../../src/client-side-encryption/providers/azure'); -const sinon = require('sinon'); -const utils = require('../../../../src/client-side-encryption/providers/utils'); -const { - MongoCryptKMSRequestNetworkTimeoutError, - MongoCryptAzureKMSRequestError -} = require('../../../../src/client-side-encryption/errors'); +import { expect } from 'chai'; +import * as http from 'http'; +import * as sinon from 'sinon'; + +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import { + MongoCryptAzureKMSRequestError, + MongoCryptKMSRequestNetworkTimeoutError +} from '../../../../src/client-side-encryption/errors'; +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import { + isEmptyCredentials, + type KMSProviders, + loadCredentials +} from '../../../../src/client-side-encryption/providers'; +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import { + fetchAzureKMSToken, + tokenCache +} from '../../../../src/client-side-encryption/providers/azure'; +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import * as utils from '../../../../src/client-side-encryption/providers/utils'; +import * as requirements from '../requirements.helper'; const originalAccessKeyId = process.env.AWS_ACCESS_KEY_ID; const originalSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY; @@ -19,23 +29,28 @@ const originalSessionToken = process.env.AWS_SESSION_TOKEN; describe('#loadCredentials', function () { context('isEmptyCredentials()', () => { it('returns true for an empty object', () => { - expect(isEmptyCredentials('rainyCloud', { rainyCloud: {} })).to.be.true; + expect(isEmptyCredentials('aws', { aws: {} })).to.be.true; }); it('returns false for an object with keys', () => { - expect(isEmptyCredentials('rainyCloud', { rainyCloud: { password: 'secret' } })).to.be.false; + // @ts-expect-error Testing error conditions here + expect(isEmptyCredentials('aws', { aws: { password: 'secret' } })).to.be.false; }); it('returns false for an nullish credentials', () => { - expect(isEmptyCredentials('rainyCloud', { rainyCloud: null })).to.be.false; - expect(isEmptyCredentials('rainyCloud', { rainyCloud: undefined })).to.be.false; - expect(isEmptyCredentials('rainyCloud', {})).to.be.false; + // @ts-expect-error Testing error conditions here + expect(isEmptyCredentials('aws', { aws: null })).to.be.false; + expect(isEmptyCredentials('aws', { aws: undefined })).to.be.false; + expect(isEmptyCredentials('aws', {})).to.be.false; }); it('returns false for non object credentials', () => { - expect(isEmptyCredentials('rainyCloud', { rainyCloud: 0 })).to.be.false; - expect(isEmptyCredentials('rainyCloud', { rainyCloud: false })).to.be.false; - expect(isEmptyCredentials('rainyCloud', { rainyCloud: Symbol('secret') })).to.be.false; + // @ts-expect-error Testing error conditions here + expect(isEmptyCredentials('aws', { aws: 0 })).to.be.false; + // @ts-expect-error Testing error conditions here + expect(isEmptyCredentials('aws', { aws: false })).to.be.false; + // @ts-expect-error Testing error conditions here + expect(isEmptyCredentials('aws', { aws: Symbol('secret') })).to.be.false; }); }); @@ -63,8 +78,8 @@ describe('#loadCredentials', function () { before(function () { if (!requirements.credentialProvidersInstalled.aws) { - this.currentTest.skipReason = 'Cannot refresh credentials without sdk provider'; - this.currentTest.skip(); + this.currentTest!.skipReason = 'Cannot refresh credentials without sdk provider'; + this.currentTest!.skip(); return; } }); @@ -92,8 +107,8 @@ describe('#loadCredentials', function () { before(function () { if (!requirements.credentialProvidersInstalled.aws) { - this.currentTest.skipReason = 'Cannot refresh credentials without sdk provider'; - this.currentTest.skip(); + this.currentTest!.skipReason = 'Cannot refresh credentials without sdk provider'; + this.currentTest!.skip(); return; } }); @@ -114,19 +129,19 @@ describe('#loadCredentials', function () { }); context('when aws is not empty', function () { - const kmsProviders = { + const kmsProviders: KMSProviders = { local: { key: Buffer.alloc(96) }, aws: { accessKeyId: 'example' - } + } as any }; before(function () { if (!requirements.credentialProvidersInstalled.aws) { - this.currentTest.skipReason = 'Cannot refresh credentials without sdk provider'; - this.currentTest.skip(); + this.currentTest!.skipReason = 'Cannot refresh credentials without sdk provider'; + this.currentTest!.skip(); return; } }); @@ -149,8 +164,8 @@ describe('#loadCredentials', function () { before(function () { if (requirements.credentialProvidersInstalled.aws) { - this.currentTest.skipReason = 'Credentials will be loaded when sdk present'; - this.currentTest.skip(); + this.currentTest!.skipReason = 'Credentials will be loaded when sdk present'; + this.currentTest!.skip(); return; } }); @@ -195,8 +210,8 @@ describe('#loadCredentials', function () { context('and gcp-metadata is installed', () => { beforeEach(function () { if (!requirements.credentialProvidersInstalled.gcp) { - this.currentTest.skipReason = 'Tests require gcp-metadata to be installed'; - this.currentTest.skip(); + this.currentTest!.skipReason = 'Tests require gcp-metadata to be installed'; + this.currentTest!.skip(); return; } }); @@ -233,8 +248,8 @@ describe('#loadCredentials', function () { context('and gcp-metadata is not installed', () => { beforeEach(function () { if (requirements.credentialProvidersInstalled.gcp) { - this.currentTest.skipReason = 'Tests require gcp-metadata to be installed'; - this.currentTest.skip(); + this.currentTest!.skipReason = 'Tests require gcp-metadata to be installed'; + this.currentTest!.skip(); return; } }); @@ -261,7 +276,7 @@ describe('#loadCredentials', function () { }); context('when there is no cached token', () => { - let mockToken = { + const mockToken = { accessToken: 'mock token', expiresOnTimestamp: Date.now() }; @@ -269,7 +284,7 @@ describe('#loadCredentials', function () { let token; beforeEach(async () => { - sinon.stub(cache, '_getToken').returns(mockToken); + sinon.stub(cache, '_getToken').resolves(mockToken); token = await cache.getToken(); }); it('fetches a token', async () => { @@ -282,7 +297,7 @@ describe('#loadCredentials', function () { context('when there is a cached token', () => { context('when the cached token expires <= 1 minute from the current time', () => { - let mockToken = { + const mockToken = { accessToken: 'mock token', expiresOnTimestamp: Date.now() }; @@ -294,7 +309,7 @@ describe('#loadCredentials', function () { accessToken: 'a new key', expiresOnTimestamp: Date.now() + 3000 }; - sinon.stub(cache, '_getToken').returns(mockToken); + sinon.stub(cache, '_getToken').resolves(mockToken); token = await cache.getToken(); }); @@ -307,12 +322,12 @@ describe('#loadCredentials', function () { }); context('when the cached token expires > 1 minute from the current time', () => { - let expiredToken = { + const expiredToken = { token: 'mock token', expiresOnTimestamp: Date.now() }; - let expectedMockToken = { + const expectedMockToken = { accessToken: 'a new key', expiresOnTimestamp: Date.now() + 10000 }; @@ -320,8 +335,8 @@ describe('#loadCredentials', function () { let token; beforeEach(async () => { - cache.cachedToken = expiredToken; - sinon.stub(cache, '_getToken').returns(expectedMockToken); + cache.cachedToken = expiredToken as any; + sinon.stub(cache, '_getToken').resolves(expectedMockToken); token = await cache.getToken(); }); it('returns the cached token', () => { @@ -420,7 +435,9 @@ describe('#loadCredentials', function () { afterEach(() => sinon.restore()); context('when the request times out', () => { before(() => { - sinon.stub(utils, 'get').rejects(new MongoCryptKMSRequestNetworkTimeoutError()); + sinon + .stub(utils, 'get') + .rejects(new MongoCryptKMSRequestNetworkTimeoutError('request timed out')); }); it('throws a MongoCryptKMSRequestError', async () => { @@ -432,7 +449,7 @@ describe('#loadCredentials', function () { context('when the request returns a non-200 error', () => { context('when the request has no body', () => { before(() => { - sinon.stub(utils, 'get').resolves({ status: 400 }); + sinon.stub(utils, 'get').resolves({ status: 400 } as any); }); it('throws a MongoCryptKMSRequestError', async () => { @@ -476,7 +493,7 @@ describe('#loadCredentials', function () { context('when the request returns a 200 response', () => { context('when the request has no body', () => { before(() => { - sinon.stub(utils, 'get').resolves({ status: 200 }); + sinon.stub(utils, 'get').resolves({ status: 200 } as any); }); it('throws a MongoCryptKMSRequestError', async () => { @@ -547,7 +564,8 @@ describe('#loadCredentials', function () { it('returns the token in the `azure` field of the kms providers', async () => { const kmsProviders = await loadCredentials({ azure: {} }); - expect(kmsProviders).to.have.property('azure').to.deep.equal({ accessToken: 'token' }); + const azure = kmsProviders.azure; + expect(azure).to.have.property('accessToken', 'token'); }); }); }); diff --git a/test/unit/client-side-encryption/requirements.helper.ts b/test/unit/client-side-encryption/requirements.helper.ts index a456c49197c..574278bccf5 100644 --- a/test/unit/client-side-encryption/requirements.helper.ts +++ b/test/unit/client-side-encryption/requirements.helper.ts @@ -1,19 +1,22 @@ -'use strict'; - // Data Key Stuff -const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID; -const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY; -const AWS_REGION = process.env.AWS_REGION; -const AWS_CMK_ID = process.env.AWS_CMK_ID; +export const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID; +export const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY; +export const AWS_REGION = process.env.AWS_REGION; +export const AWS_CMK_ID = process.env.AWS_CMK_ID; -const awsKmsProviders = { +export const awsKmsProviders = { aws: { accessKeyId: AWS_ACCESS_KEY_ID, secretAccessKey: AWS_SECRET_ACCESS_KEY } }; -const awsDataKeyOptions = { masterKey: { key: AWS_CMK_ID, region: AWS_REGION } }; +export const awsDataKeyOptions = { masterKey: { key: AWS_CMK_ID, region: AWS_REGION } }; -const SKIP_AWS_TESTS = [AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_CMK_ID].some(secret => !secret); +export const SKIP_AWS_TESTS = [ + AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY, + AWS_REGION, + AWS_CMK_ID +].some(secret => !secret); -function isAWSCredentialProviderInstalled() { +export function isAWSCredentialProviderInstalled() { try { require.resolve('@aws-sdk/credential-providers'); return true; @@ -22,7 +25,7 @@ function isAWSCredentialProviderInstalled() { } } -function isGCPCredentialProviderInstalled() { +export function isGCPCredentialProviderInstalled() { try { require.resolve('gcp-metadata'); return true; @@ -31,18 +34,7 @@ function isGCPCredentialProviderInstalled() { } } -module.exports = { - SKIP_AWS_TESTS, - KEYS: { - AWS_ACCESS_KEY_ID, - AWS_SECRET_ACCESS_KEY, - AWS_REGION, - AWS_CMK_ID - }, - awsKmsProviders, - awsDataKeyOptions, - credentialProvidersInstalled: { - aws: isAWSCredentialProviderInstalled(), - gcp: isGCPCredentialProviderInstalled() - } +export const credentialProvidersInstalled = { + aws: isAWSCredentialProviderInstalled(), + gcp: isGCPCredentialProviderInstalled() }; From 3b8bc363f93a3b858b265757885ba9fec3bd3bce Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Fri, 21 Jul 2023 08:59:40 -0600 Subject: [PATCH 04/11] cleanup requirements --- .../requirements.helper.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/test/unit/client-side-encryption/requirements.helper.ts b/test/unit/client-side-encryption/requirements.helper.ts index 574278bccf5..9737337b55e 100644 --- a/test/unit/client-side-encryption/requirements.helper.ts +++ b/test/unit/client-side-encryption/requirements.helper.ts @@ -1,3 +1,5 @@ +import {getGcpMetadata , getAwsCredentialProvider} from '../../../src/deps'; + // Data Key Stuff export const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID; export const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY; @@ -17,21 +19,11 @@ export const SKIP_AWS_TESTS = [ ].some(secret => !secret); export function isAWSCredentialProviderInstalled() { - try { - require.resolve('@aws-sdk/credential-providers'); - return true; - } catch { - return false; - } + return !('kModuleError' in getAwsCredentialProvider()); } export function isGCPCredentialProviderInstalled() { - try { - require.resolve('gcp-metadata'); - return true; - } catch { - return false; - } + return !('kModuleError' in getGcpMetadata()); } export const credentialProvidersInstalled = { From 202fda1861cb51a22592bd8528ac713a95548517 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Fri, 21 Jul 2023 09:00:56 -0600 Subject: [PATCH 05/11] cleanup src --- src/client-side-encryption/errors.ts | 6 ++---- src/client-side-encryption/providers/aws.ts | 1 - src/client-side-encryption/providers/azure.ts | 6 ++++-- src/client-side-encryption/providers/gcp.ts | 2 +- src/cmap/auth/mongodb_aws.ts | 8 -------- src/deps.ts | 13 ++++++++++++- 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/client-side-encryption/errors.ts b/src/client-side-encryption/errors.ts index bb4a8badcba..e64c3321df0 100644 --- a/src/client-side-encryption/errors.ts +++ b/src/client-side-encryption/errors.ts @@ -23,9 +23,8 @@ export class MongoCryptError extends Error { * An error indicating that `ClientEncryption.createEncryptedCollection()` failed to create data keys */ export class MongoCryptCreateDataKeyError extends MongoCryptError { - // TODO: type encrypted fields encryptedFields: Document; - constructor({ encryptedFields, cause }) { + constructor({encryptedFields, cause}: { encryptedFields: Document, cause: Error }) { super(`Unable to complete creating data keys: ${cause.message}`, { cause }); this.encryptedFields = encryptedFields; } @@ -40,9 +39,8 @@ export class MongoCryptCreateDataKeyError extends MongoCryptError { * An error indicating that `ClientEncryption.createEncryptedCollection()` failed to create a collection */ export class MongoCryptCreateEncryptedCollectionError extends MongoCryptError { - // TODO: type encrypted fields encryptedFields: Document; - constructor({ encryptedFields, cause }) { + constructor({encryptedFields, cause}: { encryptedFields: Document, cause: Error }) { super(`Unable to create collection: ${cause.message}`, { cause }); this.encryptedFields = encryptedFields; } diff --git a/src/client-side-encryption/providers/aws.ts b/src/client-side-encryption/providers/aws.ts index 30bd201fd74..64aa9f0adca 100644 --- a/src/client-side-encryption/providers/aws.ts +++ b/src/client-side-encryption/providers/aws.ts @@ -16,6 +16,5 @@ export async function loadAWSCredentials(kmsProviders: KMSProviders): Promise({ property: 'service-accounts/default/token' }); return { ...kmsProviders, gcp: { accessToken } }; diff --git a/src/cmap/auth/mongodb_aws.ts b/src/cmap/auth/mongodb_aws.ts index 57e3a028ff8..0001c080475 100644 --- a/src/cmap/auth/mongodb_aws.ts +++ b/src/cmap/auth/mongodb_aws.ts @@ -157,14 +157,6 @@ interface AWSTempCredentials { Expiration?: Date; } -/* @internal */ -export interface AWSCredentials { - accessKeyId?: string; - secretAccessKey?: string; - sessionToken?: string; - expiration?: Date; -} - async function makeTempCredentials(credentials: MongoCredentials): Promise { function makeMongoCredentialsFromAWSTemp(creds: AWSTempCredentials) { if (!creds.AccessKeyId || !creds.SecretAccessKey || !creds.Token) { diff --git a/src/deps.ts b/src/deps.ts index 6f07d70caeb..214e60b4e4f 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import type { Document } from './bson'; -import type { AWSCredentials } from './cmap/auth/mongodb_aws'; import type { ProxyOptions } from './cmap/connection'; import { MongoMissingDependencyError } from './error'; import type { MongoClient } from './mongo_client'; @@ -76,6 +75,18 @@ export function getZstdLibrary(): typeof ZStandard | { kModuleError: MongoMissin } } +/** + * @internal + * Copy of the AwsCredentialIdentityProvider interface from [`@smithy/types`](https://socket.dev/npm/package/@smithy/types/files/1.1.1/dist-types/identity/awsCredentialIdentity.d.ts), + * the return type of the aws-sdk's `fromNodeProviderChain().provider()`. + */ +export interface AWSCredentials { + accessKeyId: string; + secretAccessKey: string; + sessionToken: string; + expiration?: Date; +} + type CredentialProvider = { fromNodeProviderChain(this: void): () => Promise; }; From 1a0af799bc5c221726da0b26245ef6e43c343c7c Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Fri, 21 Jul 2023 09:08:42 -0600 Subject: [PATCH 06/11] fix lint --- .evergreen/run-kerberos-tests.sh | 5 --- src/client-side-encryption/errors.ts | 12 ++---- src/client-side-encryption/providers/azure.ts | 37 ++++++++++++------- src/deps.ts | 2 +- .../requirements.helper.ts | 3 +- tsconfig.json | 3 +- 6 files changed, 32 insertions(+), 30 deletions(-) diff --git a/.evergreen/run-kerberos-tests.sh b/.evergreen/run-kerberos-tests.sh index eb6b4b44228..bc3a8d37527 100644 --- a/.evergreen/run-kerberos-tests.sh +++ b/.evergreen/run-kerberos-tests.sh @@ -24,11 +24,6 @@ set -o xtrace npm install kerberos@">=2.0.0-beta.0" npm run check:kerberos -if [ "$NODE_LTS_VERSION" != "latest" ] && [ $NODE_LTS_VERSION -lt 20 ]; then - npm install kerberos@"^1.1.7" - npm run check:kerberos -fi - set +o xtrace # destroy ticket diff --git a/src/client-side-encryption/errors.ts b/src/client-side-encryption/errors.ts index e64c3321df0..561260bc1a9 100644 --- a/src/client-side-encryption/errors.ts +++ b/src/client-side-encryption/errors.ts @@ -5,12 +5,8 @@ import { type Document } from '../bson'; * An error indicating that something went wrong specifically with MongoDB Client Encryption */ export class MongoCryptError extends Error { - cause?: Error | string; - constructor(message: string, options: { cause?: Error | string } = {}) { - super(message); - if (options.cause != null) { - this.cause = options.cause; - } + constructor(message: string, options: { cause?: Error } = {}) { + super(message, options); } override get name() { @@ -24,7 +20,7 @@ export class MongoCryptError extends Error { */ export class MongoCryptCreateDataKeyError extends MongoCryptError { encryptedFields: Document; - constructor({encryptedFields, cause}: { encryptedFields: Document, cause: Error }) { + constructor({ encryptedFields, cause }: { encryptedFields: Document; cause: Error }) { super(`Unable to complete creating data keys: ${cause.message}`, { cause }); this.encryptedFields = encryptedFields; } @@ -40,7 +36,7 @@ export class MongoCryptCreateDataKeyError extends MongoCryptError { */ export class MongoCryptCreateEncryptedCollectionError extends MongoCryptError { encryptedFields: Document; - constructor({encryptedFields, cause}: { encryptedFields: Document, cause: Error }) { + constructor({ encryptedFields, cause }: { encryptedFields: Document; cause: Error }) { super(`Unable to create collection: ${cause.message}`, { cause }); this.encryptedFields = encryptedFields; } diff --git a/src/client-side-encryption/providers/azure.ts b/src/client-side-encryption/providers/azure.ts index df3948ad077..0dd61b72a92 100644 --- a/src/client-side-encryption/providers/azure.ts +++ b/src/client-side-encryption/providers/azure.ts @@ -6,36 +6,43 @@ import * as utils from './utils'; const MINIMUM_TOKEN_REFRESH_IN_MILLISECONDS = 6000; +/** + * The access token that libmongocrypt expects for Azure kms. + */ interface AccessToken { accessToken: string; +} + +/** + * The response from the azure idms endpoint, including the `expiresOnTimestamp`. + * `expiresOnTimestamp` is needed for caching. + */ +interface AzureTokenCacheEntry extends AccessToken { + accessToken: string; expiresOnTimestamp: number; } + /** * @internal */ export class AzureCredentialCache { - /** @internal */ - cachedToken: AccessToken | null = null; + cachedToken: AzureTokenCacheEntry | null = null; async getToken(): Promise { - if (this.needsRefresh(this.cachedToken)) { + if (this.cachedToken == null || this.needsRefresh(this.cachedToken)) { this.cachedToken = await this._getToken(); } - return { ...this.cachedToken! }; + return { accessToken: this.cachedToken.accessToken }; } - needsRefresh(token: typeof this.cachedToken): boolean { - if (token == null) { - return true; - } + needsRefresh(token: AzureTokenCacheEntry): boolean { const timeUntilExpirationMS = token.expiresOnTimestamp - Date.now(); return timeUntilExpirationMS <= MINIMUM_TOKEN_REFRESH_IN_MILLISECONDS; } /** * exposed for testing - * @internal */ resetCache() { this.cachedToken = null; @@ -43,9 +50,8 @@ export class AzureCredentialCache { /** * exposed for testing - * @internal */ - _getToken(): Promise { + _getToken(): Promise { return fetchAzureKMSToken(); } } @@ -53,7 +59,8 @@ export class AzureCredentialCache { /** @internal */ export const tokenCache = new AzureCredentialCache(); -async function parseResponse(response: { body: string; status?: number }): Promise { +/** @internal */ +async function parseResponse(response: { body: string; status?: number }): Promise { const { status, body: rawBody } = response; const body: { expires_in?: number; access_token?: string } = (() => { @@ -120,7 +127,7 @@ export function prepareRequest(options: AzureKMSRequestOptions): { : // The Node URL constructor technically supports "any object that converts to a valid URL string", // but Node types doesn't support this. See the Node docs for new URL(). // https://nodejs.org/api/url.html#new-urlinput-base - // @ts-ignore + // @ts-expect-error URL constructor typings incorrect new URL(options.url); url.searchParams.append('api-version', '2018-02-01'); @@ -140,7 +147,9 @@ export function prepareRequest(options: AzureKMSRequestOptions): { * exposed for CSFLE * [prose test 18](https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#azure-imds-credentials) */ -export async function fetchAzureKMSToken(options: AzureKMSRequestOptions = {}) { +export async function fetchAzureKMSToken( + options: AzureKMSRequestOptions = {} +): Promise { const { headers, url } = prepareRequest(options); const response = await utils.get(url, { headers }).catch(error => { if (error instanceof MongoCryptKMSRequestNetworkTimeoutError) { diff --git a/src/deps.ts b/src/deps.ts index 214e60b4e4f..f3c8965685b 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -77,7 +77,7 @@ export function getZstdLibrary(): typeof ZStandard | { kModuleError: MongoMissin /** * @internal - * Copy of the AwsCredentialIdentityProvider interface from [`@smithy/types`](https://socket.dev/npm/package/@smithy/types/files/1.1.1/dist-types/identity/awsCredentialIdentity.d.ts), + * Copy of the AwsCredentialIdentityProvider interface from [`smithy/types`](https://socket.dev/npm/package/\@smithy/types/files/1.1.1/dist-types/identity/awsCredentialIdentity.d.ts), * the return type of the aws-sdk's `fromNodeProviderChain().provider()`. */ export interface AWSCredentials { diff --git a/test/unit/client-side-encryption/requirements.helper.ts b/test/unit/client-side-encryption/requirements.helper.ts index 9737337b55e..68e6ecfce76 100644 --- a/test/unit/client-side-encryption/requirements.helper.ts +++ b/test/unit/client-side-encryption/requirements.helper.ts @@ -1,4 +1,5 @@ -import {getGcpMetadata , getAwsCredentialProvider} from '../../../src/deps'; +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import { getAwsCredentialProvider, getGcpMetadata } from '../../../src/deps'; // Data Key Stuff export const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID; diff --git a/tsconfig.json b/tsconfig.json index c8b6b54e07a..0d08d129980 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,8 @@ "moduleResolution": "node", "skipLibCheck": true, "lib": [ - "es2021" + "es2021", + "ES2022.Error" ], // We don't make use of tslib helpers, all syntax used is supported by target engine "importHelpers": false, From d62209a4edbebeed493cddfff8bf3832cea66a1c Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Mon, 24 Jul 2023 13:03:46 -0600 Subject: [PATCH 07/11] fix package.json --- package-lock.json | 85 ++++++++++++++++++++++++++++------------------- package.json | 4 +-- 2 files changed, 53 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6c1883d2d1a..e601bf5af99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "js-yaml": "^4.1.0", "mocha": "^10.2.0", "mocha-sinon": "^2.1.2", - "mongodb-client-encryption": "^2.8.0", + "mongodb-client-encryption": "^6.0.0-alpha.0", "mongodb-legacy": "^5.0.0", "nyc": "^15.1.0", "prettier": "^2.8.8", @@ -74,7 +74,7 @@ "@mongodb-js/zstd": "^1.1.0", "gcp-metadata": "^5.2.0", "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=2.3.0 <3", + "mongodb-client-encryption": ">=6.0.0-alpha.0 <7", "snappy": "^7.2.2" }, "peerDependenciesMeta": { @@ -6592,7 +6592,52 @@ "node": ">=10" } }, - "node_modules/mongodb": { + "node_modules/mongodb-client-encryption": { + "version": "6.0.0-alpha.0", + "resolved": "https://registry.npmjs.org/mongodb-client-encryption/-/mongodb-client-encryption-6.0.0-alpha.0.tgz", + "integrity": "sha512-lwkwJcjgXnxtd3A5otzTchxtqS+aVmsGpVaYnpnrL2m2s59uWXJpVStPQBt54SYDPt0Eu7pcT8nrWcVvZGZFfg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": ">=12.9.0" + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "node_modules/mongodb-legacy": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mongodb-legacy/-/mongodb-legacy-5.0.0.tgz", + "integrity": "sha512-q2G+MRwde6114bCAF/EZLmMXSsebIKMHmzsfOJq6M/Tj4gr3wLT50+rJsJNkiR0e0kjFx3dllWjqwRR1n11Zsw==", + "dev": true, + "dependencies": { + "mongodb": "^5.0.0" + }, + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongodb-legacy/node_modules/mongodb": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.7.0.tgz", "integrity": "sha512-zm82Bq33QbqtxDf58fLWBwTjARK3NSvKYjyz997KSy6hpat0prjeX/kxjbPVyZY60XYPDNETaHkHJI2UCzSLuw==", @@ -6633,12 +6678,14 @@ } } }, - "node_modules/mongodb-client-encryption": { + "node_modules/mongodb-legacy/node_modules/mongodb-client-encryption": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/mongodb-client-encryption/-/mongodb-client-encryption-2.9.0.tgz", "integrity": "sha512-OGMfTnS+JJ49ksWdExQ5048ynaQJLhPjbOi3i44PbU2sdufKH0Z4YZqn1pvd/eQ4WgLfbmSws3u9kAiFNFxpOg==", "dev": true, "hasInstallScript": true, + "optional": true, + "peer": true, "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^4.3.0", @@ -6662,36 +6709,6 @@ } } }, - "node_modules/mongodb-connection-string-url": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", - "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", - "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "node_modules/mongodb-connection-string-url/node_modules/@types/whatwg-url": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", - "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", - "dependencies": { - "@types/node": "*", - "@types/webidl-conversions": "*" - } - }, - "node_modules/mongodb-legacy": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mongodb-legacy/-/mongodb-legacy-5.0.0.tgz", - "integrity": "sha512-q2G+MRwde6114bCAF/EZLmMXSsebIKMHmzsfOJq6M/Tj4gr3wLT50+rJsJNkiR0e0kjFx3dllWjqwRR1n11Zsw==", - "dev": true, - "dependencies": { - "mongodb": "^5.0.0" - }, - "engines": { - "node": ">=14.20.1" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", diff --git a/package.json b/package.json index 299e47a61c8..12003ba28a2 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "@mongodb-js/zstd": "^1.1.0", "gcp-metadata": "^5.2.0", "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=2.3.0 <3", + "mongodb-client-encryption": ">=6.0.0-alpha.0 <7", "snappy": "^7.2.2" }, "peerDependenciesMeta": { @@ -94,7 +94,7 @@ "js-yaml": "^4.1.0", "mocha": "^10.2.0", "mocha-sinon": "^2.1.2", - "mongodb-client-encryption": "^2.8.0", + "mongodb-client-encryption": "^6.0.0-alpha.0", "mongodb-legacy": "^5.0.0", "nyc": "^15.1.0", "prettier": "^2.8.8", From b5ac95d1ceaf5716598fd699848cb90eb13be59c Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Mon, 24 Jul 2023 13:04:41 -0600 Subject: [PATCH 08/11] remove force install --- .evergreen/run-azure-kms-tests.sh | 2 +- .evergreen/run-gcp-kms-tests.sh | 2 +- .evergreen/run-serverless-tests.sh | 2 +- .evergreen/run-socks5-tests.sh | 2 +- .evergreen/run-tests.sh | 2 +- .evergreen/run-unit-tests.sh | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.evergreen/run-azure-kms-tests.sh b/.evergreen/run-azure-kms-tests.sh index 5f1d53de3c8..741b7135b8f 100644 --- a/.evergreen/run-azure-kms-tests.sh +++ b/.evergreen/run-azure-kms-tests.sh @@ -9,7 +9,7 @@ source ".evergreen/init-node-and-npm-env.sh" set -o xtrace -npm install mongodb-client-encryption@alpha --force +npm install mongodb-client-encryption@alpha export MONGODB_URI="mongodb://localhost:27017" diff --git a/.evergreen/run-gcp-kms-tests.sh b/.evergreen/run-gcp-kms-tests.sh index b3c4aeaa2b4..35cb203b073 100644 --- a/.evergreen/run-gcp-kms-tests.sh +++ b/.evergreen/run-gcp-kms-tests.sh @@ -9,7 +9,7 @@ source ".evergreen/init-node-and-npm-env.sh" set -o xtrace -npm install mongodb-client-encryption@alpha --force +npm install mongodb-client-encryption@alpha npm install gcp-metadata export MONGODB_URI="mongodb://localhost:27017" diff --git a/.evergreen/run-serverless-tests.sh b/.evergreen/run-serverless-tests.sh index 7ffae747521..df95d818448 100755 --- a/.evergreen/run-serverless-tests.sh +++ b/.evergreen/run-serverless-tests.sh @@ -10,7 +10,7 @@ if [ -z ${MONGODB_URI+omitted} ]; then echo "MONGODB_URI is unset" && exit 1; fi if [ -z ${SERVERLESS_ATLAS_USER+omitted} ]; then echo "SERVERLESS_ATLAS_USER is unset" && exit 1; fi if [ -z ${SERVERLESS_ATLAS_PASSWORD+omitted} ]; then echo "SERVERLESS_ATLAS_PASSWORD is unset" && exit 1; fi -npm install mongodb-client-encryption@alpha --force +npm install mongodb-client-encryption@alpha npx mocha \ --config test/mocha_mongodb.json \ diff --git a/.evergreen/run-socks5-tests.sh b/.evergreen/run-socks5-tests.sh index de77ac15ecb..9093fe5d0ab 100644 --- a/.evergreen/run-socks5-tests.sh +++ b/.evergreen/run-socks5-tests.sh @@ -20,7 +20,7 @@ function setup_fle() { # CSFLE_AWS_TEMP_ACCESS_KEY_ID, CSFLE_AWS_TEMP_SECRET_ACCESS_KEY, CSFLE_AWS_TEMP_SESSION_TOKEN . "$DRIVERS_TOOLS"/.evergreen/csfle/set-temp-creds.sh - npm i --force mongodb-client-encryption@alpha + npm i mongodb-client-encryption@alpha export KMIP_TLS_CA_FILE="${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem" export KMIP_TLS_CERT_FILE="${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem" export TEST_CSFLE=true diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index c2988ae6edf..5daf11d3569 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -52,7 +52,7 @@ else source "$DRIVERS_TOOLS"/.evergreen/csfle/set-temp-creds.sh fi -npm install mongodb-client-encryption@alpha --force +npm install mongodb-client-encryption@alpha npm install @mongodb-js/zstd npm install snappy diff --git a/.evergreen/run-unit-tests.sh b/.evergreen/run-unit-tests.sh index 3f95c724d43..79a447a401c 100644 --- a/.evergreen/run-unit-tests.sh +++ b/.evergreen/run-unit-tests.sh @@ -4,6 +4,6 @@ set -o errexit # Exit the script with error if any of the commands fail source "${PROJECT_DIRECTORY}/.evergreen/init-node-and-npm-env.sh" set -o xtrace -npm i --force mongodb-client-encryption@alpha +npm i mongodb-client-encryption@alpha npx nyc npm run check:unit From d2fbad1fc118e39c91c04ef100eae2a57cb1a6b8 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Mon, 24 Jul 2023 17:54:31 -0600 Subject: [PATCH 09/11] address comments --- src/client-side-encryption/errors.ts | 4 ++++ src/client-side-encryption/providers/azure.ts | 14 +++++--------- src/client-side-encryption/providers/index.ts | 14 ++++++-------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/client-side-encryption/errors.ts b/src/client-side-encryption/errors.ts index 561260bc1a9..615446e4c70 100644 --- a/src/client-side-encryption/errors.ts +++ b/src/client-side-encryption/errors.ts @@ -5,6 +5,7 @@ import { type Document } from '../bson'; * An error indicating that something went wrong specifically with MongoDB Client Encryption */ export class MongoCryptError extends Error { + /** @internal */ constructor(message: string, options: { cause?: Error } = {}) { super(message, options); } @@ -20,6 +21,7 @@ export class MongoCryptError extends Error { */ export class MongoCryptCreateDataKeyError extends MongoCryptError { encryptedFields: Document; + /** @internal */ constructor({ encryptedFields, cause }: { encryptedFields: Document; cause: Error }) { super(`Unable to complete creating data keys: ${cause.message}`, { cause }); this.encryptedFields = encryptedFields; @@ -36,6 +38,7 @@ export class MongoCryptCreateDataKeyError extends MongoCryptError { */ export class MongoCryptCreateEncryptedCollectionError extends MongoCryptError { encryptedFields: Document; + /** @internal */ constructor({ encryptedFields, cause }: { encryptedFields: Document; cause: Error }) { super(`Unable to create collection: ${cause.message}`, { cause }); this.encryptedFields = encryptedFields; @@ -53,6 +56,7 @@ export class MongoCryptCreateEncryptedCollectionError extends MongoCryptError { export class MongoCryptAzureKMSRequestError extends MongoCryptError { /** The body of the http response that failed, if present. */ body?: Document; + /** @internal */ constructor(message: string, body?: Document) { super(message); this.body = body; diff --git a/src/client-side-encryption/providers/azure.ts b/src/client-side-encryption/providers/azure.ts index 0dd61b72a92..d3620e52008 100644 --- a/src/client-side-encryption/providers/azure.ts +++ b/src/client-side-encryption/providers/azure.ts @@ -1,8 +1,8 @@ -import { type Document } from 'bson'; +import { type Document } from '../../bson'; import { MongoCryptAzureKMSRequestError, MongoCryptKMSRequestNetworkTimeoutError } from '../errors'; -import { type KMSProviders } from '.'; -import * as utils from './utils'; +import { type KMSProviders } from './index'; +import { get } from './utils'; const MINIMUM_TOKEN_REFRESH_IN_MILLISECONDS = 6000; @@ -124,11 +124,7 @@ export function prepareRequest(options: AzureKMSRequestOptions): { const url = options.url == null ? new URL('http://169.254.169.254/metadata/identity/oauth2/token') - : // The Node URL constructor technically supports "any object that converts to a valid URL string", - // but Node types doesn't support this. See the Node docs for new URL(). - // https://nodejs.org/api/url.html#new-urlinput-base - // @ts-expect-error URL constructor typings incorrect - new URL(options.url); + : new URL(options.url.toString()); url.searchParams.append('api-version', '2018-02-01'); url.searchParams.append('resource', 'https://vault.azure.net'); @@ -151,7 +147,7 @@ export async function fetchAzureKMSToken( options: AzureKMSRequestOptions = {} ): Promise { const { headers, url } = prepareRequest(options); - const response = await utils.get(url, { headers }).catch(error => { + const response = await get(url, { headers }).catch(error => { if (error instanceof MongoCryptKMSRequestNetworkTimeoutError) { throw new MongoCryptAzureKMSRequestError(`[Azure KMS] ${error.message}`); } diff --git a/src/client-side-encryption/providers/index.ts b/src/client-side-encryption/providers/index.ts index 0d56e0c3ca9..7125f1c988c 100644 --- a/src/client-side-encryption/providers/index.ts +++ b/src/client-side-encryption/providers/index.ts @@ -132,15 +132,13 @@ export interface KMSProviders { * * @internal - exposed for testing purposes only */ -export function isEmptyCredentials(provider: KMSProvider, kmsProviders: KMSProviders) { +export function isEmptyCredentials(providerName: KMSProvider, kmsProviders: KMSProviders) { + const provider = kmsProviders[providerName]; return ( - provider in kmsProviders && - kmsProviders[provider] != null && - typeof kmsProviders[provider] === 'object' && - // Typescript does not infer that kmsProviders[provider] is non-null, - // even though we check it two lines up. - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - Object.keys(kmsProviders[provider]!).length === 0 + provider && + provider != null && + typeof provider === 'object' && + Object.keys(provider).length === 0 ); } From 20a4aceb3f1fa967b06178afe6dbc2ff25f3dadb Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Tue, 25 Jul 2023 08:34:06 -0600 Subject: [PATCH 10/11] cleanup --- src/client-side-encryption/providers/azure.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/client-side-encryption/providers/azure.ts b/src/client-side-encryption/providers/azure.ts index d3620e52008..eaac6422a77 100644 --- a/src/client-side-encryption/providers/azure.ts +++ b/src/client-side-encryption/providers/azure.ts @@ -121,10 +121,7 @@ export function prepareRequest(options: AzureKMSRequestOptions): { headers: Document; url: URL; } { - const url = - options.url == null - ? new URL('http://169.254.169.254/metadata/identity/oauth2/token') - : new URL(options.url.toString()); + 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'); From b48942f28915ab36bb4be6ef4758417641d34488 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Tue, 25 Jul 2023 08:59:06 -0600 Subject: [PATCH 11/11] fix unit tests --- src/client-side-encryption/providers/index.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/client-side-encryption/providers/index.ts b/src/client-side-encryption/providers/index.ts index 7125f1c988c..5e4024d51e8 100644 --- a/src/client-side-encryption/providers/index.ts +++ b/src/client-side-encryption/providers/index.ts @@ -132,14 +132,12 @@ export interface KMSProviders { * * @internal - exposed for testing purposes only */ -export function isEmptyCredentials(providerName: KMSProvider, kmsProviders: KMSProviders) { +export function isEmptyCredentials(providerName: KMSProvider, kmsProviders: KMSProviders): boolean { const provider = kmsProviders[providerName]; - return ( - provider && - provider != null && - typeof provider === 'object' && - Object.keys(provider).length === 0 - ); + if (provider == null) { + return false; + } + return typeof provider === 'object' && Object.keys(provider).length === 0; } /**