connection-parameters.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. 'use strict'
  2. const dns = require('dns')
  3. const defaults = require('./defaults')
  4. const parse = require('pg-connection-string').parse // parses a connection string
  5. const val = function (key, config, envVar) {
  6. if (config[key]) {
  7. return config[key]
  8. }
  9. if (envVar === undefined) {
  10. envVar = process.env['PG' + key.toUpperCase()]
  11. } else if (envVar === false) {
  12. // do nothing ... use false
  13. } else {
  14. envVar = process.env[envVar]
  15. }
  16. return envVar || defaults[key]
  17. }
  18. const readSSLConfigFromEnvironment = function () {
  19. switch (process.env.PGSSLMODE) {
  20. case 'disable':
  21. return false
  22. case 'prefer':
  23. case 'require':
  24. case 'verify-ca':
  25. case 'verify-full':
  26. return true
  27. case 'no-verify':
  28. return { rejectUnauthorized: false }
  29. }
  30. return defaults.ssl
  31. }
  32. // Convert arg to a string, surround in single quotes, and escape single quotes and backslashes
  33. const quoteParamValue = function (value) {
  34. return "'" + ('' + value).replace(/\\/g, '\\\\').replace(/'/g, "\\'") + "'"
  35. }
  36. const add = function (params, config, paramName) {
  37. const value = config[paramName]
  38. if (value !== undefined && value !== null) {
  39. params.push(paramName + '=' + quoteParamValue(value))
  40. }
  41. }
  42. class ConnectionParameters {
  43. constructor(config) {
  44. // if a string is passed, it is a raw connection string so we parse it into a config
  45. config = typeof config === 'string' ? parse(config) : config || {}
  46. // if the config has a connectionString defined, parse IT into the config we use
  47. // this will override other default values with what is stored in connectionString
  48. if (config.connectionString) {
  49. config = Object.assign({}, config, parse(config.connectionString))
  50. }
  51. this.user = val('user', config)
  52. this.database = val('database', config)
  53. if (this.database === undefined) {
  54. this.database = this.user
  55. }
  56. this.port = parseInt(val('port', config), 10)
  57. this.host = val('host', config)
  58. // "hiding" the password so it doesn't show up in stack traces
  59. // or if the client is console.logged
  60. Object.defineProperty(this, 'password', {
  61. configurable: true,
  62. enumerable: false,
  63. writable: true,
  64. value: val('password', config),
  65. })
  66. this.binary = val('binary', config)
  67. this.options = val('options', config)
  68. this.ssl = typeof config.ssl === 'undefined' ? readSSLConfigFromEnvironment() : config.ssl
  69. if (typeof this.ssl === 'string') {
  70. if (this.ssl === 'true') {
  71. this.ssl = true
  72. }
  73. }
  74. // support passing in ssl=no-verify via connection string
  75. if (this.ssl === 'no-verify') {
  76. this.ssl = { rejectUnauthorized: false }
  77. }
  78. if (this.ssl && this.ssl.key) {
  79. Object.defineProperty(this.ssl, 'key', {
  80. enumerable: false,
  81. })
  82. }
  83. this.client_encoding = val('client_encoding', config)
  84. this.replication = val('replication', config)
  85. // a domain socket begins with '/'
  86. this.isDomainSocket = !(this.host || '').indexOf('/')
  87. this.application_name = val('application_name', config, 'PGAPPNAME')
  88. this.fallback_application_name = val('fallback_application_name', config, false)
  89. this.statement_timeout = val('statement_timeout', config, false)
  90. this.lock_timeout = val('lock_timeout', config, false)
  91. this.idle_in_transaction_session_timeout = val('idle_in_transaction_session_timeout', config, false)
  92. this.query_timeout = val('query_timeout', config, false)
  93. if (config.connectionTimeoutMillis === undefined) {
  94. this.connect_timeout = process.env.PGCONNECT_TIMEOUT || 0
  95. } else {
  96. this.connect_timeout = Math.floor(config.connectionTimeoutMillis / 1000)
  97. }
  98. if (config.keepAlive === false) {
  99. this.keepalives = 0
  100. } else if (config.keepAlive === true) {
  101. this.keepalives = 1
  102. }
  103. if (typeof config.keepAliveInitialDelayMillis === 'number') {
  104. this.keepalives_idle = Math.floor(config.keepAliveInitialDelayMillis / 1000)
  105. }
  106. }
  107. getLibpqConnectionString(cb) {
  108. const params = []
  109. add(params, this, 'user')
  110. add(params, this, 'password')
  111. add(params, this, 'port')
  112. add(params, this, 'application_name')
  113. add(params, this, 'fallback_application_name')
  114. add(params, this, 'connect_timeout')
  115. add(params, this, 'options')
  116. const ssl = typeof this.ssl === 'object' ? this.ssl : this.ssl ? { sslmode: this.ssl } : {}
  117. add(params, ssl, 'sslmode')
  118. add(params, ssl, 'sslca')
  119. add(params, ssl, 'sslkey')
  120. add(params, ssl, 'sslcert')
  121. add(params, ssl, 'sslrootcert')
  122. if (this.database) {
  123. params.push('dbname=' + quoteParamValue(this.database))
  124. }
  125. if (this.replication) {
  126. params.push('replication=' + quoteParamValue(this.replication))
  127. }
  128. if (this.host) {
  129. params.push('host=' + quoteParamValue(this.host))
  130. }
  131. if (this.isDomainSocket) {
  132. return cb(null, params.join(' '))
  133. }
  134. if (this.client_encoding) {
  135. params.push('client_encoding=' + quoteParamValue(this.client_encoding))
  136. }
  137. dns.lookup(this.host, function (err, address) {
  138. if (err) return cb(err, null)
  139. params.push('hostaddr=' + quoteParamValue(address))
  140. return cb(null, params.join(' '))
  141. })
  142. }
  143. }
  144. module.exports = ConnectionParameters