opusdev/vector-similarity-api
1
1import type {2 ExplicitEncryptionContextOptions,3 MongoCrypt,4 MongoCryptConstructor,5 MongoCryptOptions6} from 'mongodb-client-encryption';7 8import {9 type Binary,10 deserialize,11 type Document,12 type Int32,13 type Long,14 serialize,15 type UUID16} from '../bson';17import { type AnyBulkWriteOperation, type BulkWriteResult } from '../bulk/common';18import { type ProxyOptions } from '../cmap/connection';19import { type Collection } from '../collection';20import { type FindCursor } from '../cursor/find_cursor';21import { type Db } from '../db';22import { getMongoDBClientEncryption } from '../deps';23import { type MongoClient, type MongoClientOptions } from '../mongo_client';24import { type Filter, type WithId } from '../mongo_types';25import { type CreateCollectionOptions } from '../operations/create_collection';26import { type DeleteResult } from '../operations/delete';27import { type CSOTTimeoutContext, TimeoutContext } from '../timeout';28import { MongoDBCollectionNamespace, resolveTimeoutOptions } from '../utils';29import * as cryptoCallbacks from './crypto_callbacks';30import {31 MongoCryptCreateDataKeyError,32 MongoCryptCreateEncryptedCollectionError,33 MongoCryptInvalidArgumentError34} from './errors';35import {36 type ClientEncryptionDataKeyProvider,37 type CredentialProviders,38 isEmptyCredentials,39 type KMSProviders,40 refreshKMSCredentials41} from './providers/index';42import {43 type ClientEncryptionSocketOptions,44 type CSFLEKMSTlsOptions,45 StateMachine46} from './state_machine';47 48/**49 * @public50 * The schema for a DataKey in the key vault collection.51 */52export interface DataKey {53 _id: UUID;54 version?: number;55 keyAltNames?: string[];56 keyMaterial: Binary;57 creationDate: Date;58 updateDate: Date;59 status: number;60 masterKey: Document;61}62 63/**64 * @public65 * The public interface for explicit in-use encryption66 */67export class ClientEncryption {68 /** @internal */69 _client: MongoClient;70 /** @internal */71 _keyVaultNamespace: string;72 /** @internal */73 _keyVaultClient: MongoClient;74 /** @internal */75 _proxyOptions: ProxyOptions;76 /** @internal */77 _tlsOptions: CSFLEKMSTlsOptions;78 /** @internal */79 _kmsProviders: KMSProviders;80 /** @internal */81 _timeoutMS?: number;82 83 /** @internal */84 _mongoCrypt: MongoCrypt;85 86 /** @internal */87 _credentialProviders?: CredentialProviders;88 89 /** @internal */90 static getMongoCrypt(): MongoCryptConstructor {91 const encryption = getMongoDBClientEncryption();92 if ('kModuleError' in encryption) {93 throw encryption.kModuleError;94 }95 return encryption.MongoCrypt;96 }97 98 /**99 * Create a new encryption instance100 *101 * @example102 * ```ts103 * new ClientEncryption(mongoClient, {104 * keyVaultNamespace: 'client.encryption',105 * kmsProviders: {106 * local: {107 * key: masterKey // The master key used for encryption/decryption. A 96-byte long Buffer108 * }109 * }110 * });111 * ```112 *113 * @example114 * ```ts115 * new ClientEncryption(mongoClient, {116 * keyVaultNamespace: 'client.encryption',117 * kmsProviders: {118 * aws: {119 * accessKeyId: AWS_ACCESS_KEY,120 * secretAccessKey: AWS_SECRET_KEY121 * }122 * }123 * });124 * ```125 */126 constructor(client: MongoClient, options: ClientEncryptionOptions) {127 this._client = client;128 this._proxyOptions = options.proxyOptions ?? {};129 this._tlsOptions = options.tlsOptions ?? {};130 this._kmsProviders = options.kmsProviders || {};131 const { timeoutMS } = resolveTimeoutOptions(client, options);132 this._timeoutMS = timeoutMS;133 this._credentialProviders = options.credentialProviders;134 135 if (options.credentialProviders?.aws && !isEmptyCredentials('aws', this._kmsProviders)) {136 throw new MongoCryptInvalidArgumentError(137 'Can only provide a custom AWS credential provider when the state machine is configured for automatic AWS credential fetching'138 );139 }140 141 if (options.keyVaultNamespace == null) {142 throw new MongoCryptInvalidArgumentError('Missing required option `keyVaultNamespace`');143 }144 145 const mongoCryptOptions: MongoCryptOptions = {146 ...options,147 cryptoCallbacks,148 kmsProviders: !Buffer.isBuffer(this._kmsProviders)149 ? (serialize(this._kmsProviders) as Buffer)150 : this._kmsProviders151 };152 153 this._keyVaultNamespace = options.keyVaultNamespace;154 this._keyVaultClient = options.keyVaultClient || client;155 const MongoCrypt = ClientEncryption.getMongoCrypt();156 this._mongoCrypt = new MongoCrypt(mongoCryptOptions);157 }158 159 /**160 * Creates a data key used for explicit encryption and inserts it into the key vault namespace161 *162 * @example163 * ```ts164 * // Using async/await to create a local key165 * const dataKeyId = await clientEncryption.createDataKey('local');166 * ```167 *168 * @example169 * ```ts170 * // Using async/await to create an aws key171 * const dataKeyId = await clientEncryption.createDataKey('aws', {172 * masterKey: {173 * region: 'us-east-1',174 * key: 'xxxxxxxxxxxxxx' // CMK ARN here175 * }176 * });177 * ```178 *179 * @example180 * ```ts181 * // Using async/await to create an aws key with a keyAltName182 * const dataKeyId = await clientEncryption.createDataKey('aws', {183 * masterKey: {184 * region: 'us-east-1',185 * key: 'xxxxxxxxxxxxxx' // CMK ARN here186 * },187 * keyAltNames: [ 'mySpecialKey' ]188 * });189 * ```190 */191 async createDataKey(192 provider: ClientEncryptionDataKeyProvider,193 options: ClientEncryptionCreateDataKeyProviderOptions = {}194 ): Promise<UUID> {195 if (options.keyAltNames && !Array.isArray(options.keyAltNames)) {196 throw new MongoCryptInvalidArgumentError(197 `Option "keyAltNames" must be an array of strings, but was of type ${typeof options.keyAltNames}.`198 );199 }200 201 let keyAltNames = undefined;202 if (options.keyAltNames && options.keyAltNames.length > 0) {203 keyAltNames = options.keyAltNames.map((keyAltName, i) => {204 if (typeof keyAltName !== 'string') {205 throw new MongoCryptInvalidArgumentError(206 `Option "keyAltNames" must be an array of strings, but item at index ${i} was of type ${typeof keyAltName}`207 );208 }209 210 return serialize({ keyAltName });211 });212 }213 214 let keyMaterial = undefined;215 if (options.keyMaterial) {216 keyMaterial = serialize({ keyMaterial: options.keyMaterial });217 }218 219 const dataKeyBson = serialize({220 provider,221 ...options.masterKey222 });223 224 const context = this._mongoCrypt.makeDataKeyContext(dataKeyBson, {225 keyAltNames,226 keyMaterial227 });228 229 const stateMachine = new StateMachine({230 proxyOptions: this._proxyOptions,231 tlsOptions: this._tlsOptions,232 socketOptions: autoSelectSocketOptions(this._client.s.options)233 });234 235 const timeoutContext =236 options?.timeoutContext ??237 TimeoutContext.create(resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS }));238 239 const dataKey = deserialize(240 await stateMachine.execute(this, context, { timeoutContext })241 ) as DataKey;242 243 const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(244 this._keyVaultNamespace245 );246 247 const { insertedId } = await this._keyVaultClient248 .db(dbName)249 .collection<DataKey>(collectionName)250 .insertOne(dataKey, {251 writeConcern: { w: 'majority' },252 timeoutMS: timeoutContext?.csotEnabled()253 ? timeoutContext?.getRemainingTimeMSOrThrow()254 : undefined255 });256 257 return insertedId;258 }259 260 /**261 * Searches the keyvault for any data keys matching the provided filter. If there are matches, rewrapManyDataKey then attempts to re-wrap the data keys using the provided options.262 *263 * If no matches are found, then no bulk write is performed.264 *265 * @example266 * ```ts267 * // rewrapping all data data keys (using a filter that matches all documents)268 * const filter = {};269 *270 * const result = await clientEncryption.rewrapManyDataKey(filter);271 * if (result.bulkWriteResult != null) {272 * // keys were re-wrapped, results will be available in the bulkWrite object.273 * }274 * ```275 *276 * @example277 * ```ts278 * // attempting to rewrap all data keys with no matches279 * const filter = { _id: new Binary() } // assume _id matches no documents in the database280 * const result = await clientEncryption.rewrapManyDataKey(filter);281 *282 * if (result.bulkWriteResult == null) {283 * // no keys matched, `bulkWriteResult` does not exist on the result object284 * }285 * ```286 */287 async rewrapManyDataKey(288 filter: Filter<DataKey>,289 options: ClientEncryptionRewrapManyDataKeyProviderOptions290 ): Promise<{ bulkWriteResult?: BulkWriteResult }> {291 let keyEncryptionKeyBson = undefined;292 if (options) {293 const keyEncryptionKey = Object.assign({ provider: options.provider }, options.masterKey);294 keyEncryptionKeyBson = serialize(keyEncryptionKey);295 }296 const filterBson = serialize(filter);297 const context = this._mongoCrypt.makeRewrapManyDataKeyContext(filterBson, keyEncryptionKeyBson);298 const stateMachine = new StateMachine({299 proxyOptions: this._proxyOptions,300 tlsOptions: this._tlsOptions,301 socketOptions: autoSelectSocketOptions(this._client.s.options)302 });303 304 const timeoutContext = TimeoutContext.create(305 resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS })306 );307 308 const { v: dataKeys } = deserialize(309 await stateMachine.execute(this, context, { timeoutContext })310 );311 if (dataKeys.length === 0) {312 return {};313 }314 315 const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(316 this._keyVaultNamespace317 );318 319 const replacements = dataKeys.map(320 (key: DataKey): AnyBulkWriteOperation<DataKey> => ({321 updateOne: {322 filter: { _id: key._id },323 update: {324 $set: {325 masterKey: key.masterKey,326 keyMaterial: key.keyMaterial327 },328 $currentDate: {329 updateDate: true330 }331 }332 }333 })334 );335 336 const result = await this._keyVaultClient337 .db(dbName)338 .collection<DataKey>(collectionName)339 .bulkWrite(replacements, {340 writeConcern: { w: 'majority' },341 timeoutMS: timeoutContext.csotEnabled() ? timeoutContext?.remainingTimeMS : undefined342 });343 344 return { bulkWriteResult: result };345 }346 347 /**348 * Deletes the key with the provided id from the keyvault, if it exists.349 *350 * @example351 * ```ts352 * // delete a key by _id353 * const id = new Binary(); // id is a bson binary subtype 4 object354 * const { deletedCount } = await clientEncryption.deleteKey(id);355 *356 * if (deletedCount != null && deletedCount > 0) {357 * // successful deletion358 * }359 * ```360 *361 */362 async deleteKey(_id: Binary): Promise<DeleteResult> {363 const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(364 this._keyVaultNamespace365 );366 367 return await this._keyVaultClient368 .db(dbName)369 .collection<DataKey>(collectionName)370 .deleteOne({ _id }, { writeConcern: { w: 'majority' }, timeoutMS: this._timeoutMS });371 }372 373 /**374 * Finds all the keys currently stored in the keyvault.375 *376 * This method will not throw.377 *378 * @returns a FindCursor over all keys in the keyvault.379 * @example380 * ```ts381 * // fetching all keys382 * const keys = await clientEncryption.getKeys().toArray();383 * ```384 */385 getKeys(): FindCursor<DataKey> {386 const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(387 this._keyVaultNamespace388 );389 390 return this._keyVaultClient391 .db(dbName)392 .collection<DataKey>(collectionName)393 .find({}, { readConcern: { level: 'majority' }, timeoutMS: this._timeoutMS });394 }395 396 /**397 * Finds a key in the keyvault with the specified _id.398 *399 * Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents400 * match the id. The promise rejects with an error if an error is thrown.401 * @example402 * ```ts403 * // getting a key by id404 * const id = new Binary(); // id is a bson binary subtype 4 object405 * const key = await clientEncryption.getKey(id);406 * if (!key) {407 * // key is null if there was no matching key408 * }409 * ```410 */411 async getKey(_id: Binary): Promise<DataKey | null> {412 const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(413 this._keyVaultNamespace414 );415 416 return await this._keyVaultClient417 .db(dbName)418 .collection<DataKey>(collectionName)419 .findOne({ _id }, { readConcern: { level: 'majority' }, timeoutMS: this._timeoutMS });420 }421 422 /**423 * Finds a key in the keyvault which has the specified keyAltName.424 *425 * @param keyAltName - a keyAltName to search for a key426 * @returns Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents427 * match the keyAltName. The promise rejects with an error if an error is thrown.428 * @example429 * ```ts430 * // get a key by alt name431 * const keyAltName = 'keyAltName';432 * const key = await clientEncryption.getKeyByAltName(keyAltName);433 * if (!key) {434 * // key is null if there is no matching key435 * }436 * ```437 */438 async getKeyByAltName(keyAltName: string): Promise<WithId<DataKey> | null> {439 const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(440 this._keyVaultNamespace441 );442 443 return await this._keyVaultClient444 .db(dbName)445 .collection<DataKey>(collectionName)446 .findOne(447 { keyAltNames: keyAltName },448 { readConcern: { level: 'majority' }, timeoutMS: this._timeoutMS }449 );450 }451 452 /**453 * Adds a keyAltName to a key identified by the provided _id.454 *455 * This method resolves to/returns the *old* key value (prior to adding the new altKeyName).456 *457 * @param _id - The id of the document to update.458 * @param keyAltName - a keyAltName to search for a key459 * @returns Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents460 * match the id. The promise rejects with an error if an error is thrown.461 * @example462 * ```ts463 * // adding an keyAltName to a data key464 * const id = new Binary(); // id is a bson binary subtype 4 object465 * const keyAltName = 'keyAltName';466 * const oldKey = await clientEncryption.addKeyAltName(id, keyAltName);467 * if (!oldKey) {468 * // null is returned if there is no matching document with an id matching the supplied id469 * }470 * ```471 */472 async addKeyAltName(_id: Binary, keyAltName: string): Promise<WithId<DataKey> | null> {473 const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(474 this._keyVaultNamespace475 );476 477 const value = await this._keyVaultClient478 .db(dbName)479 .collection<DataKey>(collectionName)480 .findOneAndUpdate(481 { _id },482 { $addToSet: { keyAltNames: keyAltName } },483 { writeConcern: { w: 'majority' }, returnDocument: 'before', timeoutMS: this._timeoutMS }484 );485 486 return value;487 }488 489 /**490 * Adds a keyAltName to a key identified by the provided _id.491 *492 * This method resolves to/returns the *old* key value (prior to removing the new altKeyName).493 *494 * If the removed keyAltName is the last keyAltName for that key, the `altKeyNames` property is unset from the document.495 *496 * @param _id - The id of the document to update.497 * @param keyAltName - a keyAltName to search for a key498 * @returns Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents499 * match the id. The promise rejects with an error if an error is thrown.500 * @example501 * ```ts502 * // removing a key alt name from a data key503 * const id = new Binary(); // id is a bson binary subtype 4 object504 * const keyAltName = 'keyAltName';505 * const oldKey = await clientEncryption.removeKeyAltName(id, keyAltName);506 *507 * if (!oldKey) {508 * // null is returned if there is no matching document with an id matching the supplied id509 * }510 * ```511 */512 async removeKeyAltName(_id: Binary, keyAltName: string): Promise<WithId<DataKey> | null> {513 const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(514 this._keyVaultNamespace515 );516 517 const pipeline = [518 {519 $set: {520 keyAltNames: {521 $cond: [522 {523 $eq: ['$keyAltNames', [keyAltName]]524 },525 '$$REMOVE',526 {527 $filter: {528 input: '$keyAltNames',529 cond: {530 $ne: ['$$this', keyAltName]531 }532 }533 }534 ]535 }536 }537 }538 ];539 540 const value = await this._keyVaultClient541 .db(dbName)542 .collection<DataKey>(collectionName)543 .findOneAndUpdate({ _id }, pipeline, {544 writeConcern: { w: 'majority' },545 returnDocument: 'before',546 timeoutMS: this._timeoutMS547 });548 549 return value;550 }551 552 /**553 * A convenience method for creating an encrypted collection.554 * This method will create data keys for any encryptedFields that do not have a `keyId` defined555 * and then create a new collection with the full set of encryptedFields.556 *557 * @param db - A Node.js driver Db object with which to create the collection558 * @param name - The name of the collection to be created559 * @param options - Options for createDataKey and for createCollection560 * @returns created collection and generated encryptedFields561 * @throws MongoCryptCreateDataKeyError - If part way through the process a createDataKey invocation fails, an error will be rejected that has the partial `encryptedFields` that were created.562 * @throws MongoCryptCreateEncryptedCollectionError - If creating the collection fails, an error will be rejected that has the entire `encryptedFields` that were created.563 */564 async createEncryptedCollection<TSchema extends Document = Document>(565 db: Db,566 name: string,567 options: {568 provider: ClientEncryptionDataKeyProvider;569 createCollectionOptions: Omit<CreateCollectionOptions, 'encryptedFields'> & {570 encryptedFields: Document;571 };572 masterKey?: AWSEncryptionKeyOptions | AzureEncryptionKeyOptions | GCPEncryptionKeyOptions;573 }574 ): Promise<{ collection: Collection<TSchema>; encryptedFields: Document }> {575 const {576 provider,577 masterKey,578 createCollectionOptions: {579 encryptedFields: { ...encryptedFields },580 ...createCollectionOptions581 }582 } = options;583 584 const timeoutContext =585 this._timeoutMS != null586 ? TimeoutContext.create(resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS }))587 : undefined;588 589 if (Array.isArray(encryptedFields.fields)) {590 const createDataKeyPromises = encryptedFields.fields.map(async field =>591 field == null || typeof field !== 'object' || field.keyId != null592 ? field593 : {594 ...field,595 keyId: await this.createDataKey(provider, {596 masterKey,597 // clone the timeoutContext598 // in order to avoid sharing the same timeout for server selection and connection checkout across different concurrent operations599 timeoutContext: timeoutContext?.csotEnabled() ? timeoutContext?.clone() : undefined600 })601 }602 );603 const createDataKeyResolutions = await Promise.allSettled(createDataKeyPromises);604 605 encryptedFields.fields = createDataKeyResolutions.map((resolution, index) =>606 resolution.status === 'fulfilled' ? resolution.value : encryptedFields.fields[index]607 );608 609 const rejection = createDataKeyResolutions.find(610 (result): result is PromiseRejectedResult => result.status === 'rejected'611 );612 if (rejection != null) {613 throw new MongoCryptCreateDataKeyError(encryptedFields, { cause: rejection.reason });614 }615 }616 617 try {618 const collection = await db.createCollection<TSchema>(name, {619 ...createCollectionOptions,620 encryptedFields,621 timeoutMS: timeoutContext?.csotEnabled()622 ? timeoutContext?.getRemainingTimeMSOrThrow()623 : undefined624 });625 return { collection, encryptedFields };626 } catch (cause) {627 throw new MongoCryptCreateEncryptedCollectionError(encryptedFields, { cause });628 }629 }630 631 /**632 * Explicitly encrypt a provided value. Note that either `options.keyId` or `options.keyAltName` must633 * be specified. Specifying both `options.keyId` and `options.keyAltName` is considered an error.634 *635 * @param value - The value that you wish to serialize. Must be of a type that can be serialized into BSON636 * @param options -637 * @returns a Promise that either resolves with the encrypted value, or rejects with an error.638 *639 * @example640 * ```ts641 * // Encryption with async/await api642 * async function encryptMyData(value) {643 * const keyId = await clientEncryption.createDataKey('local');644 * return clientEncryption.encrypt(value, { keyId, algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' });645 * }646 * ```647 *648 * @example649 * ```ts650 * // Encryption using a keyAltName651 * async function encryptMyData(value) {652 * await clientEncryption.createDataKey('local', { keyAltNames: 'mySpecialKey' });653 * return clientEncryption.encrypt(value, { keyAltName: 'mySpecialKey', algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' });654 * }655 * ```656 */657 async encrypt(value: unknown, options: ClientEncryptionEncryptOptions): Promise<Binary> {658 return await this._encrypt(value, false, options);659 }660 661 /**662 * Encrypts a Match Expression or Aggregate Expression to query a range index.663 *664 * Only supported when queryType is "range" and algorithm is "Range".665 *666 * @param expression - a BSON document of one of the following forms:667 * 1. A Match Expression of this form:668 * `{$and: [{<field>: {$gt: <value1>}}, {<field>: {$lt: <value2> }}]}`669 * 2. An Aggregate Expression of this form:670 * `{$and: [{$gt: [<fieldpath>, <value1>]}, {$lt: [<fieldpath>, <value2>]}]}`671 *672 * `$gt` may also be `$gte`. `$lt` may also be `$lte`.673 *674 * @param options -675 * @returns Returns a Promise that either resolves with the encrypted value or rejects with an error.676 */677 async encryptExpression(678 expression: Document,679 options: ClientEncryptionEncryptOptions680 ): Promise<Binary> {681 return await this._encrypt(expression, true, options);682 }683 684 /**685 * Explicitly decrypt a provided encrypted value686 *687 * @param value - An encrypted value688 * @returns a Promise that either resolves with the decrypted value, or rejects with an error689 *690 * @example691 * ```ts692 * // Decrypting value with async/await API693 * async function decryptMyValue(value) {694 * return clientEncryption.decrypt(value);695 * }696 * ```697 */698 async decrypt<T = any>(value: Binary): Promise<T> {699 const valueBuffer = serialize({ v: value });700 const context = this._mongoCrypt.makeExplicitDecryptionContext(valueBuffer);701 702 const stateMachine = new StateMachine({703 proxyOptions: this._proxyOptions,704 tlsOptions: this._tlsOptions,705 socketOptions: autoSelectSocketOptions(this._client.s.options)706 });707 708 const timeoutContext =709 this._timeoutMS != null710 ? TimeoutContext.create(resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS }))711 : undefined;712 713 const { v } = deserialize(await stateMachine.execute(this, context, { timeoutContext }));714 715 return v;716 }717 718 /**719 * @internal720 * Ask the user for KMS credentials.721 *722 * This returns anything that looks like the kmsProviders original input723 * option. It can be empty, and any provider specified here will override724 * the original ones.725 */726 async askForKMSCredentials(): Promise<KMSProviders> {727 return await refreshKMSCredentials(this._kmsProviders, this._credentialProviders);728 }729 730 static get libmongocryptVersion() {731 return ClientEncryption.getMongoCrypt().libmongocryptVersion;732 }733 734 /**735 * @internal736 * A helper that perform explicit encryption of values and expressions.737 * Explicitly encrypt a provided value. Note that either `options.keyId` or `options.keyAltName` must738 * be specified. Specifying both `options.keyId` and `options.keyAltName` is considered an error.739 *740 * @param value - The value that you wish to encrypt. Must be of a type that can be serialized into BSON741 * @param expressionMode - a boolean that indicates whether or not to encrypt the value as an expression742 * @param options - options to pass to encrypt743 * @returns the raw result of the call to stateMachine.execute(). When expressionMode is set to true, the return744 * value will be a bson document. When false, the value will be a BSON Binary.745 *746 */747 private async _encrypt(748 value: unknown,749 expressionMode: boolean,750 options: ClientEncryptionEncryptOptions751 ): Promise<Binary> {752 const { algorithm, keyId, keyAltName, contentionFactor, queryType, rangeOptions, textOptions } =753 options;754 const contextOptions: ExplicitEncryptionContextOptions = {755 expressionMode,756 algorithm757 };758 if (keyId) {759 contextOptions.keyId = keyId.buffer;760 }761 if (keyAltName) {762 if (keyId) {763 throw new MongoCryptInvalidArgumentError(764 `"options" cannot contain both "keyId" and "keyAltName"`765 );766 }767 if (typeof keyAltName !== 'string') {768 throw new MongoCryptInvalidArgumentError(769 `"options.keyAltName" must be of type string, but was of type ${typeof keyAltName}`770 );771 }772 773 contextOptions.keyAltName = serialize({ keyAltName });774 }775 if (typeof contentionFactor === 'number' || typeof contentionFactor === 'bigint') {776 contextOptions.contentionFactor = contentionFactor;777 }778 if (typeof queryType === 'string') {779 contextOptions.queryType = queryType;780 }781 782 if (typeof rangeOptions === 'object') {783 contextOptions.rangeOptions = serialize(rangeOptions);784 }785 786 if (typeof textOptions === 'object') {787 contextOptions.textOptions = serialize(textOptions);788 }789 790 const valueBuffer = serialize({ v: value });791 const stateMachine = new StateMachine({792 proxyOptions: this._proxyOptions,793 tlsOptions: this._tlsOptions,794 socketOptions: autoSelectSocketOptions(this._client.s.options)795 });796 const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);797 798 const timeoutContext =799 this._timeoutMS != null800 ? TimeoutContext.create(resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS }))801 : undefined;802 const { v } = deserialize(await stateMachine.execute(this, context, { timeoutContext }));803 return v;804 }805}806 807/**808 * @public809 * Options to provide when encrypting data.810 */811export interface ClientEncryptionEncryptOptions {812 /**813 * The algorithm to use for encryption.814 */815 algorithm:816 | 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'817 | 'AEAD_AES_256_CBC_HMAC_SHA_512-Random'818 | 'Indexed'819 | 'Unindexed'820 | 'Range'821 | 'TextPreview';822 823 /**824 * The id of the Binary dataKey to use for encryption825 */826 keyId?: Binary;827 828 /**829 * A unique string name corresponding to an already existing dataKey.830 */831 keyAltName?: string;832 833 /** The contention factor. */834 contentionFactor?: bigint | number;835 836 /**837 * The query type.838 */839 queryType?: 'equality' | 'range' | 'prefixPreview' | 'suffixPreview' | 'substringPreview';840 841 /** The index options for a Queryable Encryption field supporting "range" queries.*/842 rangeOptions?: RangeOptions;843 844 /**845 * Options for a Queryable Encryption field supporting text queries. Only valid when `algorithm` is `TextPreview`.846 *847 * @experimental Public Technical Preview: `textPreview` is an experimental feature and may break at any time.848 */849 textOptions?: TextQueryOptions;850}851 852/**853 * Options for a Queryable Encryption field supporting text queries.854 *855 * @public856 * @experimental Public Technical Preview: `textPreview` is an experimental feature and may break at any time.857 */858export interface TextQueryOptions {859 /** Indicates that text indexes for this field are case sensitive */860 caseSensitive: boolean;861 /** Indicates that text indexes for this field are diacritic sensitive. */862 diacriticSensitive: boolean;863 864 prefix?: {865 /** The maximum allowed query length. */866 strMaxQueryLength: Int32 | number;867 /** The minimum allowed query length. */868 strMinQueryLength: Int32 | number;869 };870 871 suffix?: {872 /** The maximum allowed query length. */873 strMaxQueryLength: Int32 | number;874 /** The minimum allowed query length. */875 strMinQueryLength: Int32 | number;876 };877 878 substring?: {879 /** The maximum allowed length to insert. */880 strMaxLength: Int32 | number;881 /** The maximum allowed query length. */882 strMaxQueryLength: Int32 | number;883 /** The minimum allowed query length. */884 strMinQueryLength: Int32 | number;885 };886}887 888/**889 * @public890 * @experimental891 */892export interface ClientEncryptionRewrapManyDataKeyProviderOptions {893 provider: ClientEncryptionDataKeyProvider;894 masterKey?:895 | AWSEncryptionKeyOptions896 | AzureEncryptionKeyOptions897 | GCPEncryptionKeyOptions898 | KMIPEncryptionKeyOptions899 | undefined;900}901 902/**903 * @public904 * Additional settings to provide when creating a new `ClientEncryption` instance.905 */906export interface ClientEncryptionOptions {907 /**908 * The namespace of the key vault, used to store encryption keys909 */910 keyVaultNamespace: string;911 912 /**913 * A MongoClient used to fetch keys from a key vault. Defaults to client.914 */915 keyVaultClient?: MongoClient | undefined;916 917 /**918 * Options for specific KMS providers to use919 */920 kmsProviders?: KMSProviders;921 922 /**923 * Options for user provided custom credential providers.924 */925 credentialProviders?: CredentialProviders;926 927 /**928 * Options for specifying a Socks5 proxy to use for connecting to the KMS.929 */930 proxyOptions?: ProxyOptions;931 932 /**933 * TLS options for kms providers to use.934 */935 tlsOptions?: CSFLEKMSTlsOptions;936 937 /**938 * Sets the expiration time for the DEK in the cache in milliseconds. Defaults to 60000. 0 means no timeout.939 */940 keyExpirationMS?: number;941 942 /**943 * @experimental944 *945 * The timeout setting to be used for all the operations on ClientEncryption.946 *947 * When provided, `timeoutMS` is used as the timeout for each operation executed on948 * the ClientEncryption object. For example:949 *950 * ```typescript951 * const clientEncryption = new ClientEncryption(client, {952 * timeoutMS: 1_000953 * kmsProviders: { local: { key: '<KEY>' } }954 * });955 *956 * // `1_000` is used as the timeout for createDataKey call957 * await clientEncryption.createDataKey('local');958 * ```959 *960 * If `timeoutMS` is configured on the provided client, the client's `timeoutMS` value961 * will be used unless `timeoutMS` is also provided as a client encryption option.962 *963 * ```typescript964 * const client = new MongoClient('<uri>', { timeoutMS: 2_000 });965 *966 * // timeoutMS is set to 1_000 on clientEncryption967 * const clientEncryption = new ClientEncryption(client, {968 * timeoutMS: 1_000969 * kmsProviders: { local: { key: '<KEY>' } }970 * });971 * ```972 */973 timeoutMS?: number;974}975 976/**977 * @public978 * Configuration options for making an AWS encryption key979 */980export interface AWSEncryptionKeyOptions {981 /**982 * The AWS region of the KMS983 */984 region: string;985 986 /**987 * The Amazon Resource Name (ARN) to the AWS customer master key (CMK)988 */989 key: string;990 991 /**992 * An alternate host to send KMS requests to. May include port number.993 */994 endpoint?: string | undefined;995}996 997/**998 * @public999 * Configuration options for making an AWS encryption key1000 */1001export interface GCPEncryptionKeyOptions {1002 /**1003 * GCP project ID1004 */1005 projectId: string;1006 1007 /**1008 * Location name (e.g. "global")1009 */1010 location: string;1011 1012 /**1013 * Key ring name1014 */1015 keyRing: string;1016 1017 /**1018 * Key name1019 */1020 keyName: string;1021 1022 /**1023 * Key version1024 */1025 keyVersion?: string | undefined;1026 1027 /**1028 * KMS URL, defaults to `https://www.googleapis.com/auth/cloudkms`1029 */1030 endpoint?: string | undefined;1031}1032 1033/**1034 * @public1035 * Configuration options for making an Azure encryption key1036 */1037export interface AzureEncryptionKeyOptions {1038 /**1039 * Key name1040 */1041 keyName: string;1042 1043 /**1044 * Key vault URL, typically `<name>.vault.azure.net`1045 */1046 keyVaultEndpoint: string;1047 1048 /**1049 * Key version1050 */1051 keyVersion?: string | undefined;1052}1053 1054/**1055 * @public1056 * Configuration options for making a KMIP encryption key1057 */1058export interface KMIPEncryptionKeyOptions {1059 /**1060 * keyId is the KMIP Unique Identifier to a 96 byte KMIP Secret Data managed object.1061 *1062 * If keyId is omitted, a random 96 byte KMIP Secret Data managed object will be created.1063 */1064 keyId?: string;1065 1066 /**1067 * Host with optional port.1068 */1069 endpoint?: string;1070 1071 /**1072 * If true, this key should be decrypted by the KMIP server.1073 *1074 * Requires `mongodb-client-encryption>=6.0.1`.1075 */1076 delegated?: boolean;1077}1078 1079/**1080 * @public1081 * Options to provide when creating a new data key.1082 */1083export interface ClientEncryptionCreateDataKeyProviderOptions {1084 /**1085 * Identifies a new KMS-specific key used to encrypt the new data key1086 */1087 masterKey?:1088 | AWSEncryptionKeyOptions1089 | AzureEncryptionKeyOptions1090 | GCPEncryptionKeyOptions1091 | KMIPEncryptionKeyOptions1092 | undefined;1093 1094 /**1095 * An optional list of string alternate names used to reference a key.1096 * If a key is created with alternate names, then encryption may refer to the key by the unique alternate name instead of by _id.1097 */1098 keyAltNames?: string[] | undefined;1099 1100 /** @experimental */1101 keyMaterial?: Buffer | Binary;1102 1103 /** @internal */1104 timeoutContext?: CSOTTimeoutContext;1105}1106 1107/**1108 * @public1109 * @experimental1110 */1111export interface ClientEncryptionRewrapManyDataKeyResult {1112 /** The result of rewrapping data keys. If unset, no keys matched the filter. */1113 bulkWriteResult?: BulkWriteResult;1114}1115 1116/**1117 * @public1118 * RangeOptions specifies index options for a Queryable Encryption field supporting "range" queries.1119 * min, max, sparsity, trimFactor and range must match the values set in the encryptedFields of the destination collection.1120 * For double and decimal128, min/max/precision must all be set, or all be unset.1121 */1122export interface RangeOptions {1123 /** min is the minimum value for the encrypted index. Required if precision is set. */1124 min?: any;1125 /** max is the minimum value for the encrypted index. Required if precision is set. */1126 max?: any;1127 /** sparsity may be used to tune performance. must be non-negative. When omitted, a default value is used. */1128 sparsity?: Long | bigint;1129 /** trimFactor may be used to tune performance. must be non-negative. When omitted, a default value is used. */1130 trimFactor?: Int32 | number;1131 /* precision determines the number of significant digits after the decimal point. May only be set for double or decimal128. */1132 precision?: number;1133}1134 1135/**1136 * Get the socket options from the client.1137 * @param baseOptions - The mongo client options.1138 * @returns ClientEncryptionSocketOptions1139 */1140export function autoSelectSocketOptions(1141 baseOptions: MongoClientOptions1142): ClientEncryptionSocketOptions {1143 const options: ClientEncryptionSocketOptions = { autoSelectFamily: true };1144 if ('autoSelectFamily' in baseOptions) {1145 options.autoSelectFamily = baseOptions.autoSelectFamily;1146 }1147 if ('autoSelectFamilyAttemptTimeout' in baseOptions) {1148 options.autoSelectFamilyAttemptTimeout = baseOptions.autoSelectFamilyAttemptTimeout;1149 }1150 return options;1151}1152 