pg 8.4.1 → 8.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2010 - 2020 Brian Carlson
3
+ Copyright (c) 2010 - 2021 Brian Carlson
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -53,6 +53,11 @@ node-postgres's continued development has been made possible in part by generous
53
53
  <a href="https://crate.io" target="_blank">
54
54
  <img height="80" src="https://node-postgres.com/crate-io.png" />
55
55
  </a>
56
+
57
+ <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAABCAQAAAB0m0auAAAADElEQVR42mNkIBIAAABSAAI2VLqiAAAAAElFTkSuQmCC" />
58
+ <a href="https://www.eaze.com" target="_blank">
59
+ <img height="80" src="https://node-postgres.com/eaze.png" />
60
+ </a>
56
61
  </div>
57
62
 
58
63
  If you or your company are benefiting from node-postgres and would like to help keep the project financially sustainable [please consider supporting](https://github.com/sponsors/brianc) its development.
package/lib/client.js CHANGED
@@ -57,6 +57,15 @@ class Client extends EventEmitter {
57
57
  this.processID = null
58
58
  this.secretKey = null
59
59
  this.ssl = this.connectionParameters.ssl || false
60
+ // As with Password, make SSL->Key (the private key) non-enumerable.
61
+ // It won't show up in stack traces
62
+ // or if the client is console.logged
63
+ if (this.ssl && this.ssl.key) {
64
+ Object.defineProperty(this.ssl, 'key', {
65
+ enumerable: false,
66
+ })
67
+ }
68
+
60
69
  this._connectionTimeoutMillis = c.connectionTimeoutMillis || 0
61
70
  }
62
71
 
@@ -80,10 +80,20 @@ class ConnectionParameters {
80
80
 
81
81
  this.ssl = typeof config.ssl === 'undefined' ? readSSLConfigFromEnvironment() : config.ssl
82
82
 
83
+ if (typeof this.ssl === 'string') {
84
+ if (this.ssl === 'true') {
85
+ this.ssl = true
86
+ }
87
+ }
83
88
  // support passing in ssl=no-verify via connection string
84
89
  if (this.ssl === 'no-verify') {
85
90
  this.ssl = { rejectUnauthorized: false }
86
91
  }
92
+ if (this.ssl && this.ssl.key) {
93
+ Object.defineProperty(this.ssl, 'key', {
94
+ enumerable: false,
95
+ })
96
+ }
87
97
 
88
98
  this.client_encoding = val('client_encoding', config)
89
99
  this.replication = val('replication', config)
package/lib/connection.js CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  var net = require('net')
4
4
  var EventEmitter = require('events').EventEmitter
5
- var util = require('util')
6
5
 
7
6
  const { parse, serialize } = require('pg-protocol')
8
7
 
@@ -76,12 +75,18 @@ class Connection extends EventEmitter {
76
75
  return self.emit('error', new Error('There was an error establishing an SSL connection'))
77
76
  }
78
77
  var tls = require('tls')
79
- const options = Object.assign(
80
- {
81
- socket: self.stream,
82
- },
83
- self.ssl
84
- )
78
+ const options = {
79
+ socket: self.stream,
80
+ }
81
+
82
+ if (self.ssl !== true) {
83
+ Object.assign(options, self.ssl)
84
+
85
+ if ('key' in self.ssl) {
86
+ options.key = self.ssl.key
87
+ }
88
+ }
89
+
85
90
  if (net.isIP(host) === 0) {
86
91
  options.servername = host
87
92
  }
package/lib/index.js CHANGED
@@ -4,6 +4,7 @@ var Client = require('./client')
4
4
  var defaults = require('./defaults')
5
5
  var Connection = require('./connection')
6
6
  var Pool = require('pg-pool')
7
+ const { DatabaseError } = require('pg-protocol')
7
8
 
8
9
  const poolFactory = (Client) => {
9
10
  return class BoundPool extends Pool {
@@ -21,6 +22,7 @@ var PG = function (clientConstructor) {
21
22
  this._pools = []
22
23
  this.Connection = Connection
23
24
  this.types = require('pg-types')
25
+ this.DatabaseError = DatabaseError
24
26
  }
25
27
 
26
28
  if (typeof process.env.NODE_PG_FORCE_NATIVE !== 'undefined') {
@@ -40,9 +42,6 @@ if (typeof process.env.NODE_PG_FORCE_NATIVE !== 'undefined') {
40
42
  if (err.code !== 'MODULE_NOT_FOUND') {
41
43
  throw err
42
44
  }
43
- /* eslint-disable no-console */
44
- console.error(err.message)
45
- /* eslint-enable no-console */
46
45
  }
47
46
 
48
47
  // overwrite module.exports.native so that getter is never called again
package/lib/query.js CHANGED
@@ -197,22 +197,22 @@ class Query extends EventEmitter {
197
197
  })
198
198
  }
199
199
 
200
- if (this.values) {
201
- try {
202
- this.values = this.values.map(utils.prepareValue)
203
- } catch (err) {
204
- this.handleError(err, connection)
205
- return
206
- }
200
+ // because we're mapping user supplied values to
201
+ // postgres wire protocol compatible values it could
202
+ // throw an exception, so try/catch this section
203
+ try {
204
+ connection.bind({
205
+ portal: this.portal,
206
+ statement: this.name,
207
+ values: this.values,
208
+ binary: this.binary,
209
+ valueMapper: utils.prepareValue,
210
+ })
211
+ } catch (err) {
212
+ this.handleError(err, connection)
213
+ return
207
214
  }
208
215
 
209
- connection.bind({
210
- portal: this.portal,
211
- statement: this.name,
212
- values: this.values,
213
- binary: this.binary,
214
- })
215
-
216
216
  connection.describe({
217
217
  type: 'P',
218
218
  name: this.portal || '',
package/lib/sasl.js CHANGED
@@ -20,19 +20,27 @@ function continueSession(session, password, serverData) {
20
20
  if (session.message !== 'SASLInitialResponse') {
21
21
  throw new Error('SASL: Last message was not SASLInitialResponse')
22
22
  }
23
+ if (typeof password !== 'string') {
24
+ throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string')
25
+ }
26
+ if (typeof serverData !== 'string') {
27
+ throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: serverData must be a string')
28
+ }
23
29
 
24
- const sv = extractVariablesFromFirstServerMessage(serverData)
30
+ const sv = parseServerFirstMessage(serverData)
25
31
 
26
32
  if (!sv.nonce.startsWith(session.clientNonce)) {
27
33
  throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce does not start with client nonce')
34
+ } else if (sv.nonce.length === session.clientNonce.length) {
35
+ throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce is too short')
28
36
  }
29
37
 
30
38
  var saltBytes = Buffer.from(sv.salt, 'base64')
31
39
 
32
40
  var saltedPassword = Hi(password, saltBytes, sv.iteration)
33
41
 
34
- var clientKey = createHMAC(saltedPassword, 'Client Key')
35
- var storedKey = crypto.createHash('sha256').update(clientKey).digest()
42
+ var clientKey = hmacSha256(saltedPassword, 'Client Key')
43
+ var storedKey = sha256(clientKey)
36
44
 
37
45
  var clientFirstMessageBare = 'n=*,r=' + session.clientNonce
38
46
  var serverFirstMessage = 'r=' + sv.nonce + ',s=' + sv.salt + ',i=' + sv.iteration
@@ -41,12 +49,12 @@ function continueSession(session, password, serverData) {
41
49
 
42
50
  var authMessage = clientFirstMessageBare + ',' + serverFirstMessage + ',' + clientFinalMessageWithoutProof
43
51
 
44
- var clientSignature = createHMAC(storedKey, authMessage)
52
+ var clientSignature = hmacSha256(storedKey, authMessage)
45
53
  var clientProofBytes = xorBuffers(clientKey, clientSignature)
46
54
  var clientProof = clientProofBytes.toString('base64')
47
55
 
48
- var serverKey = createHMAC(saltedPassword, 'Server Key')
49
- var serverSignatureBytes = createHMAC(serverKey, authMessage)
56
+ var serverKey = hmacSha256(saltedPassword, 'Server Key')
57
+ var serverSignatureBytes = hmacSha256(serverKey, authMessage)
50
58
 
51
59
  session.message = 'SASLResponse'
52
60
  session.serverSignature = serverSignatureBytes.toString('base64')
@@ -57,54 +65,87 @@ function finalizeSession(session, serverData) {
57
65
  if (session.message !== 'SASLResponse') {
58
66
  throw new Error('SASL: Last message was not SASLResponse')
59
67
  }
68
+ if (typeof serverData !== 'string') {
69
+ throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: serverData must be a string')
70
+ }
60
71
 
61
- var serverSignature
62
-
63
- String(serverData)
64
- .split(',')
65
- .forEach(function (part) {
66
- switch (part[0]) {
67
- case 'v':
68
- serverSignature = part.substr(2)
69
- break
70
- }
71
- })
72
+ const { serverSignature } = parseServerFinalMessage(serverData)
72
73
 
73
74
  if (serverSignature !== session.serverSignature) {
74
75
  throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not match')
75
76
  }
76
77
  }
77
78
 
78
- function extractVariablesFromFirstServerMessage(data) {
79
- var nonce, salt, iteration
80
-
81
- String(data)
82
- .split(',')
83
- .forEach(function (part) {
84
- switch (part[0]) {
85
- case 'r':
86
- nonce = part.substr(2)
87
- break
88
- case 's':
89
- salt = part.substr(2)
90
- break
91
- case 'i':
92
- iteration = parseInt(part.substr(2), 10)
93
- break
79
+ /**
80
+ * printable = %x21-2B / %x2D-7E
81
+ * ;; Printable ASCII except ",".
82
+ * ;; Note that any "printable" is also
83
+ * ;; a valid "value".
84
+ */
85
+ function isPrintableChars(text) {
86
+ if (typeof text !== 'string') {
87
+ throw new TypeError('SASL: text must be a string')
88
+ }
89
+ return text
90
+ .split('')
91
+ .map((_, i) => text.charCodeAt(i))
92
+ .every((c) => (c >= 0x21 && c <= 0x2b) || (c >= 0x2d && c <= 0x7e))
93
+ }
94
+
95
+ /**
96
+ * base64-char = ALPHA / DIGIT / "/" / "+"
97
+ *
98
+ * base64-4 = 4base64-char
99
+ *
100
+ * base64-3 = 3base64-char "="
101
+ *
102
+ * base64-2 = 2base64-char "=="
103
+ *
104
+ * base64 = *base64-4 [base64-3 / base64-2]
105
+ */
106
+ function isBase64(text) {
107
+ return /^(?:[a-zA-Z0-9+/]{4})*(?:[a-zA-Z0-9+/]{2}==|[a-zA-Z0-9+/]{3}=)?$/.test(text)
108
+ }
109
+
110
+ function parseAttributePairs(text) {
111
+ if (typeof text !== 'string') {
112
+ throw new TypeError('SASL: attribute pairs text must be a string')
113
+ }
114
+
115
+ return new Map(
116
+ text.split(',').map((attrValue) => {
117
+ if (!/^.=/.test(attrValue)) {
118
+ throw new Error('SASL: Invalid attribute pair entry')
94
119
  }
120
+ const name = attrValue[0]
121
+ const value = attrValue.substring(2)
122
+ return [name, value]
95
123
  })
124
+ )
125
+ }
96
126
 
127
+ function parseServerFirstMessage(data) {
128
+ const attrPairs = parseAttributePairs(data)
129
+
130
+ const nonce = attrPairs.get('r')
97
131
  if (!nonce) {
98
132
  throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing')
133
+ } else if (!isPrintableChars(nonce)) {
134
+ throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce must only contain printable characters')
99
135
  }
100
-
136
+ const salt = attrPairs.get('s')
101
137
  if (!salt) {
102
138
  throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: salt missing')
139
+ } else if (!isBase64(salt)) {
140
+ throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: salt must be base64')
103
141
  }
104
-
105
- if (!iteration) {
142
+ const iterationText = attrPairs.get('i')
143
+ if (!iterationText) {
106
144
  throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration missing')
145
+ } else if (!/^[1-9][0-9]*$/.test(iterationText)) {
146
+ throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: invalid iteration count')
107
147
  }
148
+ const iteration = parseInt(iterationText, 10)
108
149
 
109
150
  return {
110
151
  nonce,
@@ -113,31 +154,48 @@ function extractVariablesFromFirstServerMessage(data) {
113
154
  }
114
155
  }
115
156
 
157
+ function parseServerFinalMessage(serverData) {
158
+ const attrPairs = parseAttributePairs(serverData)
159
+ const serverSignature = attrPairs.get('v')
160
+ if (!serverSignature) {
161
+ throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature is missing')
162
+ } else if (!isBase64(serverSignature)) {
163
+ throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature must be base64')
164
+ }
165
+ return {
166
+ serverSignature,
167
+ }
168
+ }
169
+
116
170
  function xorBuffers(a, b) {
117
- if (!Buffer.isBuffer(a)) a = Buffer.from(a)
118
- if (!Buffer.isBuffer(b)) b = Buffer.from(b)
119
- var res = []
120
- if (a.length > b.length) {
121
- for (var i = 0; i < b.length; i++) {
122
- res.push(a[i] ^ b[i])
123
- }
124
- } else {
125
- for (var j = 0; j < a.length; j++) {
126
- res.push(a[j] ^ b[j])
127
- }
128
- }
129
- return Buffer.from(res)
171
+ if (!Buffer.isBuffer(a)) {
172
+ throw new TypeError('first argument must be a Buffer')
173
+ }
174
+ if (!Buffer.isBuffer(b)) {
175
+ throw new TypeError('second argument must be a Buffer')
176
+ }
177
+ if (a.length !== b.length) {
178
+ throw new Error('Buffer lengths must match')
179
+ }
180
+ if (a.length === 0) {
181
+ throw new Error('Buffers cannot be empty')
182
+ }
183
+ return Buffer.from(a.map((_, i) => a[i] ^ b[i]))
184
+ }
185
+
186
+ function sha256(text) {
187
+ return crypto.createHash('sha256').update(text).digest()
130
188
  }
131
189
 
132
- function createHMAC(key, msg) {
190
+ function hmacSha256(key, msg) {
133
191
  return crypto.createHmac('sha256', key).update(msg).digest()
134
192
  }
135
193
 
136
194
  function Hi(password, saltBytes, iterations) {
137
- var ui1 = createHMAC(password, Buffer.concat([saltBytes, Buffer.from([0, 0, 0, 1])]))
195
+ var ui1 = hmacSha256(password, Buffer.concat([saltBytes, Buffer.from([0, 0, 0, 1])]))
138
196
  var ui = ui1
139
197
  for (var i = 0; i < iterations - 1; i++) {
140
- ui1 = createHMAC(password, ui1)
198
+ ui1 = hmacSha256(password, ui1)
141
199
  ui = xorBuffers(ui, ui1)
142
200
  }
143
201
 
package/lib/utils.js CHANGED
@@ -38,6 +38,10 @@ function arrayString(val) {
38
38
  // note: you can override this function to provide your own conversion mechanism
39
39
  // for complex types, etc...
40
40
  var prepareValue = function (val, seen) {
41
+ // null and undefined are both null for postgres
42
+ if (val == null) {
43
+ return null
44
+ }
41
45
  if (val instanceof Buffer) {
42
46
  return val
43
47
  }
@@ -58,9 +62,6 @@ var prepareValue = function (val, seen) {
58
62
  if (Array.isArray(val)) {
59
63
  return arrayString(val)
60
64
  }
61
- if (val === null || typeof val === 'undefined') {
62
- return null
63
- }
64
65
  if (typeof val === 'object') {
65
66
  return prepareObject(val, seen)
66
67
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pg",
3
- "version": "8.4.1",
3
+ "version": "8.6.0",
4
4
  "description": "PostgreSQL client - pure javascript & libpq with the same API",
5
5
  "keywords": [
6
6
  "database",
@@ -14,16 +14,17 @@
14
14
  "homepage": "https://github.com/brianc/node-postgres",
15
15
  "repository": {
16
16
  "type": "git",
17
- "url": "git://github.com/brianc/node-postgres.git"
17
+ "url": "git://github.com/brianc/node-postgres.git",
18
+ "directory": "packages/pg"
18
19
  },
19
20
  "author": "Brian Carlson <brian.m.carlson@gmail.com>",
20
21
  "main": "./lib",
21
22
  "dependencies": {
22
23
  "buffer-writer": "2.0.0",
23
24
  "packet-reader": "1.0.0",
24
- "pg-connection-string": "^2.4.0",
25
- "pg-pool": "^3.2.1",
26
- "pg-protocol": "^1.3.0",
25
+ "pg-connection-string": "^2.5.0",
26
+ "pg-pool": "^3.3.0",
27
+ "pg-protocol": "^1.5.0",
27
28
  "pg-types": "^2.1.0",
28
29
  "pgpass": "1.x"
29
30
  },
@@ -52,5 +53,5 @@
52
53
  "engines": {
53
54
  "node": ">= 8.0.0"
54
55
  },
55
- "gitHead": "36342c9a84b68123f666879a9f34ac319a44727a"
56
+ "gitHead": "d45947938263bec30a1e3252452f04177b785f66"
56
57
  }