CoolFace
Apppublic

opusdev/vector-similarity-api

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes
client_encryption.ts1152 linesDownload Raw Back to client-side-encryption
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