Skip to content

feat(NODE-4986)!: remove callbacks from ClientEncryption encrypt, decrypt, and createDataKey #3797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .evergreen/run-azure-kms-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ source ".evergreen/init-node-and-npm-env.sh"

set -o xtrace

npm install mongodb-client-encryption@alpha

export MONGODB_URI="mongodb://localhost:27017"

export EXPECTED_AZUREKMS_OUTCOME=${EXPECTED_AZUREKMS_OUTCOME:-omitted}
Expand Down
2 changes: 0 additions & 2 deletions .evergreen/run-custom-csfle-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ bash ./etc/build-static.sh

npm run rebuild # just in case this is necessary?

ls
ls lib
BINDINGS_DIR=$(pwd)
popd # libmongocrypt/bindings/node
popd # ../csfle-deps-tmp
Expand Down
1 change: 0 additions & 1 deletion .evergreen/run-gcp-kms-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ source ".evergreen/init-node-and-npm-env.sh"

set -o xtrace

npm install mongodb-client-encryption@alpha
npm install gcp-metadata

export MONGODB_URI="mongodb://localhost:27017"
Expand Down
2 changes: 0 additions & 2 deletions .evergreen/run-serverless-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ 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

npx mocha \
--config test/mocha_mongodb.json \
test/integration/crud/crud.spec.test.js \
Expand Down
1 change: 0 additions & 1 deletion .evergreen/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ else
source "$DRIVERS_TOOLS"/.evergreen/csfle/set-temp-creds.sh
fi

npm install mongodb-client-encryption@alpha
npm install @mongodb-js/zstd
npm install snappy

Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"js-yaml": "^4.1.0",
"mocha": "^10.2.0",
"mocha-sinon": "^2.1.2",
"mongodb-client-encryption": "^6.0.0-alpha.1",
"mongodb-client-encryption": "6.0.0-alpha.1",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we bump this to alpha 2 now that it's out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alpha.2 introduced the bug that's making the CI red, rolling back fixes it locally but all of EVG disagrees 😅 I'm not sure how the code is getting a hold of the alpha.2 version, I thought we only installed from devDeps now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context: https://jira.mongodb.org/browse/NODE-5516

Now that we track the package in our devDeps we can get rid of the peppering of manual installations. cc @baileympearson

"mongodb-legacy": "^5.0.0",
"nyc": "^15.1.0",
"prettier": "^2.8.8",
Expand Down
3 changes: 2 additions & 1 deletion src/bson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export {
MinKey,
ObjectId,
serialize,
Timestamp
Timestamp,
UUID
} from 'bson';

/**
Expand Down
126 changes: 29 additions & 97 deletions src/client-side-encryption/client_encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
MongoCryptOptions
} from 'mongodb-client-encryption';

import { type Binary, type Document, type Long, serialize } from '../bson';
import { type Binary, type Document, type Long, serialize, type UUID } from '../bson';
import { type AnyBulkWriteOperation, type BulkWriteResult } from '../bulk/common';
import { type ProxyOptions } from '../cmap/connection';
import { type Collection } from '../collection';
Expand All @@ -16,8 +16,7 @@ import { type MongoClient } from '../mongo_client';
import { type Filter } from '../mongo_types';
import { type CreateCollectionOptions } from '../operations/create_collection';
import { type DeleteResult } from '../operations/delete';
import { type Callback, MongoDBCollectionNamespace } from '../utils';
import { maybeCallback, promiseOrCallback } from './common';
import { MongoDBCollectionNamespace } from '../utils';
import * as cryptoCallbacks from './crypto_callbacks';
import {
MongoCryptCreateDataKeyError,
Expand All @@ -36,7 +35,7 @@ import { type CSFLEKMSTlsOptions, StateMachine } from './state_machine';
* The schema for a DataKey in the key vault collection.
*/
export interface DataKey {
_id: Binary;
_id: UUID;
version?: number;
keyAltNames?: string[];
keyMaterial: Binary;
Expand Down Expand Up @@ -133,18 +132,6 @@ export class ClientEncryption {
*
* @example
* ```ts
* // Using callbacks to create a local key
* clientEncryption.createDataKey('local', (err, dataKey) => {
* if (err) {
* // This means creating the key failed.
* } else {
* // key creation succeeded
* }
* });
* ```
*
* @example
* ```ts
* // Using async/await to create a local key
* const dataKeyId = await clientEncryption.createDataKey('local');
* ```
Expand Down Expand Up @@ -172,21 +159,10 @@ export class ClientEncryption {
* });
* ```
*/
createDataKey(
async createDataKey(
provider: ClientEncryptionDataKeyProvider,
options?: ClientEncryptionCreateDataKeyProviderOptions,
callback?: Callback<DataKey>
) {
if (typeof options === 'function') {
callback = options;
options = {};
}
if (options == null) {
options = {};
}

const dataKey = Object.assign({ provider }, options.masterKey);

options: ClientEncryptionCreateDataKeyProviderOptions = {}
): Promise<UUID> {
if (options.keyAltNames && !Array.isArray(options.keyAltNames)) {
throw new MongoCryptInvalidArgumentError(
`Option "keyAltNames" must be an array of strings, but was of type ${typeof options.keyAltNames}.`
Expand All @@ -211,42 +187,33 @@ export class ClientEncryption {
keyMaterial = serialize({ keyMaterial: options.keyMaterial });
}

const dataKeyBson = serialize(dataKey);
const dataKeyBson = serialize({
provider,
...options.masterKey
});

const context = this._mongoCrypt.makeDataKeyContext(dataKeyBson, {
keyAltNames,
keyMaterial
});

const stateMachine = new StateMachine({
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
});

// @ts-expect-error We did not convert promiseOrCallback to TS
return promiseOrCallback(callback, cb => {
stateMachine.execute<DataKey>(this, context, (err, dataKey) => {
if (err || !dataKey) {
cb(err, null);
return;
}
const dataKey = await stateMachine.executeAsync<DataKey>(this, context);

const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(
this._keyVaultNamespace
);
const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(
this._keyVaultNamespace
);

this._keyVaultClient
.db(dbName)
.collection<DataKey>(collectionName)
.insertOne(dataKey, { writeConcern: { w: 'majority' } })
.then(
result => {
return cb(null, result.insertedId);
},
err => {
cb(err, null);
}
);
});
});
const { insertedId } = await this._keyVaultClient
.db(dbName)
.collection<DataKey>(collectionName)
.insertOne(dataKey, { writeConcern: { w: 'majority' } });

return insertedId;
}

/**
Expand Down Expand Up @@ -601,21 +568,7 @@ export class ClientEncryption {
*
* @param value - The value that you wish to serialize. Must be of a type that can be serialized into BSON
* @param options -
* @param callback - Optional callback to invoke when value is encrypted
* @returns If no callback is provided, returns a Promise that either resolves with the encrypted value, or rejects with an error. If a callback is provided, returns nothing.
*
* @example
* ```ts
* // Encryption with callback API
* function encryptMyData(value, callback) {
* clientEncryption.createDataKey('local', (err, keyId) => {
* if (err) {
* return callback(err);
* }
* clientEncryption.encrypt(value, { keyId, algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' }, callback);
* });
* }
* ```
* @returns a Promise that either resolves with the encrypted value, or rejects with an error.
*
* @example
* ```ts
Expand All @@ -635,12 +588,8 @@ export class ClientEncryption {
* }
* ```
*/
encrypt(
value: unknown,
options: ClientEncryptionEncryptOptions,
callback: Callback<Binary>
): Promise<Binary> | void {
return maybeCallback(() => this._encrypt(value, false, options), callback);
async encrypt(value: unknown, options: ClientEncryptionEncryptOptions): Promise<Binary> {
return this._encrypt(value, false, options);
}

/**
Expand Down Expand Up @@ -672,16 +621,7 @@ export class ClientEncryption {
* Explicitly decrypt a provided encrypted value
*
* @param value - An encrypted value
* @param callback - Optional callback to invoke when value is decrypted
* @returns If no callback is provided, returns a Promise that either resolves with the decrypted value, or rejects with an error. If a callback is provided, returns nothing.
*
* ```ts
* @example
* // Decrypting value with callback API
* function decryptMyValue(value, callback) {
* clientEncryption.decrypt(value, callback);
* }
* ```
* @returns a Promise that either resolves with the decrypted value, or rejects with an error
*
* @example
* ```ts
Expand All @@ -691,7 +631,7 @@ export class ClientEncryption {
* }
* ```
*/
decrypt<T = any>(value: Binary, callback?: Callback<T>): Promise<T> | void {
async decrypt<T = any>(value: Binary): Promise<T> {
const valueBuffer = serialize({ v: value });
const context = this._mongoCrypt.makeExplicitDecryptionContext(valueBuffer);

Expand All @@ -700,17 +640,9 @@ export class ClientEncryption {
tlsOptions: this._tlsOptions
});

// @ts-expect-error We did not convert promiseOrCallback to TS
return promiseOrCallback(callback, cb => {
stateMachine.execute<{ v: T }>(this, context, (err, result) => {
if (err || !result) {
cb(err, null);
return;
}
const { v } = await stateMachine.executeAsync<{ v: T }>(this, context);

cb(null, result.v);
});
});
return v;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export {
MaxKey,
MinKey,
ObjectId,
Timestamp
Timestamp,
UUID
} from './bson';
export { AnyBulkWriteOperation, BulkWriteOptions, MongoBulkWriteError } from './bulk/common';
export { ClientEncryption } from './client-side-encryption/client_encryption';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as BSON from 'bson';
import { expect } from 'chai';
import { readFileSync } from 'fs';
import * as path from 'path';
import * as util from 'util';

// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { ClientEncryption } from '../../../src/client-side-encryption/client_encryption';
Expand Down Expand Up @@ -138,11 +137,8 @@ describe('Connection Pool Deadlock Prevention', function () {
keyVaultClient: this.keyVaultClient,
extraOptions: getEncryptExtraOptions()
});
this.clientEncryption.encryptPromisified = util.promisify(
this.clientEncryption.encrypt.bind(this.clientEncryption)
);

this.ciphertext = await this.clientEncryption.encryptPromisified('string0', {
this.ciphertext = await this.clientEncryption.encrypt('string0', {
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic',
keyAltName: 'local'
});
Expand Down
Loading