| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555 | "use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.ClientEncryption = void 0;const bson_1 = require("../bson");const deps_1 = require("../deps");const utils_1 = require("../utils");const cryptoCallbacks = require("./crypto_callbacks");const errors_1 = require("./errors");const index_1 = require("./providers/index");const state_machine_1 = require("./state_machine");/** * @public * The public interface for explicit in-use encryption */class ClientEncryption {    /** @internal */    static getMongoCrypt() {        const encryption = (0, deps_1.getMongoDBClientEncryption)();        if ('kModuleError' in encryption) {            throw encryption.kModuleError;        }        return encryption.MongoCrypt;    }    /**     * Create a new encryption instance     *     * @example     * ```ts     * new ClientEncryption(mongoClient, {     *   keyVaultNamespace: 'client.encryption',     *   kmsProviders: {     *     local: {     *       key: masterKey // The master key used for encryption/decryption. A 96-byte long Buffer     *     }     *   }     * });     * ```     *     * @example     * ```ts     * new ClientEncryption(mongoClient, {     *   keyVaultNamespace: 'client.encryption',     *   kmsProviders: {     *     aws: {     *       accessKeyId: AWS_ACCESS_KEY,     *       secretAccessKey: AWS_SECRET_KEY     *     }     *   }     * });     * ```     */    constructor(client, options) {        this._client = client;        this._proxyOptions = options.proxyOptions ?? {};        this._tlsOptions = options.tlsOptions ?? {};        this._kmsProviders = options.kmsProviders || {};        if (options.keyVaultNamespace == null) {            throw new errors_1.MongoCryptInvalidArgumentError('Missing required option `keyVaultNamespace`');        }        const mongoCryptOptions = {            ...options,            cryptoCallbacks,            kmsProviders: !Buffer.isBuffer(this._kmsProviders)                ? (0, bson_1.serialize)(this._kmsProviders)                : this._kmsProviders        };        this._keyVaultNamespace = options.keyVaultNamespace;        this._keyVaultClient = options.keyVaultClient || client;        const MongoCrypt = ClientEncryption.getMongoCrypt();        this._mongoCrypt = new MongoCrypt(mongoCryptOptions);    }    /**     * Creates a data key used for explicit encryption and inserts it into the key vault namespace     *     * @example     * ```ts     * // Using async/await to create a local key     * const dataKeyId = await clientEncryption.createDataKey('local');     * ```     *     * @example     * ```ts     * // Using async/await to create an aws key     * const dataKeyId = await clientEncryption.createDataKey('aws', {     *   masterKey: {     *     region: 'us-east-1',     *     key: 'xxxxxxxxxxxxxx' // CMK ARN here     *   }     * });     * ```     *     * @example     * ```ts     * // Using async/await to create an aws key with a keyAltName     * const dataKeyId = await clientEncryption.createDataKey('aws', {     *   masterKey: {     *     region: 'us-east-1',     *     key: 'xxxxxxxxxxxxxx' // CMK ARN here     *   },     *   keyAltNames: [ 'mySpecialKey' ]     * });     * ```     */    async createDataKey(provider, options = {}) {        if (options.keyAltNames && !Array.isArray(options.keyAltNames)) {            throw new errors_1.MongoCryptInvalidArgumentError(`Option "keyAltNames" must be an array of strings, but was of type ${typeof options.keyAltNames}.`);        }        let keyAltNames = undefined;        if (options.keyAltNames && options.keyAltNames.length > 0) {            keyAltNames = options.keyAltNames.map((keyAltName, i) => {                if (typeof keyAltName !== 'string') {                    throw new errors_1.MongoCryptInvalidArgumentError(`Option "keyAltNames" must be an array of strings, but item at index ${i} was of type ${typeof keyAltName}`);                }                return (0, bson_1.serialize)({ keyAltName });            });        }        let keyMaterial = undefined;        if (options.keyMaterial) {            keyMaterial = (0, bson_1.serialize)({ keyMaterial: options.keyMaterial });        }        const dataKeyBson = (0, bson_1.serialize)({            provider,            ...options.masterKey        });        const context = this._mongoCrypt.makeDataKeyContext(dataKeyBson, {            keyAltNames,            keyMaterial        });        const stateMachine = new state_machine_1.StateMachine({            proxyOptions: this._proxyOptions,            tlsOptions: this._tlsOptions        });        const dataKey = await stateMachine.execute(this, context);        const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);        const { insertedId } = await this._keyVaultClient            .db(dbName)            .collection(collectionName)            .insertOne(dataKey, { writeConcern: { w: 'majority' } });        return insertedId;    }    /**     * 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.     *     * If no matches are found, then no bulk write is performed.     *     * @example     * ```ts     * // rewrapping all data data keys (using a filter that matches all documents)     * const filter = {};     *     * const result = await clientEncryption.rewrapManyDataKey(filter);     * if (result.bulkWriteResult != null) {     *  // keys were re-wrapped, results will be available in the bulkWrite object.     * }     * ```     *     * @example     * ```ts     * // attempting to rewrap all data keys with no matches     * const filter = { _id: new Binary() } // assume _id matches no documents in the database     * const result = await clientEncryption.rewrapManyDataKey(filter);     *     * if (result.bulkWriteResult == null) {     *  // no keys matched, `bulkWriteResult` does not exist on the result object     * }     * ```     */    async rewrapManyDataKey(filter, options) {        let keyEncryptionKeyBson = undefined;        if (options) {            const keyEncryptionKey = Object.assign({ provider: options.provider }, options.masterKey);            keyEncryptionKeyBson = (0, bson_1.serialize)(keyEncryptionKey);        }        const filterBson = (0, bson_1.serialize)(filter);        const context = this._mongoCrypt.makeRewrapManyDataKeyContext(filterBson, keyEncryptionKeyBson);        const stateMachine = new state_machine_1.StateMachine({            proxyOptions: this._proxyOptions,            tlsOptions: this._tlsOptions        });        const { v: dataKeys } = await stateMachine.execute(this, context);        if (dataKeys.length === 0) {            return {};        }        const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);        const replacements = dataKeys.map((key) => ({            updateOne: {                filter: { _id: key._id },                update: {                    $set: {                        masterKey: key.masterKey,                        keyMaterial: key.keyMaterial                    },                    $currentDate: {                        updateDate: true                    }                }            }        }));        const result = await this._keyVaultClient            .db(dbName)            .collection(collectionName)            .bulkWrite(replacements, {            writeConcern: { w: 'majority' }        });        return { bulkWriteResult: result };    }    /**     * Deletes the key with the provided id from the keyvault, if it exists.     *     * @example     * ```ts     * // delete a key by _id     * const id = new Binary(); // id is a bson binary subtype 4 object     * const { deletedCount } = await clientEncryption.deleteKey(id);     *     * if (deletedCount != null && deletedCount > 0) {     *   // successful deletion     * }     * ```     *     */    async deleteKey(_id) {        const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);        return this._keyVaultClient            .db(dbName)            .collection(collectionName)            .deleteOne({ _id }, { writeConcern: { w: 'majority' } });    }    /**     * Finds all the keys currently stored in the keyvault.     *     * This method will not throw.     *     * @returns a FindCursor over all keys in the keyvault.     * @example     * ```ts     * // fetching all keys     * const keys = await clientEncryption.getKeys().toArray();     * ```     */    getKeys() {        const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);        return this._keyVaultClient            .db(dbName)            .collection(collectionName)            .find({}, { readConcern: { level: 'majority' } });    }    /**     * Finds a key in the keyvault with the specified _id.     *     * Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents     * match the id.  The promise rejects with an error if an error is thrown.     * @example     * ```ts     * // getting a key by id     * const id = new Binary(); // id is a bson binary subtype 4 object     * const key = await clientEncryption.getKey(id);     * if (!key) {     *  // key is null if there was no matching key     * }     * ```     */    async getKey(_id) {        const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);        return this._keyVaultClient            .db(dbName)            .collection(collectionName)            .findOne({ _id }, { readConcern: { level: 'majority' } });    }    /**     * Finds a key in the keyvault which has the specified keyAltName.     *     * @param keyAltName - a keyAltName to search for a key     * @returns Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents     * match the keyAltName.  The promise rejects with an error if an error is thrown.     * @example     * ```ts     * // get a key by alt name     * const keyAltName = 'keyAltName';     * const key = await clientEncryption.getKeyByAltName(keyAltName);     * if (!key) {     *  // key is null if there is no matching key     * }     * ```     */    async getKeyByAltName(keyAltName) {        const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);        return this._keyVaultClient            .db(dbName)            .collection(collectionName)            .findOne({ keyAltNames: keyAltName }, { readConcern: { level: 'majority' } });    }    /**     * Adds a keyAltName to a key identified by the provided _id.     *     * This method resolves to/returns the *old* key value (prior to adding the new altKeyName).     *     * @param _id - The id of the document to update.     * @param keyAltName - a keyAltName to search for a key     * @returns Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents     * match the id.  The promise rejects with an error if an error is thrown.     * @example     * ```ts     * // adding an keyAltName to a data key     * const id = new Binary();  // id is a bson binary subtype 4 object     * const keyAltName = 'keyAltName';     * const oldKey = await clientEncryption.addKeyAltName(id, keyAltName);     * if (!oldKey) {     *  // null is returned if there is no matching document with an id matching the supplied id     * }     * ```     */    async addKeyAltName(_id, keyAltName) {        const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);        const value = await this._keyVaultClient            .db(dbName)            .collection(collectionName)            .findOneAndUpdate({ _id }, { $addToSet: { keyAltNames: keyAltName } }, { writeConcern: { w: 'majority' }, returnDocument: 'before' });        return value;    }    /**     * Adds a keyAltName to a key identified by the provided _id.     *     * This method resolves to/returns the *old* key value (prior to removing the new altKeyName).     *     * If the removed keyAltName is the last keyAltName for that key, the `altKeyNames` property is unset from the document.     *     * @param _id - The id of the document to update.     * @param keyAltName - a keyAltName to search for a key     * @returns Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents     * match the id.  The promise rejects with an error if an error is thrown.     * @example     * ```ts     * // removing a key alt name from a data key     * const id = new Binary();  // id is a bson binary subtype 4 object     * const keyAltName = 'keyAltName';     * const oldKey = await clientEncryption.removeKeyAltName(id, keyAltName);     *     * if (!oldKey) {     *  // null is returned if there is no matching document with an id matching the supplied id     * }     * ```     */    async removeKeyAltName(_id, keyAltName) {        const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);        const pipeline = [            {                $set: {                    keyAltNames: {                        $cond: [                            {                                $eq: ['$keyAltNames', [keyAltName]]                            },                            '$$REMOVE',                            {                                $filter: {                                    input: '$keyAltNames',                                    cond: {                                        $ne: ['$$this', keyAltName]                                    }                                }                            }                        ]                    }                }            }        ];        const value = await this._keyVaultClient            .db(dbName)            .collection(collectionName)            .findOneAndUpdate({ _id }, pipeline, {            writeConcern: { w: 'majority' },            returnDocument: 'before'        });        return value;    }    /**     * A convenience method for creating an encrypted collection.     * This method will create data keys for any encryptedFields that do not have a `keyId` defined     * and then create a new collection with the full set of encryptedFields.     *     * @param db - A Node.js driver Db object with which to create the collection     * @param name - The name of the collection to be created     * @param options - Options for createDataKey and for createCollection     * @returns created collection and generated encryptedFields     * @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.     * @throws MongoCryptCreateEncryptedCollectionError - If creating the collection fails, an error will be rejected that has the entire `encryptedFields` that were created.     */    async createEncryptedCollection(db, name, options) {        const { provider, masterKey, createCollectionOptions: { encryptedFields: { ...encryptedFields }, ...createCollectionOptions } } = options;        if (Array.isArray(encryptedFields.fields)) {            const createDataKeyPromises = encryptedFields.fields.map(async (field) => field == null || typeof field !== 'object' || field.keyId != null                ? field                : {                    ...field,                    keyId: await this.createDataKey(provider, { masterKey })                });            const createDataKeyResolutions = await Promise.allSettled(createDataKeyPromises);            encryptedFields.fields = createDataKeyResolutions.map((resolution, index) => resolution.status === 'fulfilled' ? resolution.value : encryptedFields.fields[index]);            const rejection = createDataKeyResolutions.find((result) => result.status === 'rejected');            if (rejection != null) {                throw new errors_1.MongoCryptCreateDataKeyError(encryptedFields, { cause: rejection.reason });            }        }        try {            const collection = await db.createCollection(name, {                ...createCollectionOptions,                encryptedFields            });            return { collection, encryptedFields };        }        catch (cause) {            throw new errors_1.MongoCryptCreateEncryptedCollectionError(encryptedFields, { cause });        }    }    /**     * Explicitly encrypt a provided value. Note that either `options.keyId` or `options.keyAltName` must     * be specified. Specifying both `options.keyId` and `options.keyAltName` is considered an error.     *     * @param value - The value that you wish to serialize. Must be of a type that can be serialized into BSON     * @param options -     * @returns a Promise that either resolves with the encrypted value, or rejects with an error.     *     * @example     * ```ts     * // Encryption with async/await api     * async function encryptMyData(value) {     *   const keyId = await clientEncryption.createDataKey('local');     *   return clientEncryption.encrypt(value, { keyId, algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' });     * }     * ```     *     * @example     * ```ts     * // Encryption using a keyAltName     * async function encryptMyData(value) {     *   await clientEncryption.createDataKey('local', { keyAltNames: 'mySpecialKey' });     *   return clientEncryption.encrypt(value, { keyAltName: 'mySpecialKey', algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' });     * }     * ```     */    async encrypt(value, options) {        return this._encrypt(value, false, options);    }    /**     * Encrypts a Match Expression or Aggregate Expression to query a range index.     *     * Only supported when queryType is "rangePreview" and algorithm is "RangePreview".     *     * @experimental The Range algorithm is experimental only. It is not intended for production use. It is subject to breaking changes.     *     * @param expression - a BSON document of one of the following forms:     *  1. A Match Expression of this form:     *      `{$and: [{<field>: {$gt: <value1>}}, {<field>: {$lt: <value2> }}]}`     *  2. An Aggregate Expression of this form:     *      `{$and: [{$gt: [<fieldpath>, <value1>]}, {$lt: [<fieldpath>, <value2>]}]}`     *     *    `$gt` may also be `$gte`. `$lt` may also be `$lte`.     *     * @param options -     * @returns Returns a Promise that either resolves with the encrypted value or rejects with an error.     */    async encryptExpression(expression, options) {        return this._encrypt(expression, true, options);    }    /**     * Explicitly decrypt a provided encrypted value     *     * @param value - An encrypted value     * @returns a Promise that either resolves with the decrypted value, or rejects with an error     *     * @example     * ```ts     * // Decrypting value with async/await API     * async function decryptMyValue(value) {     *   return clientEncryption.decrypt(value);     * }     * ```     */    async decrypt(value) {        const valueBuffer = (0, bson_1.serialize)({ v: value });        const context = this._mongoCrypt.makeExplicitDecryptionContext(valueBuffer);        const stateMachine = new state_machine_1.StateMachine({            proxyOptions: this._proxyOptions,            tlsOptions: this._tlsOptions        });        const { v } = await stateMachine.execute(this, context);        return v;    }    /**     * @internal     * Ask the user for KMS credentials.     *     * This returns anything that looks like the kmsProviders original input     * option. It can be empty, and any provider specified here will override     * the original ones.     */    async askForKMSCredentials() {        return (0, index_1.refreshKMSCredentials)(this._kmsProviders);    }    static get libmongocryptVersion() {        return ClientEncryption.getMongoCrypt().libmongocryptVersion;    }    /**     * @internal     * A helper that perform explicit encryption of values and expressions.     * Explicitly encrypt a provided value. Note that either `options.keyId` or `options.keyAltName` must     * be specified. Specifying both `options.keyId` and `options.keyAltName` is considered an error.     *     * @param value - The value that you wish to encrypt. Must be of a type that can be serialized into BSON     * @param expressionMode - a boolean that indicates whether or not to encrypt the value as an expression     * @param options - options to pass to encrypt     * @returns the raw result of the call to stateMachine.execute().  When expressionMode is set to true, the return     *          value will be a bson document.  When false, the value will be a BSON Binary.     *     */    async _encrypt(value, expressionMode, options) {        const { algorithm, keyId, keyAltName, contentionFactor, queryType, rangeOptions } = options;        const contextOptions = {            expressionMode,            algorithm        };        if (keyId) {            contextOptions.keyId = keyId.buffer;        }        if (keyAltName) {            if (keyId) {                throw new errors_1.MongoCryptInvalidArgumentError(`"options" cannot contain both "keyId" and "keyAltName"`);            }            if (typeof keyAltName !== 'string') {                throw new errors_1.MongoCryptInvalidArgumentError(`"options.keyAltName" must be of type string, but was of type ${typeof keyAltName}`);            }            contextOptions.keyAltName = (0, bson_1.serialize)({ keyAltName });        }        if (typeof contentionFactor === 'number' || typeof contentionFactor === 'bigint') {            contextOptions.contentionFactor = contentionFactor;        }        if (typeof queryType === 'string') {            contextOptions.queryType = queryType;        }        if (typeof rangeOptions === 'object') {            contextOptions.rangeOptions = (0, bson_1.serialize)(rangeOptions);        }        const valueBuffer = (0, bson_1.serialize)({ v: value });        const stateMachine = new state_machine_1.StateMachine({            proxyOptions: this._proxyOptions,            tlsOptions: this._tlsOptions        });        const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);        const result = await stateMachine.execute(this, context);        return result.v;    }}exports.ClientEncryption = ClientEncryption;//# sourceMappingURL=client_encryption.js.map
 |