connection_string.js 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.FEATURE_FLAGS = exports.DEFAULT_OPTIONS = exports.OPTIONS = exports.parseOptions = exports.checkTLSOptions = exports.resolveSRVRecord = void 0;
  4. const dns = require("dns");
  5. const fs = require("fs");
  6. const mongodb_connection_string_url_1 = require("mongodb-connection-string-url");
  7. const url_1 = require("url");
  8. const mongo_credentials_1 = require("./cmap/auth/mongo_credentials");
  9. const providers_1 = require("./cmap/auth/providers");
  10. const compression_1 = require("./cmap/wire_protocol/compression");
  11. const encrypter_1 = require("./encrypter");
  12. const error_1 = require("./error");
  13. const logger_1 = require("./logger");
  14. const mongo_client_1 = require("./mongo_client");
  15. const promise_provider_1 = require("./promise_provider");
  16. const read_concern_1 = require("./read_concern");
  17. const read_preference_1 = require("./read_preference");
  18. const utils_1 = require("./utils");
  19. const write_concern_1 = require("./write_concern");
  20. const VALID_TXT_RECORDS = ['authSource', 'replicaSet', 'loadBalanced'];
  21. const LB_SINGLE_HOST_ERROR = 'loadBalanced option only supported with a single host in the URI';
  22. const LB_REPLICA_SET_ERROR = 'loadBalanced option not supported with a replicaSet option';
  23. const LB_DIRECT_CONNECTION_ERROR = 'loadBalanced option not supported when directConnection is provided';
  24. /**
  25. * Determines whether a provided address matches the provided parent domain in order
  26. * to avoid certain attack vectors.
  27. *
  28. * @param srvAddress - The address to check against a domain
  29. * @param parentDomain - The domain to check the provided address against
  30. * @returns Whether the provided address matches the parent domain
  31. */
  32. function matchesParentDomain(srvAddress, parentDomain) {
  33. const regex = /^.*?\./;
  34. const srv = `.${srvAddress.replace(regex, '')}`;
  35. const parent = `.${parentDomain.replace(regex, '')}`;
  36. return srv.endsWith(parent);
  37. }
  38. /**
  39. * Lookup a `mongodb+srv` connection string, combine the parts and reparse it as a normal
  40. * connection string.
  41. *
  42. * @param uri - The connection string to parse
  43. * @param options - Optional user provided connection string options
  44. */
  45. function resolveSRVRecord(options, callback) {
  46. if (typeof options.srvHost !== 'string') {
  47. return callback(new error_1.MongoAPIError('Option "srvHost" must not be empty'));
  48. }
  49. if (options.srvHost.split('.').length < 3) {
  50. // TODO(NODE-3484): Replace with MongoConnectionStringError
  51. return callback(new error_1.MongoAPIError('URI must include hostname, domain name, and tld'));
  52. }
  53. // Resolve the SRV record and use the result as the list of hosts to connect to.
  54. const lookupAddress = options.srvHost;
  55. dns.resolveSrv(`_${options.srvServiceName}._tcp.${lookupAddress}`, (err, addresses) => {
  56. if (err)
  57. return callback(err);
  58. if (addresses.length === 0) {
  59. return callback(new error_1.MongoAPIError('No addresses found at host'));
  60. }
  61. for (const { name } of addresses) {
  62. if (!matchesParentDomain(name, lookupAddress)) {
  63. return callback(new error_1.MongoAPIError('Server record does not share hostname with parent URI'));
  64. }
  65. }
  66. const hostAddresses = addresses.map(r => { var _a; return utils_1.HostAddress.fromString(`${r.name}:${(_a = r.port) !== null && _a !== void 0 ? _a : 27017}`); });
  67. const lbError = validateLoadBalancedOptions(hostAddresses, options, true);
  68. if (lbError) {
  69. return callback(lbError);
  70. }
  71. // Resolve TXT record and add options from there if they exist.
  72. dns.resolveTxt(lookupAddress, (err, record) => {
  73. var _a, _b, _c;
  74. if (err) {
  75. if (err.code !== 'ENODATA' && err.code !== 'ENOTFOUND') {
  76. return callback(err);
  77. }
  78. }
  79. else {
  80. if (record.length > 1) {
  81. return callback(new error_1.MongoParseError('Multiple text records not allowed'));
  82. }
  83. const txtRecordOptions = new url_1.URLSearchParams(record[0].join(''));
  84. const txtRecordOptionKeys = [...txtRecordOptions.keys()];
  85. if (txtRecordOptionKeys.some(key => !VALID_TXT_RECORDS.includes(key))) {
  86. return callback(new error_1.MongoParseError(`Text record may only set any of: ${VALID_TXT_RECORDS.join(', ')}`));
  87. }
  88. if (VALID_TXT_RECORDS.some(option => txtRecordOptions.get(option) === '')) {
  89. return callback(new error_1.MongoParseError('Cannot have empty URI params in DNS TXT Record'));
  90. }
  91. const source = (_a = txtRecordOptions.get('authSource')) !== null && _a !== void 0 ? _a : undefined;
  92. const replicaSet = (_b = txtRecordOptions.get('replicaSet')) !== null && _b !== void 0 ? _b : undefined;
  93. const loadBalanced = (_c = txtRecordOptions.get('loadBalanced')) !== null && _c !== void 0 ? _c : undefined;
  94. if (!options.userSpecifiedAuthSource &&
  95. source &&
  96. options.credentials &&
  97. !providers_1.AUTH_MECHS_AUTH_SRC_EXTERNAL.has(options.credentials.mechanism)) {
  98. options.credentials = mongo_credentials_1.MongoCredentials.merge(options.credentials, { source });
  99. }
  100. if (!options.userSpecifiedReplicaSet && replicaSet) {
  101. options.replicaSet = replicaSet;
  102. }
  103. if (loadBalanced === 'true') {
  104. options.loadBalanced = true;
  105. }
  106. if (options.replicaSet && options.srvMaxHosts > 0) {
  107. return callback(new error_1.MongoParseError('Cannot combine replicaSet option with srvMaxHosts'));
  108. }
  109. const lbError = validateLoadBalancedOptions(hostAddresses, options, true);
  110. if (lbError) {
  111. return callback(lbError);
  112. }
  113. }
  114. callback(undefined, hostAddresses);
  115. });
  116. });
  117. }
  118. exports.resolveSRVRecord = resolveSRVRecord;
  119. /**
  120. * Checks if TLS options are valid
  121. *
  122. * @param options - The options used for options parsing
  123. * @throws MongoParseError if TLS options are invalid
  124. */
  125. function checkTLSOptions(options) {
  126. if (!options)
  127. return;
  128. const check = (a, b) => {
  129. if (Reflect.has(options, a) && Reflect.has(options, b)) {
  130. throw new error_1.MongoParseError(`The '${a}' option cannot be used with '${b}'`);
  131. }
  132. };
  133. check('tlsInsecure', 'tlsAllowInvalidCertificates');
  134. check('tlsInsecure', 'tlsAllowInvalidHostnames');
  135. check('tlsInsecure', 'tlsDisableCertificateRevocationCheck');
  136. check('tlsInsecure', 'tlsDisableOCSPEndpointCheck');
  137. check('tlsAllowInvalidCertificates', 'tlsDisableCertificateRevocationCheck');
  138. check('tlsAllowInvalidCertificates', 'tlsDisableOCSPEndpointCheck');
  139. check('tlsDisableCertificateRevocationCheck', 'tlsDisableOCSPEndpointCheck');
  140. }
  141. exports.checkTLSOptions = checkTLSOptions;
  142. const TRUTHS = new Set(['true', 't', '1', 'y', 'yes']);
  143. const FALSEHOODS = new Set(['false', 'f', '0', 'n', 'no', '-1']);
  144. function getBoolean(name, value) {
  145. if (typeof value === 'boolean')
  146. return value;
  147. const valueString = String(value).toLowerCase();
  148. if (TRUTHS.has(valueString)) {
  149. if (valueString !== 'true') {
  150. (0, utils_1.emitWarningOnce)(`deprecated value for ${name} : ${valueString} - please update to ${name} : true instead`);
  151. }
  152. return true;
  153. }
  154. if (FALSEHOODS.has(valueString)) {
  155. if (valueString !== 'false') {
  156. (0, utils_1.emitWarningOnce)(`deprecated value for ${name} : ${valueString} - please update to ${name} : false instead`);
  157. }
  158. return false;
  159. }
  160. throw new error_1.MongoParseError(`Expected ${name} to be stringified boolean value, got: ${value}`);
  161. }
  162. function getInt(name, value) {
  163. if (typeof value === 'number')
  164. return Math.trunc(value);
  165. const parsedValue = Number.parseInt(String(value), 10);
  166. if (!Number.isNaN(parsedValue))
  167. return parsedValue;
  168. throw new error_1.MongoParseError(`Expected ${name} to be stringified int value, got: ${value}`);
  169. }
  170. function getUint(name, value) {
  171. const parsedValue = getInt(name, value);
  172. if (parsedValue < 0) {
  173. throw new error_1.MongoParseError(`${name} can only be a positive int value, got: ${value}`);
  174. }
  175. return parsedValue;
  176. }
  177. function* entriesFromString(value) {
  178. const keyValuePairs = value.split(',');
  179. for (const keyValue of keyValuePairs) {
  180. const [key, value] = keyValue.split(':');
  181. if (value == null) {
  182. throw new error_1.MongoParseError('Cannot have undefined values in key value pairs');
  183. }
  184. yield [key, value];
  185. }
  186. }
  187. class CaseInsensitiveMap extends Map {
  188. constructor(entries = []) {
  189. super(entries.map(([k, v]) => [k.toLowerCase(), v]));
  190. }
  191. has(k) {
  192. return super.has(k.toLowerCase());
  193. }
  194. get(k) {
  195. return super.get(k.toLowerCase());
  196. }
  197. set(k, v) {
  198. return super.set(k.toLowerCase(), v);
  199. }
  200. delete(k) {
  201. return super.delete(k.toLowerCase());
  202. }
  203. }
  204. function parseOptions(uri, mongoClient = undefined, options = {}) {
  205. if (mongoClient != null && !(mongoClient instanceof mongo_client_1.MongoClient)) {
  206. options = mongoClient;
  207. mongoClient = undefined;
  208. }
  209. const url = new mongodb_connection_string_url_1.default(uri);
  210. const { hosts, isSRV } = url;
  211. const mongoOptions = Object.create(null);
  212. // Feature flags
  213. for (const flag of Object.getOwnPropertySymbols(options)) {
  214. if (exports.FEATURE_FLAGS.has(flag)) {
  215. mongoOptions[flag] = options[flag];
  216. }
  217. }
  218. mongoOptions.hosts = isSRV ? [] : hosts.map(utils_1.HostAddress.fromString);
  219. const urlOptions = new CaseInsensitiveMap();
  220. if (url.pathname !== '/' && url.pathname !== '') {
  221. const dbName = decodeURIComponent(url.pathname[0] === '/' ? url.pathname.slice(1) : url.pathname);
  222. if (dbName) {
  223. urlOptions.set('dbName', [dbName]);
  224. }
  225. }
  226. if (url.username !== '') {
  227. const auth = {
  228. username: decodeURIComponent(url.username)
  229. };
  230. if (typeof url.password === 'string') {
  231. auth.password = decodeURIComponent(url.password);
  232. }
  233. urlOptions.set('auth', [auth]);
  234. }
  235. for (const key of url.searchParams.keys()) {
  236. const values = [...url.searchParams.getAll(key)];
  237. if (values.includes('')) {
  238. throw new error_1.MongoAPIError('URI cannot contain options with no value');
  239. }
  240. if (!urlOptions.has(key)) {
  241. urlOptions.set(key, values);
  242. }
  243. }
  244. const objectOptions = new CaseInsensitiveMap(Object.entries(options).filter(([, v]) => v != null));
  245. // Validate options that can only be provided by one of uri or object
  246. if (urlOptions.has('serverApi')) {
  247. throw new error_1.MongoParseError('URI cannot contain `serverApi`, it can only be passed to the client');
  248. }
  249. if (objectOptions.has('loadBalanced')) {
  250. throw new error_1.MongoParseError('loadBalanced is only a valid option in the URI');
  251. }
  252. // All option collection
  253. const allOptions = new CaseInsensitiveMap();
  254. const allKeys = new Set([
  255. ...urlOptions.keys(),
  256. ...objectOptions.keys(),
  257. ...exports.DEFAULT_OPTIONS.keys()
  258. ]);
  259. for (const key of allKeys) {
  260. const values = [];
  261. const objectOptionValue = objectOptions.get(key);
  262. if (objectOptionValue != null) {
  263. values.push(objectOptionValue);
  264. }
  265. const urlValue = urlOptions.get(key);
  266. if (urlValue != null) {
  267. values.push(...urlValue);
  268. }
  269. const defaultOptionsValue = exports.DEFAULT_OPTIONS.get(key);
  270. if (defaultOptionsValue != null) {
  271. values.push(defaultOptionsValue);
  272. }
  273. allOptions.set(key, values);
  274. }
  275. if (allOptions.has('tlsCertificateKeyFile') && !allOptions.has('tlsCertificateFile')) {
  276. allOptions.set('tlsCertificateFile', allOptions.get('tlsCertificateKeyFile'));
  277. }
  278. if (allOptions.has('tls') || allOptions.has('ssl')) {
  279. const tlsAndSslOpts = (allOptions.get('tls') || [])
  280. .concat(allOptions.get('ssl') || [])
  281. .map(getBoolean.bind(null, 'tls/ssl'));
  282. if (new Set(tlsAndSslOpts).size !== 1) {
  283. throw new error_1.MongoParseError('All values of tls/ssl must be the same.');
  284. }
  285. }
  286. const unsupportedOptions = (0, utils_1.setDifference)(allKeys, Array.from(Object.keys(exports.OPTIONS)).map(s => s.toLowerCase()));
  287. if (unsupportedOptions.size !== 0) {
  288. const optionWord = unsupportedOptions.size > 1 ? 'options' : 'option';
  289. const isOrAre = unsupportedOptions.size > 1 ? 'are' : 'is';
  290. throw new error_1.MongoParseError(`${optionWord} ${Array.from(unsupportedOptions).join(', ')} ${isOrAre} not supported`);
  291. }
  292. // Option parsing and setting
  293. for (const [key, descriptor] of Object.entries(exports.OPTIONS)) {
  294. const values = allOptions.get(key);
  295. if (!values || values.length === 0)
  296. continue;
  297. setOption(mongoOptions, key, descriptor, values);
  298. }
  299. if (mongoOptions.credentials) {
  300. const isGssapi = mongoOptions.credentials.mechanism === providers_1.AuthMechanism.MONGODB_GSSAPI;
  301. const isX509 = mongoOptions.credentials.mechanism === providers_1.AuthMechanism.MONGODB_X509;
  302. const isAws = mongoOptions.credentials.mechanism === providers_1.AuthMechanism.MONGODB_AWS;
  303. if ((isGssapi || isX509) &&
  304. allOptions.has('authSource') &&
  305. mongoOptions.credentials.source !== '$external') {
  306. // If authSource was explicitly given and its incorrect, we error
  307. throw new error_1.MongoParseError(`${mongoOptions.credentials} can only have authSource set to '$external'`);
  308. }
  309. if (!(isGssapi || isX509 || isAws) && mongoOptions.dbName && !allOptions.has('authSource')) {
  310. // inherit the dbName unless GSSAPI or X509, then silently ignore dbName
  311. // and there was no specific authSource given
  312. mongoOptions.credentials = mongo_credentials_1.MongoCredentials.merge(mongoOptions.credentials, {
  313. source: mongoOptions.dbName
  314. });
  315. }
  316. mongoOptions.credentials.validate();
  317. // Check if the only auth related option provided was authSource, if so we can remove credentials
  318. if (mongoOptions.credentials.password === '' &&
  319. mongoOptions.credentials.username === '' &&
  320. mongoOptions.credentials.mechanism === providers_1.AuthMechanism.MONGODB_DEFAULT &&
  321. Object.keys(mongoOptions.credentials.mechanismProperties).length === 0) {
  322. delete mongoOptions.credentials;
  323. }
  324. }
  325. if (!mongoOptions.dbName) {
  326. // dbName default is applied here because of the credential validation above
  327. mongoOptions.dbName = 'test';
  328. }
  329. checkTLSOptions(mongoOptions);
  330. if (options.promiseLibrary)
  331. promise_provider_1.PromiseProvider.set(options.promiseLibrary);
  332. const lbError = validateLoadBalancedOptions(hosts, mongoOptions, isSRV);
  333. if (lbError) {
  334. throw lbError;
  335. }
  336. if (mongoClient && mongoOptions.autoEncryption) {
  337. encrypter_1.Encrypter.checkForMongoCrypt();
  338. mongoOptions.encrypter = new encrypter_1.Encrypter(mongoClient, uri, options);
  339. mongoOptions.autoEncrypter = mongoOptions.encrypter.autoEncrypter;
  340. }
  341. // Potential SRV Overrides and SRV connection string validations
  342. mongoOptions.userSpecifiedAuthSource =
  343. objectOptions.has('authSource') || urlOptions.has('authSource');
  344. mongoOptions.userSpecifiedReplicaSet =
  345. objectOptions.has('replicaSet') || urlOptions.has('replicaSet');
  346. if (isSRV) {
  347. // SRV Record is resolved upon connecting
  348. mongoOptions.srvHost = hosts[0];
  349. if (mongoOptions.directConnection) {
  350. throw new error_1.MongoAPIError('SRV URI does not support directConnection');
  351. }
  352. if (mongoOptions.srvMaxHosts > 0 && typeof mongoOptions.replicaSet === 'string') {
  353. throw new error_1.MongoParseError('Cannot use srvMaxHosts option with replicaSet');
  354. }
  355. // SRV turns on TLS by default, but users can override and turn it off
  356. const noUserSpecifiedTLS = !objectOptions.has('tls') && !urlOptions.has('tls');
  357. const noUserSpecifiedSSL = !objectOptions.has('ssl') && !urlOptions.has('ssl');
  358. if (noUserSpecifiedTLS && noUserSpecifiedSSL) {
  359. mongoOptions.tls = true;
  360. }
  361. }
  362. else {
  363. const userSpecifiedSrvOptions = urlOptions.has('srvMaxHosts') ||
  364. objectOptions.has('srvMaxHosts') ||
  365. urlOptions.has('srvServiceName') ||
  366. objectOptions.has('srvServiceName');
  367. if (userSpecifiedSrvOptions) {
  368. throw new error_1.MongoParseError('Cannot use srvMaxHosts or srvServiceName with a non-srv connection string');
  369. }
  370. }
  371. if (mongoOptions.directConnection && mongoOptions.hosts.length !== 1) {
  372. throw new error_1.MongoParseError('directConnection option requires exactly one host');
  373. }
  374. if (!mongoOptions.proxyHost &&
  375. (mongoOptions.proxyPort || mongoOptions.proxyUsername || mongoOptions.proxyPassword)) {
  376. throw new error_1.MongoParseError('Must specify proxyHost if other proxy options are passed');
  377. }
  378. if ((mongoOptions.proxyUsername && !mongoOptions.proxyPassword) ||
  379. (!mongoOptions.proxyUsername && mongoOptions.proxyPassword)) {
  380. throw new error_1.MongoParseError('Can only specify both of proxy username/password or neither');
  381. }
  382. const proxyOptions = ['proxyHost', 'proxyPort', 'proxyUsername', 'proxyPassword'].map(key => { var _a; return (_a = urlOptions.get(key)) !== null && _a !== void 0 ? _a : []; });
  383. if (proxyOptions.some(options => options.length > 1)) {
  384. throw new error_1.MongoParseError('Proxy options cannot be specified multiple times in the connection string');
  385. }
  386. return mongoOptions;
  387. }
  388. exports.parseOptions = parseOptions;
  389. function validateLoadBalancedOptions(hosts, mongoOptions, isSrv) {
  390. if (mongoOptions.loadBalanced) {
  391. if (hosts.length > 1) {
  392. return new error_1.MongoParseError(LB_SINGLE_HOST_ERROR);
  393. }
  394. if (mongoOptions.replicaSet) {
  395. return new error_1.MongoParseError(LB_REPLICA_SET_ERROR);
  396. }
  397. if (mongoOptions.directConnection) {
  398. return new error_1.MongoParseError(LB_DIRECT_CONNECTION_ERROR);
  399. }
  400. if (isSrv && mongoOptions.srvMaxHosts > 0) {
  401. return new error_1.MongoParseError('Cannot limit srv hosts with loadBalanced enabled');
  402. }
  403. }
  404. return;
  405. }
  406. function setOption(mongoOptions, key, descriptor, values) {
  407. const { target, type, transform, deprecated } = descriptor;
  408. const name = target !== null && target !== void 0 ? target : key;
  409. if (deprecated) {
  410. const deprecatedMsg = typeof deprecated === 'string' ? `: ${deprecated}` : '';
  411. (0, utils_1.emitWarning)(`${key} is a deprecated option${deprecatedMsg}`);
  412. }
  413. switch (type) {
  414. case 'boolean':
  415. mongoOptions[name] = getBoolean(name, values[0]);
  416. break;
  417. case 'int':
  418. mongoOptions[name] = getInt(name, values[0]);
  419. break;
  420. case 'uint':
  421. mongoOptions[name] = getUint(name, values[0]);
  422. break;
  423. case 'string':
  424. if (values[0] == null) {
  425. break;
  426. }
  427. mongoOptions[name] = String(values[0]);
  428. break;
  429. case 'record':
  430. if (!(0, utils_1.isRecord)(values[0])) {
  431. throw new error_1.MongoParseError(`${name} must be an object`);
  432. }
  433. mongoOptions[name] = values[0];
  434. break;
  435. case 'any':
  436. mongoOptions[name] = values[0];
  437. break;
  438. default: {
  439. if (!transform) {
  440. throw new error_1.MongoParseError('Descriptors missing a type must define a transform');
  441. }
  442. const transformValue = transform({ name, options: mongoOptions, values });
  443. mongoOptions[name] = transformValue;
  444. break;
  445. }
  446. }
  447. }
  448. exports.OPTIONS = {
  449. appName: {
  450. target: 'metadata',
  451. transform({ options, values: [value] }) {
  452. return (0, utils_1.makeClientMetadata)({ ...options.driverInfo, appName: String(value) });
  453. }
  454. },
  455. auth: {
  456. target: 'credentials',
  457. transform({ name, options, values: [value] }) {
  458. if (!(0, utils_1.isRecord)(value, ['username', 'password'])) {
  459. throw new error_1.MongoParseError(`${name} must be an object with 'username' and 'password' properties`);
  460. }
  461. return mongo_credentials_1.MongoCredentials.merge(options.credentials, {
  462. username: value.username,
  463. password: value.password
  464. });
  465. }
  466. },
  467. authMechanism: {
  468. target: 'credentials',
  469. transform({ options, values: [value] }) {
  470. var _a, _b;
  471. const mechanisms = Object.values(providers_1.AuthMechanism);
  472. const [mechanism] = mechanisms.filter(m => m.match(RegExp(String.raw `\b${value}\b`, 'i')));
  473. if (!mechanism) {
  474. throw new error_1.MongoParseError(`authMechanism one of ${mechanisms}, got ${value}`);
  475. }
  476. let source = (_a = options.credentials) === null || _a === void 0 ? void 0 : _a.source;
  477. if (mechanism === providers_1.AuthMechanism.MONGODB_PLAIN ||
  478. providers_1.AUTH_MECHS_AUTH_SRC_EXTERNAL.has(mechanism)) {
  479. // some mechanisms have '$external' as the Auth Source
  480. source = '$external';
  481. }
  482. let password = (_b = options.credentials) === null || _b === void 0 ? void 0 : _b.password;
  483. if (mechanism === providers_1.AuthMechanism.MONGODB_X509 && password === '') {
  484. password = undefined;
  485. }
  486. return mongo_credentials_1.MongoCredentials.merge(options.credentials, {
  487. mechanism,
  488. source,
  489. password
  490. });
  491. }
  492. },
  493. authMechanismProperties: {
  494. target: 'credentials',
  495. transform({ options, values: [optionValue] }) {
  496. if (typeof optionValue === 'string') {
  497. const mechanismProperties = Object.create(null);
  498. for (const [key, value] of entriesFromString(optionValue)) {
  499. try {
  500. mechanismProperties[key] = getBoolean(key, value);
  501. }
  502. catch {
  503. mechanismProperties[key] = value;
  504. }
  505. }
  506. return mongo_credentials_1.MongoCredentials.merge(options.credentials, {
  507. mechanismProperties
  508. });
  509. }
  510. if (!(0, utils_1.isRecord)(optionValue)) {
  511. throw new error_1.MongoParseError('AuthMechanismProperties must be an object');
  512. }
  513. return mongo_credentials_1.MongoCredentials.merge(options.credentials, { mechanismProperties: optionValue });
  514. }
  515. },
  516. authSource: {
  517. target: 'credentials',
  518. transform({ options, values: [value] }) {
  519. const source = String(value);
  520. return mongo_credentials_1.MongoCredentials.merge(options.credentials, { source });
  521. }
  522. },
  523. autoEncryption: {
  524. type: 'record'
  525. },
  526. bsonRegExp: {
  527. type: 'boolean'
  528. },
  529. serverApi: {
  530. target: 'serverApi',
  531. transform({ values: [version] }) {
  532. const serverApiToValidate = typeof version === 'string' ? { version } : version;
  533. const versionToValidate = serverApiToValidate && serverApiToValidate.version;
  534. if (!versionToValidate) {
  535. throw new error_1.MongoParseError(`Invalid \`serverApi\` property; must specify a version from the following enum: ["${Object.values(mongo_client_1.ServerApiVersion).join('", "')}"]`);
  536. }
  537. if (!Object.values(mongo_client_1.ServerApiVersion).some(v => v === versionToValidate)) {
  538. throw new error_1.MongoParseError(`Invalid server API version=${versionToValidate}; must be in the following enum: ["${Object.values(mongo_client_1.ServerApiVersion).join('", "')}"]`);
  539. }
  540. return serverApiToValidate;
  541. }
  542. },
  543. checkKeys: {
  544. type: 'boolean'
  545. },
  546. compressors: {
  547. default: 'none',
  548. target: 'compressors',
  549. transform({ values }) {
  550. const compressionList = new Set();
  551. for (const compVal of values) {
  552. const compValArray = typeof compVal === 'string' ? compVal.split(',') : compVal;
  553. if (!Array.isArray(compValArray)) {
  554. throw new error_1.MongoInvalidArgumentError('compressors must be an array or a comma-delimited list of strings');
  555. }
  556. for (const c of compValArray) {
  557. if (Object.keys(compression_1.Compressor).includes(String(c))) {
  558. compressionList.add(String(c));
  559. }
  560. else {
  561. throw new error_1.MongoInvalidArgumentError(`${c} is not a valid compression mechanism. Must be one of: ${Object.keys(compression_1.Compressor)}.`);
  562. }
  563. }
  564. }
  565. return [...compressionList];
  566. }
  567. },
  568. connectTimeoutMS: {
  569. default: 30000,
  570. type: 'uint'
  571. },
  572. dbName: {
  573. type: 'string'
  574. },
  575. directConnection: {
  576. default: false,
  577. type: 'boolean'
  578. },
  579. driverInfo: {
  580. target: 'metadata',
  581. default: (0, utils_1.makeClientMetadata)(),
  582. transform({ options, values: [value] }) {
  583. var _a, _b;
  584. if (!(0, utils_1.isRecord)(value))
  585. throw new error_1.MongoParseError('DriverInfo must be an object');
  586. return (0, utils_1.makeClientMetadata)({
  587. driverInfo: value,
  588. appName: (_b = (_a = options.metadata) === null || _a === void 0 ? void 0 : _a.application) === null || _b === void 0 ? void 0 : _b.name
  589. });
  590. }
  591. },
  592. enableUtf8Validation: { type: 'boolean', default: true },
  593. family: {
  594. transform({ name, values: [value] }) {
  595. const transformValue = getInt(name, value);
  596. if (transformValue === 4 || transformValue === 6) {
  597. return transformValue;
  598. }
  599. throw new error_1.MongoParseError(`Option 'family' must be 4 or 6 got ${transformValue}.`);
  600. }
  601. },
  602. fieldsAsRaw: {
  603. type: 'record'
  604. },
  605. forceServerObjectId: {
  606. default: false,
  607. type: 'boolean'
  608. },
  609. fsync: {
  610. deprecated: 'Please use journal instead',
  611. target: 'writeConcern',
  612. transform({ name, options, values: [value] }) {
  613. const wc = write_concern_1.WriteConcern.fromOptions({
  614. writeConcern: {
  615. ...options.writeConcern,
  616. fsync: getBoolean(name, value)
  617. }
  618. });
  619. if (!wc)
  620. throw new error_1.MongoParseError(`Unable to make a writeConcern from fsync=${value}`);
  621. return wc;
  622. }
  623. },
  624. heartbeatFrequencyMS: {
  625. default: 10000,
  626. type: 'uint'
  627. },
  628. ignoreUndefined: {
  629. type: 'boolean'
  630. },
  631. j: {
  632. deprecated: 'Please use journal instead',
  633. target: 'writeConcern',
  634. transform({ name, options, values: [value] }) {
  635. const wc = write_concern_1.WriteConcern.fromOptions({
  636. writeConcern: {
  637. ...options.writeConcern,
  638. journal: getBoolean(name, value)
  639. }
  640. });
  641. if (!wc)
  642. throw new error_1.MongoParseError(`Unable to make a writeConcern from journal=${value}`);
  643. return wc;
  644. }
  645. },
  646. journal: {
  647. target: 'writeConcern',
  648. transform({ name, options, values: [value] }) {
  649. const wc = write_concern_1.WriteConcern.fromOptions({
  650. writeConcern: {
  651. ...options.writeConcern,
  652. journal: getBoolean(name, value)
  653. }
  654. });
  655. if (!wc)
  656. throw new error_1.MongoParseError(`Unable to make a writeConcern from journal=${value}`);
  657. return wc;
  658. }
  659. },
  660. keepAlive: {
  661. default: true,
  662. type: 'boolean'
  663. },
  664. keepAliveInitialDelay: {
  665. default: 120000,
  666. type: 'uint'
  667. },
  668. loadBalanced: {
  669. default: false,
  670. type: 'boolean'
  671. },
  672. localThresholdMS: {
  673. default: 15,
  674. type: 'uint'
  675. },
  676. logger: {
  677. default: new logger_1.Logger('MongoClient'),
  678. transform({ values: [value] }) {
  679. if (value instanceof logger_1.Logger) {
  680. return value;
  681. }
  682. (0, utils_1.emitWarning)('Alternative loggers might not be supported');
  683. // TODO: make Logger an interface that others can implement, make usage consistent in driver
  684. // DRIVERS-1204
  685. return;
  686. }
  687. },
  688. loggerLevel: {
  689. target: 'logger',
  690. transform({ values: [value] }) {
  691. return new logger_1.Logger('MongoClient', { loggerLevel: value });
  692. }
  693. },
  694. maxConnecting: {
  695. default: 2,
  696. transform({ name, values: [value] }) {
  697. const maxConnecting = getUint(name, value);
  698. if (maxConnecting === 0) {
  699. throw new error_1.MongoInvalidArgumentError('maxConnecting must be > 0 if specified');
  700. }
  701. return maxConnecting;
  702. }
  703. },
  704. maxIdleTimeMS: {
  705. default: 0,
  706. type: 'uint'
  707. },
  708. maxPoolSize: {
  709. default: 100,
  710. type: 'uint'
  711. },
  712. maxStalenessSeconds: {
  713. target: 'readPreference',
  714. transform({ name, options, values: [value] }) {
  715. const maxStalenessSeconds = getUint(name, value);
  716. if (options.readPreference) {
  717. return read_preference_1.ReadPreference.fromOptions({
  718. readPreference: { ...options.readPreference, maxStalenessSeconds }
  719. });
  720. }
  721. else {
  722. return new read_preference_1.ReadPreference('secondary', undefined, { maxStalenessSeconds });
  723. }
  724. }
  725. },
  726. minInternalBufferSize: {
  727. type: 'uint'
  728. },
  729. minPoolSize: {
  730. default: 0,
  731. type: 'uint'
  732. },
  733. minHeartbeatFrequencyMS: {
  734. default: 500,
  735. type: 'uint'
  736. },
  737. monitorCommands: {
  738. default: false,
  739. type: 'boolean'
  740. },
  741. name: {
  742. target: 'driverInfo',
  743. transform({ values: [value], options }) {
  744. return { ...options.driverInfo, name: String(value) };
  745. }
  746. },
  747. noDelay: {
  748. default: true,
  749. type: 'boolean'
  750. },
  751. pkFactory: {
  752. default: utils_1.DEFAULT_PK_FACTORY,
  753. transform({ values: [value] }) {
  754. if ((0, utils_1.isRecord)(value, ['createPk']) && typeof value.createPk === 'function') {
  755. return value;
  756. }
  757. throw new error_1.MongoParseError(`Option pkFactory must be an object with a createPk function, got ${value}`);
  758. }
  759. },
  760. promiseLibrary: {
  761. deprecated: true,
  762. type: 'any'
  763. },
  764. promoteBuffers: {
  765. type: 'boolean'
  766. },
  767. promoteLongs: {
  768. type: 'boolean'
  769. },
  770. promoteValues: {
  771. type: 'boolean'
  772. },
  773. proxyHost: {
  774. type: 'string'
  775. },
  776. proxyPassword: {
  777. type: 'string'
  778. },
  779. proxyPort: {
  780. type: 'uint'
  781. },
  782. proxyUsername: {
  783. type: 'string'
  784. },
  785. raw: {
  786. default: false,
  787. type: 'boolean'
  788. },
  789. readConcern: {
  790. transform({ values: [value], options }) {
  791. if (value instanceof read_concern_1.ReadConcern || (0, utils_1.isRecord)(value, ['level'])) {
  792. return read_concern_1.ReadConcern.fromOptions({ ...options.readConcern, ...value });
  793. }
  794. throw new error_1.MongoParseError(`ReadConcern must be an object, got ${JSON.stringify(value)}`);
  795. }
  796. },
  797. readConcernLevel: {
  798. target: 'readConcern',
  799. transform({ values: [level], options }) {
  800. return read_concern_1.ReadConcern.fromOptions({
  801. ...options.readConcern,
  802. level: level
  803. });
  804. }
  805. },
  806. readPreference: {
  807. default: read_preference_1.ReadPreference.primary,
  808. transform({ values: [value], options }) {
  809. var _a, _b, _c;
  810. if (value instanceof read_preference_1.ReadPreference) {
  811. return read_preference_1.ReadPreference.fromOptions({
  812. readPreference: { ...options.readPreference, ...value },
  813. ...value
  814. });
  815. }
  816. if ((0, utils_1.isRecord)(value, ['mode'])) {
  817. const rp = read_preference_1.ReadPreference.fromOptions({
  818. readPreference: { ...options.readPreference, ...value },
  819. ...value
  820. });
  821. if (rp)
  822. return rp;
  823. else
  824. throw new error_1.MongoParseError(`Cannot make read preference from ${JSON.stringify(value)}`);
  825. }
  826. if (typeof value === 'string') {
  827. const rpOpts = {
  828. hedge: (_a = options.readPreference) === null || _a === void 0 ? void 0 : _a.hedge,
  829. maxStalenessSeconds: (_b = options.readPreference) === null || _b === void 0 ? void 0 : _b.maxStalenessSeconds
  830. };
  831. return new read_preference_1.ReadPreference(value, (_c = options.readPreference) === null || _c === void 0 ? void 0 : _c.tags, rpOpts);
  832. }
  833. throw new error_1.MongoParseError(`Unknown ReadPreference value: ${value}`);
  834. }
  835. },
  836. readPreferenceTags: {
  837. target: 'readPreference',
  838. transform({ values, options }) {
  839. const tags = Array.isArray(values[0])
  840. ? values[0]
  841. : values;
  842. const readPreferenceTags = [];
  843. for (const tag of tags) {
  844. const readPreferenceTag = Object.create(null);
  845. if (typeof tag === 'string') {
  846. for (const [k, v] of entriesFromString(tag)) {
  847. readPreferenceTag[k] = v;
  848. }
  849. }
  850. if ((0, utils_1.isRecord)(tag)) {
  851. for (const [k, v] of Object.entries(tag)) {
  852. readPreferenceTag[k] = v;
  853. }
  854. }
  855. readPreferenceTags.push(readPreferenceTag);
  856. }
  857. return read_preference_1.ReadPreference.fromOptions({
  858. readPreference: options.readPreference,
  859. readPreferenceTags
  860. });
  861. }
  862. },
  863. replicaSet: {
  864. type: 'string'
  865. },
  866. retryReads: {
  867. default: true,
  868. type: 'boolean'
  869. },
  870. retryWrites: {
  871. default: true,
  872. type: 'boolean'
  873. },
  874. serializeFunctions: {
  875. type: 'boolean'
  876. },
  877. serverSelectionTimeoutMS: {
  878. default: 30000,
  879. type: 'uint'
  880. },
  881. servername: {
  882. type: 'string'
  883. },
  884. socketTimeoutMS: {
  885. default: 0,
  886. type: 'uint'
  887. },
  888. srvMaxHosts: {
  889. type: 'uint',
  890. default: 0
  891. },
  892. srvServiceName: {
  893. type: 'string',
  894. default: 'mongodb'
  895. },
  896. ssl: {
  897. target: 'tls',
  898. type: 'boolean'
  899. },
  900. sslCA: {
  901. target: 'ca',
  902. transform({ values: [value] }) {
  903. return fs.readFileSync(String(value), { encoding: 'ascii' });
  904. }
  905. },
  906. sslCRL: {
  907. target: 'crl',
  908. transform({ values: [value] }) {
  909. return fs.readFileSync(String(value), { encoding: 'ascii' });
  910. }
  911. },
  912. sslCert: {
  913. target: 'cert',
  914. transform({ values: [value] }) {
  915. return fs.readFileSync(String(value), { encoding: 'ascii' });
  916. }
  917. },
  918. sslKey: {
  919. target: 'key',
  920. transform({ values: [value] }) {
  921. return fs.readFileSync(String(value), { encoding: 'ascii' });
  922. }
  923. },
  924. sslPass: {
  925. deprecated: true,
  926. target: 'passphrase',
  927. type: 'string'
  928. },
  929. sslValidate: {
  930. target: 'rejectUnauthorized',
  931. type: 'boolean'
  932. },
  933. tls: {
  934. type: 'boolean'
  935. },
  936. tlsAllowInvalidCertificates: {
  937. target: 'rejectUnauthorized',
  938. transform({ name, values: [value] }) {
  939. // allowInvalidCertificates is the inverse of rejectUnauthorized
  940. return !getBoolean(name, value);
  941. }
  942. },
  943. tlsAllowInvalidHostnames: {
  944. target: 'checkServerIdentity',
  945. transform({ name, values: [value] }) {
  946. // tlsAllowInvalidHostnames means setting the checkServerIdentity function to a noop
  947. return getBoolean(name, value) ? () => undefined : undefined;
  948. }
  949. },
  950. tlsCAFile: {
  951. target: 'ca',
  952. transform({ values: [value] }) {
  953. return fs.readFileSync(String(value), { encoding: 'ascii' });
  954. }
  955. },
  956. tlsCertificateFile: {
  957. target: 'cert',
  958. transform({ values: [value] }) {
  959. return fs.readFileSync(String(value), { encoding: 'ascii' });
  960. }
  961. },
  962. tlsCertificateKeyFile: {
  963. target: 'key',
  964. transform({ values: [value] }) {
  965. return fs.readFileSync(String(value), { encoding: 'ascii' });
  966. }
  967. },
  968. tlsCertificateKeyFilePassword: {
  969. target: 'passphrase',
  970. type: 'any'
  971. },
  972. tlsInsecure: {
  973. transform({ name, options, values: [value] }) {
  974. const tlsInsecure = getBoolean(name, value);
  975. if (tlsInsecure) {
  976. options.checkServerIdentity = () => undefined;
  977. options.rejectUnauthorized = false;
  978. }
  979. else {
  980. options.checkServerIdentity = options.tlsAllowInvalidHostnames
  981. ? () => undefined
  982. : undefined;
  983. options.rejectUnauthorized = options.tlsAllowInvalidCertificates ? false : true;
  984. }
  985. return tlsInsecure;
  986. }
  987. },
  988. w: {
  989. target: 'writeConcern',
  990. transform({ values: [value], options }) {
  991. return write_concern_1.WriteConcern.fromOptions({ writeConcern: { ...options.writeConcern, w: value } });
  992. }
  993. },
  994. waitQueueTimeoutMS: {
  995. default: 0,
  996. type: 'uint'
  997. },
  998. writeConcern: {
  999. target: 'writeConcern',
  1000. transform({ values: [value], options }) {
  1001. if ((0, utils_1.isRecord)(value) || value instanceof write_concern_1.WriteConcern) {
  1002. return write_concern_1.WriteConcern.fromOptions({
  1003. writeConcern: {
  1004. ...options.writeConcern,
  1005. ...value
  1006. }
  1007. });
  1008. }
  1009. else if (value === 'majority' || typeof value === 'number') {
  1010. return write_concern_1.WriteConcern.fromOptions({
  1011. writeConcern: {
  1012. ...options.writeConcern,
  1013. w: value
  1014. }
  1015. });
  1016. }
  1017. throw new error_1.MongoParseError(`Invalid WriteConcern cannot parse: ${JSON.stringify(value)}`);
  1018. }
  1019. },
  1020. wtimeout: {
  1021. deprecated: 'Please use wtimeoutMS instead',
  1022. target: 'writeConcern',
  1023. transform({ values: [value], options }) {
  1024. const wc = write_concern_1.WriteConcern.fromOptions({
  1025. writeConcern: {
  1026. ...options.writeConcern,
  1027. wtimeout: getUint('wtimeout', value)
  1028. }
  1029. });
  1030. if (wc)
  1031. return wc;
  1032. throw new error_1.MongoParseError(`Cannot make WriteConcern from wtimeout`);
  1033. }
  1034. },
  1035. wtimeoutMS: {
  1036. target: 'writeConcern',
  1037. transform({ values: [value], options }) {
  1038. const wc = write_concern_1.WriteConcern.fromOptions({
  1039. writeConcern: {
  1040. ...options.writeConcern,
  1041. wtimeoutMS: getUint('wtimeoutMS', value)
  1042. }
  1043. });
  1044. if (wc)
  1045. return wc;
  1046. throw new error_1.MongoParseError(`Cannot make WriteConcern from wtimeout`);
  1047. }
  1048. },
  1049. zlibCompressionLevel: {
  1050. default: 0,
  1051. type: 'int'
  1052. },
  1053. // Custom types for modifying core behavior
  1054. connectionType: { type: 'any' },
  1055. srvPoller: { type: 'any' },
  1056. // Accepted NodeJS Options
  1057. minDHSize: { type: 'any' },
  1058. pskCallback: { type: 'any' },
  1059. secureContext: { type: 'any' },
  1060. enableTrace: { type: 'any' },
  1061. requestCert: { type: 'any' },
  1062. rejectUnauthorized: { type: 'any' },
  1063. checkServerIdentity: { type: 'any' },
  1064. ALPNProtocols: { type: 'any' },
  1065. SNICallback: { type: 'any' },
  1066. session: { type: 'any' },
  1067. requestOCSP: { type: 'any' },
  1068. localAddress: { type: 'any' },
  1069. localPort: { type: 'any' },
  1070. hints: { type: 'any' },
  1071. lookup: { type: 'any' },
  1072. ca: { type: 'any' },
  1073. cert: { type: 'any' },
  1074. ciphers: { type: 'any' },
  1075. crl: { type: 'any' },
  1076. ecdhCurve: { type: 'any' },
  1077. key: { type: 'any' },
  1078. passphrase: { type: 'any' },
  1079. pfx: { type: 'any' },
  1080. secureProtocol: { type: 'any' },
  1081. index: { type: 'any' },
  1082. // Legacy Options, these are unused but left here to avoid errors with CSFLE lib
  1083. useNewUrlParser: { type: 'boolean' },
  1084. useUnifiedTopology: { type: 'boolean' }
  1085. };
  1086. exports.DEFAULT_OPTIONS = new CaseInsensitiveMap(Object.entries(exports.OPTIONS)
  1087. .filter(([, descriptor]) => descriptor.default != null)
  1088. .map(([k, d]) => [k, d.default]));
  1089. /**
  1090. * Set of permitted feature flags
  1091. * @internal
  1092. */
  1093. exports.FEATURE_FLAGS = new Set([Symbol.for('@@mdb.skipPingOnConnect')]);
  1094. //# sourceMappingURL=connection_string.js.map