Skip to content

Commit ea2d60a

Browse files
refactor(NODE-5514): make FLE logic use async-await (#3830)
1 parent a17b0af commit ea2d60a

13 files changed

+385
-723
lines changed

Diff for: src/client-side-encryption/auto_encrypter.ts

+44-134
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import {
77
import { deserialize, type Document, serialize } from '../bson';
88
import { type CommandOptions, type ProxyOptions } from '../cmap/connection';
99
import { getMongoDBClientEncryption } from '../deps';
10-
import { type AnyError, MongoRuntimeError } from '../error';
10+
import { MongoRuntimeError } from '../error';
1111
import { MongoClient, type MongoClientOptions } from '../mongo_client';
12-
import { type Callback, MongoDBCollectionNamespace } from '../utils';
12+
import { MongoDBCollectionNamespace } from '../utils';
1313
import * as cryptoCallbacks from './crypto_callbacks';
1414
import { MongoCryptInvalidArgumentError } from './errors';
1515
import { MongocryptdManager } from './mongocryptd_manager';
@@ -396,133 +396,66 @@ export class AutoEncrypter {
396396
*
397397
* This function is a no-op when bypassSpawn is set or the crypt shared library is used.
398398
*/
399-
init(callback: Callback<MongoClient>) {
399+
async init(): Promise<MongoClient | void> {
400400
if (this._bypassMongocryptdAndCryptShared || this.cryptSharedLibVersionInfo) {
401-
return callback();
401+
return;
402402
}
403403
if (!this._mongocryptdManager) {
404-
return callback(
405-
new MongoRuntimeError(
406-
'Reached impossible state: mongocryptdManager is undefined when neither bypassSpawn nor the shared lib are specified.'
407-
)
404+
throw new MongoRuntimeError(
405+
'Reached impossible state: mongocryptdManager is undefined when neither bypassSpawn nor the shared lib are specified.'
408406
);
409407
}
410408
if (!this._mongocryptdClient) {
411-
return callback(
412-
new MongoRuntimeError(
413-
'Reached impossible state: mongocryptdClient is undefined when neither bypassSpawn nor the shared lib are specified.'
414-
)
409+
throw new MongoRuntimeError(
410+
'Reached impossible state: mongocryptdClient is undefined when neither bypassSpawn nor the shared lib are specified.'
415411
);
416412
}
417-
const _callback = (err?: AnyError, res?: MongoClient) => {
418-
if (
419-
err &&
420-
err.message &&
421-
(err.message.match(/timed out after/) || err.message.match(/ENOTFOUND/))
422-
) {
423-
callback(
424-
new MongoRuntimeError(
425-
'Unable to connect to `mongocryptd`, please make sure it is running or in your PATH for auto-spawn',
426-
{ cause: err }
427-
)
428-
);
429-
return;
430-
}
431-
432-
callback(err, res);
433-
};
434413

435-
if (this._mongocryptdManager.bypassSpawn) {
436-
this._mongocryptdClient.connect().then(
437-
result => {
438-
return _callback(undefined, result);
439-
},
440-
error => {
441-
_callback(error, undefined);
442-
}
443-
);
444-
return;
414+
if (!this._mongocryptdManager.bypassSpawn) {
415+
await this._mongocryptdManager.spawn();
445416
}
446417

447-
this._mongocryptdManager.spawn(() => {
448-
if (!this._mongocryptdClient) {
449-
return callback(
450-
new MongoRuntimeError(
451-
'Reached impossible state: mongocryptdClient is undefined after spawning libmongocrypt.'
452-
)
418+
try {
419+
const client = await this._mongocryptdClient.connect();
420+
return client;
421+
} catch (error) {
422+
const { message } = error;
423+
if (message && (message.match(/timed out after/) || message.match(/ENOTFOUND/))) {
424+
throw new MongoRuntimeError(
425+
'Unable to connect to `mongocryptd`, please make sure it is running or in your PATH for auto-spawn',
426+
{ cause: error }
453427
);
454428
}
455-
this._mongocryptdClient.connect().then(
456-
result => {
457-
return _callback(undefined, result);
458-
},
459-
error => {
460-
_callback(error, undefined);
461-
}
462-
);
463-
});
429+
throw error;
430+
}
464431
}
465432

466433
/**
467434
* Cleans up the `_mongocryptdClient`, if present.
468435
*/
469-
teardown(force: boolean, callback: Callback<void>) {
470-
if (this._mongocryptdClient) {
471-
this._mongocryptdClient.close(force).then(
472-
result => {
473-
return callback(undefined, result);
474-
},
475-
error => {
476-
callback(error);
477-
}
478-
);
479-
} else {
480-
callback();
481-
}
436+
async teardown(force: boolean): Promise<void> {
437+
await this._mongocryptdClient?.close(force);
482438
}
483439

484-
encrypt(ns: string, cmd: Document, callback: Callback<Document | Uint8Array>): void;
485-
encrypt(
486-
ns: string,
487-
cmd: Document,
488-
options: CommandOptions,
489-
callback: Callback<Document | Uint8Array>
490-
): void;
491440
/**
492441
* Encrypt a command for a given namespace.
493442
*/
494-
encrypt(
443+
async encrypt(
495444
ns: string,
496445
cmd: Document,
497-
options?: CommandOptions | Callback<Document | Uint8Array>,
498-
callback?: Callback<Document | Uint8Array>
499-
) {
500-
callback = typeof options === 'function' ? options : callback;
501-
502-
if (callback == null) {
503-
throw new MongoCryptInvalidArgumentError('Callback must be provided');
504-
}
505-
506-
options = typeof options === 'function' ? {} : options;
507-
508-
// If `bypassAutoEncryption` has been specified, don't encrypt
446+
options: CommandOptions = {}
447+
): Promise<Document | Uint8Array> {
509448
if (this._bypassEncryption) {
510-
callback(undefined, cmd);
511-
return;
449+
// If `bypassAutoEncryption` has been specified, don't encrypt
450+
return cmd;
512451
}
513452

514453
const commandBuffer = Buffer.isBuffer(cmd) ? cmd : serialize(cmd, options);
515454

516-
let context;
517-
try {
518-
context = this._mongocrypt.makeEncryptionContext(
519-
MongoDBCollectionNamespace.fromString(ns).db,
520-
commandBuffer
521-
);
522-
} catch (err) {
523-
callback(err, undefined);
524-
return;
525-
}
455+
const context = this._mongocrypt.makeEncryptionContext(
456+
MongoDBCollectionNamespace.fromString(ns).db,
457+
commandBuffer
458+
);
526459

527460
context.id = this._contextCounter++;
528461
context.ns = ns;
@@ -534,34 +467,16 @@ export class AutoEncrypter {
534467
proxyOptions: this._proxyOptions,
535468
tlsOptions: this._tlsOptions
536469
});
537-
stateMachine.execute<Document>(this, context, callback);
470+
return stateMachine.execute<Document>(this, context);
538471
}
539472

540473
/**
541474
* Decrypt a command response
542475
*/
543-
decrypt(
544-
response: Uint8Array,
545-
options: CommandOptions | Callback<Document>,
546-
callback?: Callback<Document>
547-
) {
548-
callback = typeof options === 'function' ? options : callback;
549-
550-
if (callback == null) {
551-
throw new MongoCryptInvalidArgumentError('Callback must be provided');
552-
}
553-
554-
options = typeof options === 'function' ? {} : options;
555-
476+
async decrypt(response: Uint8Array | Document, options: CommandOptions = {}): Promise<Document> {
556477
const buffer = Buffer.isBuffer(response) ? response : serialize(response, options);
557478

558-
let context;
559-
try {
560-
context = this._mongocrypt.makeDecryptionContext(buffer);
561-
} catch (err) {
562-
callback(err, undefined);
563-
return;
564-
}
479+
const context = this._mongocrypt.makeDecryptionContext(buffer);
565480

566481
context.id = this._contextCounter++;
567482

@@ -572,16 +487,11 @@ export class AutoEncrypter {
572487
});
573488

574489
const decorateResult = this[kDecorateResult];
575-
stateMachine.execute(this, context, function (error?: Error, result?: Document) {
576-
// Only for testing/internal usage
577-
if (!error && result && decorateResult) {
578-
const error = decorateDecryptionResult(result, response);
579-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
580-
if (error) return callback!(error);
581-
}
582-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
583-
callback!(error, result);
584-
});
490+
const result = await stateMachine.execute<Document>(this, context);
491+
if (decorateResult) {
492+
decorateDecryptionResult(result, response);
493+
}
494+
return result;
585495
}
586496

587497
/**
@@ -621,14 +531,14 @@ function decorateDecryptionResult(
621531
decrypted: Document & { [kDecoratedKeys]?: Array<string> },
622532
original: Document,
623533
isTopLevelDecorateCall = true
624-
): Error | void {
534+
): void {
625535
if (isTopLevelDecorateCall) {
626536
// The original value could have been either a JS object or a BSON buffer
627537
if (Buffer.isBuffer(original)) {
628538
original = deserialize(original);
629539
}
630540
if (Buffer.isBuffer(decrypted)) {
631-
return new MongoRuntimeError('Expected result of decryption to be deserialized BSON object');
541+
throw new MongoRuntimeError('Expected result of decryption to be deserialized BSON object');
632542
}
633543
}
634544

@@ -647,10 +557,10 @@ function decorateDecryptionResult(
647557
writable: false
648558
});
649559
}
650-
// this is defined in the preceeding if-statement
560+
// this is defined in the preceding if-statement
651561
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
652562
decrypted[kDecoratedKeys]!.push(k);
653-
// Do not recurse into this decrypted value. It could be a subdocument/array,
563+
// Do not recurse into this decrypted value. It could be a sub-document/array,
654564
// in which case there is no original value associated with its subfields.
655565
continue;
656566
}

Diff for: src/client-side-encryption/client_encryption.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { type FindCursor } from '../cursor/find_cursor';
1313
import { type Db } from '../db';
1414
import { getMongoDBClientEncryption } from '../deps';
1515
import { type MongoClient } from '../mongo_client';
16-
import { type Filter } from '../mongo_types';
16+
import { type Filter, type WithId } from '../mongo_types';
1717
import { type CreateCollectionOptions } from '../operations/create_collection';
1818
import { type DeleteResult } from '../operations/delete';
1919
import { MongoDBCollectionNamespace } from '../utils';
@@ -202,7 +202,7 @@ export class ClientEncryption {
202202
tlsOptions: this._tlsOptions
203203
});
204204

205-
const dataKey = await stateMachine.executeAsync<DataKey>(this, context);
205+
const dataKey = await stateMachine.execute<DataKey>(this, context);
206206

207207
const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(
208208
this._keyVaultNamespace
@@ -246,7 +246,7 @@ export class ClientEncryption {
246246
async rewrapManyDataKey(
247247
filter: Filter<DataKey>,
248248
options: ClientEncryptionRewrapManyDataKeyProviderOptions
249-
) {
249+
): Promise<{ bulkWriteResult?: BulkWriteResult }> {
250250
let keyEncryptionKeyBson = undefined;
251251
if (options) {
252252
const keyEncryptionKey = Object.assign({ provider: options.provider }, options.masterKey);
@@ -259,16 +259,16 @@ export class ClientEncryption {
259259
tlsOptions: this._tlsOptions
260260
});
261261

262-
const dataKey = await stateMachine.executeAsync<{ v: DataKey[] }>(this, context);
263-
if (!dataKey || dataKey.v.length === 0) {
262+
const { v: dataKeys } = await stateMachine.execute<{ v: DataKey[] }>(this, context);
263+
if (dataKeys.length === 0) {
264264
return {};
265265
}
266266

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

271-
const replacements = dataKey.v.map(
271+
const replacements = dataKeys.map(
272272
(key: DataKey): AnyBulkWriteOperation<DataKey> => ({
273273
updateOne: {
274274
filter: { _id: key._id },
@@ -386,7 +386,7 @@ export class ClientEncryption {
386386
* }
387387
* ```
388388
*/
389-
async getKeyByAltName(keyAltName: string) {
389+
async getKeyByAltName(keyAltName: string): Promise<WithId<DataKey> | null> {
390390
const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(
391391
this._keyVaultNamespace
392392
);
@@ -417,7 +417,7 @@ export class ClientEncryption {
417417
* }
418418
* ```
419419
*/
420-
async addKeyAltName(_id: Binary, keyAltName: string) {
420+
async addKeyAltName(_id: Binary, keyAltName: string): Promise<WithId<DataKey> | null> {
421421
const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(
422422
this._keyVaultNamespace
423423
);
@@ -457,7 +457,7 @@ export class ClientEncryption {
457457
* }
458458
* ```
459459
*/
460-
async removeKeyAltName(_id: Binary, keyAltName: string) {
460+
async removeKeyAltName(_id: Binary, keyAltName: string): Promise<WithId<DataKey> | null> {
461461
const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(
462462
this._keyVaultNamespace
463463
);
@@ -640,7 +640,7 @@ export class ClientEncryption {
640640
tlsOptions: this._tlsOptions
641641
});
642642

643-
const { v } = await stateMachine.executeAsync<{ v: T }>(this, context);
643+
const { v } = await stateMachine.execute<{ v: T }>(this, context);
644644

645645
return v;
646646
}
@@ -719,7 +719,7 @@ export class ClientEncryption {
719719
});
720720
const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);
721721

722-
const result = await stateMachine.executeAsync<{ v: Binary }>(this, context);
722+
const result = await stateMachine.execute<{ v: Binary }>(this, context);
723723
return result.v;
724724
}
725725
}

0 commit comments

Comments
 (0)