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 135392x   392x 392x 392x 392x 392x                   392x             39110x       39110x   39110x 39110x   39110x 348x 38762x 37544x     39110x 920x 38190x 348x   37842x     39110x                 39110x       75386x 75386x 38240x   38240x       228162x 38240x 189922x     38240x   38240x 38240x   38240x 994240x 38240x       38240x 514727x     38240x   75386x       38122x 38122x 37252x 37252x                   38154x 38154x       38154x 38154x 37284x   870x           39110x 39110x                        
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'
        }
      );
    }
  }
}