connection_config.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. // This file was modified by Oracle on September 21, 2021.
  2. // New connection options for additional authentication factors were
  3. // introduced.
  4. // Multi-factor authentication capability is now enabled if one of these
  5. // options is used.
  6. // Modifications copyright (c) 2021, Oracle and/or its affiliates.
  7. 'use strict';
  8. const { URL } = require('url');
  9. const ClientConstants = require('./constants/client');
  10. const Charsets = require('./constants/charsets');
  11. const { version } = require('../package.json')
  12. let SSLProfiles = null;
  13. const validOptions = {
  14. authPlugins: 1,
  15. authSwitchHandler: 1,
  16. bigNumberStrings: 1,
  17. charset: 1,
  18. charsetNumber: 1,
  19. compress: 1,
  20. connectAttributes: 1,
  21. connectTimeout: 1,
  22. database: 1,
  23. dateStrings: 1,
  24. debug: 1,
  25. decimalNumbers: 1,
  26. enableKeepAlive: 1,
  27. flags: 1,
  28. host: 1,
  29. insecureAuth: 1,
  30. isServer: 1,
  31. keepAliveInitialDelay: 1,
  32. localAddress: 1,
  33. maxPreparedStatements: 1,
  34. multipleStatements: 1,
  35. namedPlaceholders: 1,
  36. nestTables: 1,
  37. password: 1,
  38. // with multi-factor authentication, the main password (used for the first
  39. // authentication factor) can be provided via password1
  40. password1: 1,
  41. password2: 1,
  42. password3: 1,
  43. passwordSha1: 1,
  44. pool: 1,
  45. port: 1,
  46. queryFormat: 1,
  47. rowsAsArray: 1,
  48. socketPath: 1,
  49. ssl: 1,
  50. stream: 1,
  51. stringifyObjects: 1,
  52. supportBigNumbers: 1,
  53. timezone: 1,
  54. trace: 1,
  55. typeCast: 1,
  56. uri: 1,
  57. user: 1,
  58. // These options are used for Pool
  59. connectionLimit: 1,
  60. maxIdle: 1,
  61. idleTimeout: 1,
  62. Promise: 1,
  63. queueLimit: 1,
  64. waitForConnections: 1
  65. };
  66. class ConnectionConfig {
  67. constructor(options) {
  68. if (typeof options === 'string') {
  69. options = ConnectionConfig.parseUrl(options);
  70. } else if (options && options.uri) {
  71. const uriOptions = ConnectionConfig.parseUrl(options.uri);
  72. for (const key in uriOptions) {
  73. if (!Object.prototype.hasOwnProperty.call(uriOptions, key)) continue;
  74. if (options[key]) continue;
  75. options[key] = uriOptions[key];
  76. }
  77. }
  78. for (const key in options) {
  79. if (!Object.prototype.hasOwnProperty.call(options, key)) continue;
  80. if (validOptions[key] !== 1) {
  81. // REVIEW: Should this be emitted somehow?
  82. // eslint-disable-next-line no-console
  83. console.error(
  84. `Ignoring invalid configuration option passed to Connection: ${key}. This is currently a warning, but in future versions of MySQL2, an error will be thrown if you pass an invalid configuration option to a Connection`
  85. );
  86. }
  87. }
  88. this.isServer = options.isServer;
  89. this.stream = options.stream;
  90. this.host = options.host || 'localhost';
  91. this.port = (typeof options.port === 'string' ? parseInt(options.port, 10) : options.port)|| 3306;
  92. this.localAddress = options.localAddress;
  93. this.socketPath = options.socketPath;
  94. this.user = options.user || undefined;
  95. // for the purpose of multi-factor authentication, or not, the main
  96. // password (used for the 1st authentication factor) can also be
  97. // provided via the "password1" option
  98. this.password = options.password || options.password1 || undefined;
  99. this.password2 = options.password2 || undefined;
  100. this.password3 = options.password3 || undefined;
  101. this.passwordSha1 = options.passwordSha1 || undefined;
  102. this.database = options.database;
  103. this.connectTimeout = isNaN(options.connectTimeout)
  104. ? 10 * 1000
  105. : options.connectTimeout;
  106. this.insecureAuth = options.insecureAuth || false;
  107. this.supportBigNumbers = options.supportBigNumbers || false;
  108. this.bigNumberStrings = options.bigNumberStrings || false;
  109. this.decimalNumbers = options.decimalNumbers || false;
  110. this.dateStrings = options.dateStrings || false;
  111. this.debug = options.debug;
  112. this.trace = options.trace !== false;
  113. this.stringifyObjects = options.stringifyObjects || false;
  114. this.enableKeepAlive = options.enableKeepAlive !== false;
  115. this.keepAliveInitialDelay = options.keepAliveInitialDelay || 0;
  116. if (
  117. options.timezone &&
  118. !/^(?:local|Z|[ +-]\d\d:\d\d)$/.test(options.timezone)
  119. ) {
  120. // strictly supports timezones specified by mysqljs/mysql:
  121. // https://github.com/mysqljs/mysql#user-content-connection-options
  122. // eslint-disable-next-line no-console
  123. console.error(
  124. `Ignoring invalid timezone passed to Connection: ${options.timezone}. This is currently a warning, but in future versions of MySQL2, an error will be thrown if you pass an invalid configuration option to a Connection`
  125. );
  126. // SqlStrings falls back to UTC on invalid timezone
  127. this.timezone = 'Z';
  128. } else {
  129. this.timezone = options.timezone || 'local';
  130. }
  131. this.queryFormat = options.queryFormat;
  132. this.pool = options.pool || undefined;
  133. this.ssl =
  134. typeof options.ssl === 'string'
  135. ? ConnectionConfig.getSSLProfile(options.ssl)
  136. : options.ssl || false;
  137. this.multipleStatements = options.multipleStatements || false;
  138. this.rowsAsArray = options.rowsAsArray || false;
  139. this.namedPlaceholders = options.namedPlaceholders || false;
  140. this.nestTables =
  141. options.nestTables === undefined ? undefined : options.nestTables;
  142. this.typeCast = options.typeCast === undefined ? true : options.typeCast;
  143. if (this.timezone[0] === ' ') {
  144. // "+" is a url encoded char for space so it
  145. // gets translated to space when giving a
  146. // connection string..
  147. this.timezone = `+${this.timezone.slice(1)}`;
  148. }
  149. if (this.ssl) {
  150. if (typeof this.ssl !== 'object') {
  151. throw new TypeError(
  152. `SSL profile must be an object, instead it's a ${typeof this.ssl}`
  153. );
  154. }
  155. // Default rejectUnauthorized to true
  156. this.ssl.rejectUnauthorized = this.ssl.rejectUnauthorized !== false;
  157. }
  158. this.maxPacketSize = 0;
  159. this.charsetNumber = options.charset
  160. ? ConnectionConfig.getCharsetNumber(options.charset)
  161. : options.charsetNumber || Charsets.UTF8MB4_UNICODE_CI;
  162. this.compress = options.compress || false;
  163. this.authPlugins = options.authPlugins;
  164. this.authSwitchHandler = options.authSwitchHandler;
  165. this.clientFlags = ConnectionConfig.mergeFlags(
  166. ConnectionConfig.getDefaultFlags(options),
  167. options.flags || ''
  168. );
  169. // Default connection attributes
  170. // https://dev.mysql.com/doc/refman/8.0/en/performance-schema-connection-attribute-tables.html
  171. const defaultConnectAttributes = {
  172. _client_name: 'Node-MySQL-2',
  173. _client_version: version
  174. };
  175. this.connectAttributes = { ...defaultConnectAttributes, ...(options.connectAttributes || {})};
  176. this.maxPreparedStatements = options.maxPreparedStatements || 16000;
  177. }
  178. static mergeFlags(default_flags, user_flags) {
  179. let flags = 0x0,
  180. i;
  181. if (!Array.isArray(user_flags)) {
  182. user_flags = String(user_flags || '')
  183. .toUpperCase()
  184. .split(/\s*,+\s*/);
  185. }
  186. // add default flags unless "blacklisted"
  187. for (i in default_flags) {
  188. if (user_flags.indexOf(`-${default_flags[i]}`) >= 0) {
  189. continue;
  190. }
  191. flags |= ClientConstants[default_flags[i]] || 0x0;
  192. }
  193. // add user flags unless already already added
  194. for (i in user_flags) {
  195. if (user_flags[i][0] === '-') {
  196. continue;
  197. }
  198. if (default_flags.indexOf(user_flags[i]) >= 0) {
  199. continue;
  200. }
  201. flags |= ClientConstants[user_flags[i]] || 0x0;
  202. }
  203. return flags;
  204. }
  205. static getDefaultFlags(options) {
  206. const defaultFlags = [
  207. 'LONG_PASSWORD',
  208. 'FOUND_ROWS',
  209. 'LONG_FLAG',
  210. 'CONNECT_WITH_DB',
  211. 'ODBC',
  212. 'LOCAL_FILES',
  213. 'IGNORE_SPACE',
  214. 'PROTOCOL_41',
  215. 'IGNORE_SIGPIPE',
  216. 'TRANSACTIONS',
  217. 'RESERVED',
  218. 'SECURE_CONNECTION',
  219. 'MULTI_RESULTS',
  220. 'TRANSACTIONS',
  221. 'SESSION_TRACK',
  222. 'CONNECT_ATTRS'
  223. ];
  224. if (options && options.multipleStatements) {
  225. defaultFlags.push('MULTI_STATEMENTS');
  226. }
  227. defaultFlags.push('PLUGIN_AUTH');
  228. defaultFlags.push('PLUGIN_AUTH_LENENC_CLIENT_DATA');
  229. return defaultFlags;
  230. }
  231. static getCharsetNumber(charset) {
  232. const num = Charsets[charset.toUpperCase()];
  233. if (num === undefined) {
  234. throw new TypeError(`Unknown charset '${charset}'`);
  235. }
  236. return num;
  237. }
  238. static getSSLProfile(name) {
  239. if (!SSLProfiles) {
  240. SSLProfiles = require('./constants/ssl_profiles.js');
  241. }
  242. const ssl = SSLProfiles[name];
  243. if (ssl === undefined) {
  244. throw new TypeError(`Unknown SSL profile '${name}'`);
  245. }
  246. return ssl;
  247. }
  248. static parseUrl(url) {
  249. const parsedUrl = new URL(url);
  250. const options = {
  251. host: parsedUrl.hostname,
  252. port: parseInt(parsedUrl.port, 10),
  253. database: parsedUrl.pathname.slice(1),
  254. user: parsedUrl.username,
  255. password: parsedUrl.password
  256. };
  257. parsedUrl.searchParams.forEach((value, key) => {
  258. try {
  259. // Try to parse this as a JSON expression first
  260. options[key] = JSON.parse(value);
  261. } catch (err) {
  262. // Otherwise assume it is a plain string
  263. options[key] = value;
  264. }
  265. });
  266. return options;
  267. }
  268. }
  269. module.exports = ConnectionConfig;