All files / src encrypter.ts

89.47% Statements 51/57
86.66% Branches 26/30
85.71% Functions 6/7
89.47% Lines 51/57

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135131x   131x 131x 131x 131x 131x                   131x             9931x       9931x   9931x 9931x   9931x 82x 9849x 9562x     9931x 255x 9676x 82x   9594x     9931x                 9931x       19156x 19156x 9726x   9726x       58965x 9726x 49239x     9726x   9726x 9726x   9726x 252876x 9726x       9726x 134948x     9726x   19156x       9684x 9684x 9479x 9479x                   9716x 9716x       9716x 9716x 9511x   205x           9931x 9931x                        
import { callbackify } from 'util';
 
import { AutoEncrypter, type AutoEncryptionOptions } from './client-side-encryption/auto_encrypter';
import { MONGO_CLIENT_EVENTS } from './constants';
import { getMongoDBClientEncryption } from './deps';
import { MongoInvalidArgumentError, MongoMissingDependencyError } from './error';
import { MongoClient, type MongoClientOptions } from './mongo_client';
import { type Callback } from './utils';
 
/** @internal */
export interface EncrypterOptions {
  autoEncryption: AutoEncryptionOptions;
  maxPoolSize?: number;
}
 
/** @internal */
export class Encrypter {
  private internalClient: MongoClient | null;
  bypassAutoEncryption: boolean;
  needsConnecting: boolean;
  autoEncrypter: AutoEncrypter;
 
  constructor(client: MongoClient, uri: string, options: MongoClientOptions) {
    Iif (typeof options.autoEncryption !== 'object') {
      throw new MongoInvalidArgumentError('Option "autoEncryption" must be specified');
    }
    // initialize to null, if we call getInternalClient, we may set this it is important to not overwrite those function calls.
    this.internalClient = null;
 
    this.bypassAutoEncryption = !!options.autoEncryption.bypassAutoEncryption;
    this.needsConnecting = false;
 
    if (options.maxPoolSize === 0 && options.autoEncryption.keyVaultClient == null) {
      options.autoEncryption.keyVaultClient = client;
    } else if (options.autoEncryption.keyVaultClient == null) {
      options.autoEncryption.keyVaultClient = this.getInternalClient(client, uri, options);
    }
 
    if (this.bypassAutoEncryption) {
      options.autoEncryption.metadataClient = undefined;
    } else if (options.maxPoolSize === 0) {
      options.autoEncryption.metadataClient = client;
    } else {
      options.autoEncryption.metadataClient = this.getInternalClient(client, uri, options);
    }
 
    Iif (options.proxyHost) {
      options.autoEncryption.proxyOptions = {
        proxyHost: options.proxyHost,
        proxyPort: options.proxyPort,
        proxyUsername: options.proxyUsername,
        proxyPassword: options.proxyPassword
      };
    }
 
    this.autoEncrypter = new AutoEncrypter(client, options.autoEncryption);
  }
 
  getInternalClient(client: MongoClient, uri: string, options: MongoClientOptions): MongoClient {
    let internalClient = this.internalClient;
    if (internalClient == null) {
      const clonedOptions: MongoClientOptions = {};
 
      for (const key of [
        ...Object.getOwnPropertyNames(options),
        ...Object.getOwnPropertySymbols(options)
      ] as string[]) {
        if (['autoEncryption', 'minPoolSize', 'servers', 'caseTranslate', 'dbName'].includes(key))
          continue;
        Reflect.set(clonedOptions, key, Reflect.get(options, key));
      }
 
      clonedOptions.minPoolSize = 0;
 
      internalClient = new MongoClient(uri, clonedOptions);
      this.internalClient = internalClient;
 
      for (const eventName of MONGO_CLIENT_EVENTS) {
        for (const listener of client.listeners(eventName)) {
          internalClient.on(eventName, listener);
        }
      }
 
      client.on('newListener', (eventName, listener) => {
        internalClient?.on(eventName, listener);
      });
 
      this.needsConnecting = true;
    }
    return internalClient;
  }
 
  async connectInternalClient(): Promise<void> {
    const internalClient = this.internalClient;
    if (this.needsConnecting && internalClient != null) {
      this.needsConnecting = false;
      await internalClient.connect();
    }
  }
 
  closeCallback(client: MongoClient, force: boolean, callback: Callback<void>) {
    callbackify(this.close.bind(this))(client, force, callback);
  }
 
  async close(client: MongoClient, force: boolean): Promise<void> {
    let error;
    try {
      await this.autoEncrypter.teardown(force);
    } catch (autoEncrypterError) {
      error = autoEncrypterError;
    }
    const internalClient = this.internalClient;
    if (internalClient != null && client !== internalClient) {
      return await internalClient.close(force);
    }
    Iif (error != null) {
      throw error;
    }
  }
 
  static checkForMongoCrypt(): void {
    const mongodbClientEncryption = getMongoDBClientEncryption();
    Iif ('kModuleError' in mongodbClientEncryption) {
      throw new MongoMissingDependencyError(
        'Auto-encryption requested, but the module is not installed. ' +
          'Please add `mongodb-client-encryption` as a dependency of your project',
        {
          cause: mongodbClientEncryption['kModuleError'],
          dependencyName: 'mongodb-client-encryption'
        }
      );
    }
  }
}