connection_string.js 40 KB

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