connection_string.js 40 KB

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