Spaces:
Runtime error
Runtime error
Download static/node_modules/mongodb/lib/client-side-encryption/state_machine.js from kaab4321/NarrativeFlow: direct link, hf CLI and curl.
- Browser
- Download file 17.2 kB
-
https://huggingface.co/spaces/kaab4321/NarrativeFlow/resolve/main/static/node_modules/mongodb/lib/client-side-encryption/state_machine.js
- Command line
-
hf download hf://spaces/kaab4321/NarrativeFlow/static/node_modules/mongodb/lib/client-side-encryption/state_machine.js
-
curl -L -o state_machine.js https://huggingface.co/spaces/kaab4321/NarrativeFlow/resolve/main/static/node_modules/mongodb/lib/client-side-encryption/state_machine.js
17.2 kB
| ; | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.StateMachine = void 0; | |
| const fs = require("fs/promises"); | |
| const net = require("net"); | |
| const tls = require("tls"); | |
| const bson_1 = require("../bson"); | |
| const deps_1 = require("../deps"); | |
| const utils_1 = require("../utils"); | |
| const errors_1 = require("./errors"); | |
| let socks = null; | |
| function loadSocks() { | |
| if (socks == null) { | |
| const socksImport = (0, deps_1.getSocks)(); | |
| if ('kModuleError' in socksImport) { | |
| throw socksImport.kModuleError; | |
| } | |
| socks = socksImport; | |
| } | |
| return socks; | |
| } | |
| // libmongocrypt states | |
| const MONGOCRYPT_CTX_ERROR = 0; | |
| const MONGOCRYPT_CTX_NEED_MONGO_COLLINFO = 1; | |
| const MONGOCRYPT_CTX_NEED_MONGO_MARKINGS = 2; | |
| const MONGOCRYPT_CTX_NEED_MONGO_KEYS = 3; | |
| const MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS = 7; | |
| const MONGOCRYPT_CTX_NEED_KMS = 4; | |
| const MONGOCRYPT_CTX_READY = 5; | |
| const MONGOCRYPT_CTX_DONE = 6; | |
| const HTTPS_PORT = 443; | |
| const stateToString = new Map([ | |
| [MONGOCRYPT_CTX_ERROR, 'MONGOCRYPT_CTX_ERROR'], | |
| [MONGOCRYPT_CTX_NEED_MONGO_COLLINFO, 'MONGOCRYPT_CTX_NEED_MONGO_COLLINFO'], | |
| [MONGOCRYPT_CTX_NEED_MONGO_MARKINGS, 'MONGOCRYPT_CTX_NEED_MONGO_MARKINGS'], | |
| [MONGOCRYPT_CTX_NEED_MONGO_KEYS, 'MONGOCRYPT_CTX_NEED_MONGO_KEYS'], | |
| [MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS, 'MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS'], | |
| [MONGOCRYPT_CTX_NEED_KMS, 'MONGOCRYPT_CTX_NEED_KMS'], | |
| [MONGOCRYPT_CTX_READY, 'MONGOCRYPT_CTX_READY'], | |
| [MONGOCRYPT_CTX_DONE, 'MONGOCRYPT_CTX_DONE'] | |
| ]); | |
| const INSECURE_TLS_OPTIONS = [ | |
| 'tlsInsecure', | |
| 'tlsAllowInvalidCertificates', | |
| 'tlsAllowInvalidHostnames', | |
| // These options are disallowed by the spec, so we explicitly filter them out if provided, even | |
| // though the StateMachine does not declare support for these options. | |
| 'tlsDisableOCSPEndpointCheck', | |
| 'tlsDisableCertificateRevocationCheck' | |
| ]; | |
| /** | |
| * Helper function for logging. Enabled by setting the environment flag MONGODB_CRYPT_DEBUG. | |
| * @param msg - Anything you want to be logged. | |
| */ | |
| function debug(msg) { | |
| if (process.env.MONGODB_CRYPT_DEBUG) { | |
| // eslint-disable-next-line no-console | |
| console.error(msg); | |
| } | |
| } | |
| /** | |
| * @internal | |
| * An internal class that executes across a MongoCryptContext until either | |
| * a finishing state or an error is reached. Do not instantiate directly. | |
| */ | |
| class StateMachine { | |
| constructor(options, bsonOptions = (0, bson_1.pluckBSONSerializeOptions)(options)) { | |
| this.options = options; | |
| this.bsonOptions = bsonOptions; | |
| } | |
| /** | |
| * Executes the state machine according to the specification | |
| */ | |
| async execute(executor, context) { | |
| const keyVaultNamespace = executor._keyVaultNamespace; | |
| const keyVaultClient = executor._keyVaultClient; | |
| const metaDataClient = executor._metaDataClient; | |
| const mongocryptdClient = executor._mongocryptdClient; | |
| const mongocryptdManager = executor._mongocryptdManager; | |
| let result = null; | |
| while (context.state !== MONGOCRYPT_CTX_DONE && context.state !== MONGOCRYPT_CTX_ERROR) { | |
| debug(`[context#${context.id}] ${stateToString.get(context.state) || context.state}`); | |
| switch (context.state) { | |
| case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO: { | |
| const filter = (0, bson_1.deserialize)(context.nextMongoOperation()); | |
| if (!metaDataClient) { | |
| throw new errors_1.MongoCryptError('unreachable state machine state: entered MONGOCRYPT_CTX_NEED_MONGO_COLLINFO but metadata client is undefined'); | |
| } | |
| const collInfo = await this.fetchCollectionInfo(metaDataClient, context.ns, filter); | |
| if (collInfo) { | |
| context.addMongoOperationResponse(collInfo); | |
| } | |
| context.finishMongoOperation(); | |
| break; | |
| } | |
| case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS: { | |
| const command = context.nextMongoOperation(); | |
| if (!mongocryptdClient) { | |
| throw new errors_1.MongoCryptError('unreachable state machine state: entered MONGOCRYPT_CTX_NEED_MONGO_MARKINGS but mongocryptdClient is undefined'); | |
| } | |
| // When we are using the shared library, we don't have a mongocryptd manager. | |
| const markedCommand = mongocryptdManager | |
| ? await mongocryptdManager.withRespawn(this.markCommand.bind(this, mongocryptdClient, context.ns, command)) | |
| : await this.markCommand(mongocryptdClient, context.ns, command); | |
| context.addMongoOperationResponse(markedCommand); | |
| context.finishMongoOperation(); | |
| break; | |
| } | |
| case MONGOCRYPT_CTX_NEED_MONGO_KEYS: { | |
| const filter = context.nextMongoOperation(); | |
| const keys = await this.fetchKeys(keyVaultClient, keyVaultNamespace, filter); | |
| if (keys.length === 0) { | |
| // This is kind of a hack. For `rewrapManyDataKey`, we have tests that | |
| // guarantee that when there are no matching keys, `rewrapManyDataKey` returns | |
| // nothing. We also have tests for auto encryption that guarantee for `encrypt` | |
| // we return an error when there are no matching keys. This error is generated in | |
| // subsequent iterations of the state machine. | |
| // Some apis (`encrypt`) throw if there are no filter matches and others (`rewrapManyDataKey`) | |
| // do not. We set the result manually here, and let the state machine continue. `libmongocrypt` | |
| // will inform us if we need to error by setting the state to `MONGOCRYPT_CTX_ERROR` but | |
| // otherwise we'll return `{ v: [] }`. | |
| result = { v: [] }; | |
| } | |
| for await (const key of keys) { | |
| context.addMongoOperationResponse((0, bson_1.serialize)(key)); | |
| } | |
| context.finishMongoOperation(); | |
| break; | |
| } | |
| case MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS: { | |
| const kmsProviders = await executor.askForKMSCredentials(); | |
| context.provideKMSProviders((0, bson_1.serialize)(kmsProviders)); | |
| break; | |
| } | |
| case MONGOCRYPT_CTX_NEED_KMS: { | |
| const requests = Array.from(this.requests(context)); | |
| await Promise.all(requests); | |
| context.finishKMSRequests(); | |
| break; | |
| } | |
| case MONGOCRYPT_CTX_READY: { | |
| const finalizedContext = context.finalize(); | |
| // @ts-expect-error finalize can change the state, check for error | |
| if (context.state === MONGOCRYPT_CTX_ERROR) { | |
| const message = context.status.message || 'Finalization error'; | |
| throw new errors_1.MongoCryptError(message); | |
| } | |
| result = (0, bson_1.deserialize)(finalizedContext, this.options); | |
| break; | |
| } | |
| default: | |
| throw new errors_1.MongoCryptError(`Unknown state: ${context.state}`); | |
| } | |
| } | |
| if (context.state === MONGOCRYPT_CTX_ERROR || result == null) { | |
| const message = context.status.message; | |
| if (!message) { | |
| debug(`unidentifiable error in MongoCrypt - received an error status from \`libmongocrypt\` but received no error message.`); | |
| } | |
| throw new errors_1.MongoCryptError(message ?? | |
| 'unidentifiable error in MongoCrypt - received an error status from `libmongocrypt` but received no error message.'); | |
| } | |
| return result; | |
| } | |
| /** | |
| * Handles the request to the KMS service. Exposed for testing purposes. Do not directly invoke. | |
| * @param kmsContext - A C++ KMS context returned from the bindings | |
| * @returns A promise that resolves when the KMS reply has be fully parsed | |
| */ | |
| async kmsRequest(request) { | |
| const parsedUrl = request.endpoint.split(':'); | |
| const port = parsedUrl[1] != null ? Number.parseInt(parsedUrl[1], 10) : HTTPS_PORT; | |
| const options = { | |
| host: parsedUrl[0], | |
| servername: parsedUrl[0], | |
| port | |
| }; | |
| const message = request.message; | |
| const buffer = new utils_1.BufferPool(); | |
| const netSocket = new net.Socket(); | |
| let socket; | |
| function destroySockets() { | |
| for (const sock of [socket, netSocket]) { | |
| if (sock) { | |
| sock.removeAllListeners(); | |
| sock.destroy(); | |
| } | |
| } | |
| } | |
| function ontimeout() { | |
| return new errors_1.MongoCryptError('KMS request timed out'); | |
| } | |
| function onerror(cause) { | |
| return new errors_1.MongoCryptError('KMS request failed', { cause }); | |
| } | |
| function onclose() { | |
| return new errors_1.MongoCryptError('KMS request closed'); | |
| } | |
| const tlsOptions = this.options.tlsOptions; | |
| if (tlsOptions) { | |
| const kmsProvider = request.kmsProvider; | |
| const providerTlsOptions = tlsOptions[kmsProvider]; | |
| if (providerTlsOptions) { | |
| const error = this.validateTlsOptions(kmsProvider, providerTlsOptions); | |
| if (error) { | |
| throw error; | |
| } | |
| try { | |
| await this.setTlsOptions(providerTlsOptions, options); | |
| } | |
| catch (err) { | |
| throw onerror(err); | |
| } | |
| } | |
| } | |
| const { promise: willConnect, reject: rejectOnNetSocketError, resolve: resolveOnNetSocketConnect } = (0, utils_1.promiseWithResolvers)(); | |
| netSocket | |
| .once('timeout', () => rejectOnNetSocketError(ontimeout())) | |
| .once('error', err => rejectOnNetSocketError(onerror(err))) | |
| .once('close', () => rejectOnNetSocketError(onclose())) | |
| .once('connect', () => resolveOnNetSocketConnect()); | |
| try { | |
| if (this.options.proxyOptions && this.options.proxyOptions.proxyHost) { | |
| netSocket.connect({ | |
| host: this.options.proxyOptions.proxyHost, | |
| port: this.options.proxyOptions.proxyPort || 1080 | |
| }); | |
| await willConnect; | |
| try { | |
| socks ??= loadSocks(); | |
| options.socket = (await socks.SocksClient.createConnection({ | |
| existing_socket: netSocket, | |
| command: 'connect', | |
| destination: { host: options.host, port: options.port }, | |
| proxy: { | |
| // host and port are ignored because we pass existing_socket | |
| host: 'iLoveJavaScript', | |
| port: 0, | |
| type: 5, | |
| userId: this.options.proxyOptions.proxyUsername, | |
| password: this.options.proxyOptions.proxyPassword | |
| } | |
| })).socket; | |
| } | |
| catch (err) { | |
| throw onerror(err); | |
| } | |
| } | |
| socket = tls.connect(options, () => { | |
| socket.write(message); | |
| }); | |
| const { promise: willResolveKmsRequest, reject: rejectOnTlsSocketError, resolve } = (0, utils_1.promiseWithResolvers)(); | |
| socket | |
| .once('timeout', () => rejectOnTlsSocketError(ontimeout())) | |
| .once('error', err => rejectOnTlsSocketError(onerror(err))) | |
| .once('close', () => rejectOnTlsSocketError(onclose())) | |
| .on('data', data => { | |
| buffer.append(data); | |
| while (request.bytesNeeded > 0 && buffer.length) { | |
| const bytesNeeded = Math.min(request.bytesNeeded, buffer.length); | |
| request.addResponse(buffer.read(bytesNeeded)); | |
| } | |
| if (request.bytesNeeded <= 0) { | |
| resolve(); | |
| } | |
| }); | |
| await willResolveKmsRequest; | |
| } | |
| finally { | |
| // There's no need for any more activity on this socket at this point. | |
| destroySockets(); | |
| } | |
| } | |
| *requests(context) { | |
| for (let request = context.nextKMSRequest(); request != null; request = context.nextKMSRequest()) { | |
| yield this.kmsRequest(request); | |
| } | |
| } | |
| /** | |
| * Validates the provided TLS options are secure. | |
| * | |
| * @param kmsProvider - The KMS provider name. | |
| * @param tlsOptions - The client TLS options for the provider. | |
| * | |
| * @returns An error if any option is invalid. | |
| */ | |
| validateTlsOptions(kmsProvider, tlsOptions) { | |
| const tlsOptionNames = Object.keys(tlsOptions); | |
| for (const option of INSECURE_TLS_OPTIONS) { | |
| if (tlsOptionNames.includes(option)) { | |
| return new errors_1.MongoCryptError(`Insecure TLS options prohibited for ${kmsProvider}: ${option}`); | |
| } | |
| } | |
| } | |
| /** | |
| * Sets only the valid secure TLS options. | |
| * | |
| * @param tlsOptions - The client TLS options for the provider. | |
| * @param options - The existing connection options. | |
| */ | |
| async setTlsOptions(tlsOptions, options) { | |
| if (tlsOptions.tlsCertificateKeyFile) { | |
| const cert = await fs.readFile(tlsOptions.tlsCertificateKeyFile); | |
| options.cert = options.key = cert; | |
| } | |
| if (tlsOptions.tlsCAFile) { | |
| options.ca = await fs.readFile(tlsOptions.tlsCAFile); | |
| } | |
| if (tlsOptions.tlsCertificateKeyFilePassword) { | |
| options.passphrase = tlsOptions.tlsCertificateKeyFilePassword; | |
| } | |
| } | |
| /** | |
| * Fetches collection info for a provided namespace, when libmongocrypt | |
| * enters the `MONGOCRYPT_CTX_NEED_MONGO_COLLINFO` state. The result is | |
| * used to inform libmongocrypt of the schema associated with this | |
| * namespace. Exposed for testing purposes. Do not directly invoke. | |
| * | |
| * @param client - A MongoClient connected to the topology | |
| * @param ns - The namespace to list collections from | |
| * @param filter - A filter for the listCollections command | |
| * @param callback - Invoked with the info of the requested collection, or with an error | |
| */ | |
| async fetchCollectionInfo(client, ns, filter) { | |
| const { db } = utils_1.MongoDBCollectionNamespace.fromString(ns); | |
| const collections = await client | |
| .db(db) | |
| .listCollections(filter, { | |
| promoteLongs: false, | |
| promoteValues: false | |
| }) | |
| .toArray(); | |
| const info = collections.length > 0 ? (0, bson_1.serialize)(collections[0]) : null; | |
| return info; | |
| } | |
| /** | |
| * Calls to the mongocryptd to provide markings for a command. | |
| * Exposed for testing purposes. Do not directly invoke. | |
| * @param client - A MongoClient connected to a mongocryptd | |
| * @param ns - The namespace (database.collection) the command is being executed on | |
| * @param command - The command to execute. | |
| * @param callback - Invoked with the serialized and marked bson command, or with an error | |
| */ | |
| async markCommand(client, ns, command) { | |
| const options = { promoteLongs: false, promoteValues: false }; | |
| const { db } = utils_1.MongoDBCollectionNamespace.fromString(ns); | |
| const rawCommand = (0, bson_1.deserialize)(command, options); | |
| const response = await client.db(db).command(rawCommand, options); | |
| return (0, bson_1.serialize)(response, this.bsonOptions); | |
| } | |
| /** | |
| * Requests keys from the keyVault collection on the topology. | |
| * Exposed for testing purposes. Do not directly invoke. | |
| * @param client - A MongoClient connected to the topology | |
| * @param keyVaultNamespace - The namespace (database.collection) of the keyVault Collection | |
| * @param filter - The filter for the find query against the keyVault Collection | |
| * @param callback - Invoked with the found keys, or with an error | |
| */ | |
| fetchKeys(client, keyVaultNamespace, filter) { | |
| const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(keyVaultNamespace); | |
| return client | |
| .db(dbName) | |
| .collection(collectionName, { readConcern: { level: 'majority' } }) | |
| .find((0, bson_1.deserialize)(filter)) | |
| .toArray(); | |
| } | |
| } | |
| exports.StateMachine = StateMachine; | |
| //# sourceMappingURL=state_machine.js.map |