import { createSecureContext } from 'tls'; import type { ICredentialDataDecryptedObject, IDataObject, IExecuteFunctions, INodeExecutionData, } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; import get from 'lodash/get'; import set from 'lodash/set'; import { MongoClient, ObjectId } from 'mongodb'; import { formatPrivateKey } from '../../utils/utilities'; import type { IMongoCredentials, IMongoCredentialsType, IMongoParametricCredentials, } from './mongoDb.types'; /** * Standard way of building the MongoDB connection string, unless overridden with a provided string * * @param {ICredentialDataDecryptedObject} credentials MongoDB credentials to use, unless conn string is overridden */ export function buildParameterizedConnString(credentials: IMongoParametricCredentials): string { if (credentials.port) { return `mongodb://${credentials.user}:${credentials.password}@${credentials.host}:${credentials.port}`; } else { return `mongodb+srv://${credentials.user}:${credentials.password}@${credentials.host}`; } } /** * Build mongoDb connection string and resolve database name. * If a connection string override value is provided, that will be used in place of individual args * * @param {ICredentialDataDecryptedObject} credentials raw/input MongoDB credentials to use */ export function buildMongoConnectionParams( self: IExecuteFunctions, credentials: IMongoCredentialsType, ): IMongoCredentials { const sanitizedDbName = credentials.database && credentials.database.trim().length > 0 ? credentials.database.trim() : ''; if (credentials.configurationType === 'connectionString') { if (credentials.connectionString && credentials.connectionString.trim().length > 0) { return { connectionString: credentials.connectionString.trim(), database: sanitizedDbName, }; } else { throw new NodeOperationError( self.getNode(), 'Cannot override credentials: valid MongoDB connection string not provided ', ); } } else { return { connectionString: buildParameterizedConnString(credentials), database: sanitizedDbName, }; } } /** * Verify credentials. If ok, build mongoDb connection string and resolve database name. * * @param {ICredentialDataDecryptedObject} credentials raw/input MongoDB credentials to use */ export function validateAndResolveMongoCredentials( self: IExecuteFunctions, credentials?: ICredentialDataDecryptedObject, ): IMongoCredentials { if (credentials === undefined) { throw new NodeOperationError(self.getNode(), 'No credentials got returned!'); } else { return buildMongoConnectionParams(self, credentials as unknown as IMongoCredentialsType); } } export function prepareItems( items: INodeExecutionData[], fields: string[], updateKey = '', useDotNotation = false, dateFields: string[] = [], ) { let data = items; if (updateKey) { if (!fields.includes(updateKey)) { fields.push(updateKey); } data = items.filter((item) => item.json[updateKey] !== undefined); } const preperedItems = data.map(({ json }) => { const updateItem: IDataObject = {}; for (const field of fields) { let fieldData; if (useDotNotation) { fieldData = get(json, field, null); } else { fieldData = json[field] !== undefined ? json[field] : null; } if (fieldData && dateFields.includes(field)) { fieldData = new Date(fieldData as string); } if (useDotNotation) { set(updateItem, field, fieldData); } else { updateItem[field] = fieldData; } } return updateItem; }); return preperedItems; } export function prepareFields(fields: string) { return fields .split(',') .map((field) => field.trim()) .filter((field) => !!field); } export function stringifyObjectIDs(items: IDataObject[]) { items.forEach((item) => { if (item._id instanceof ObjectId) { item._id = item._id.toString(); } if (item.id instanceof ObjectId) { item.id = item.id.toString(); } }); } export async function connectMongoClient(connectionString: string, credentials: IDataObject = {}) { let client: MongoClient; if (credentials.tls) { const ca = credentials.ca ? formatPrivateKey(credentials.ca as string) : undefined; const cert = credentials.cert ? formatPrivateKey(credentials.cert as string) : undefined; const key = credentials.key ? formatPrivateKey(credentials.key as string) : undefined; const passphrase = (credentials.passphrase as string) || undefined; const secureContext = createSecureContext({ ca, cert, key, passphrase, }); client = await MongoClient.connect(connectionString, { tls: true, secureContext, }); } else { client = await MongoClient.connect(connectionString); } return client; }