pg 7.7.1 → 7.9.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/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ For richer information consult the commit log on github with referenced pull req
4
4
 
5
5
  We do not include break-fix version release in this file.
6
6
 
7
+ ### 7.9.0
8
+
9
+ - Add support for [sasl/scram authentication](https://github.com/brianc/node-postgres/pull/1835).
10
+
11
+ ### 7.8.0
12
+
13
+ - Add support for passing [secureOptions](https://github.com/brianc/node-postgres/pull/1804) SSL config.
14
+ - Upgrade [pg-types](https://github.com/brianc/node-postgres/pull/1806) to 2.0.
15
+
7
16
  ### 7.7.0
8
17
 
9
18
  - Add support for configurable [query timeout](https://github.com/brianc/node-postgres/pull/1760) on a client level.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2010 - 2018 Brian Carlson
3
+ Copyright (c) 2010 - 2019 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
@@ -67,7 +67,7 @@ The causes and solutions to common errors can be found among the [Frequently Ask
67
67
 
68
68
  ## License
69
69
 
70
- Copyright (c) 2010-2018 Brian Carlson (brian.m.carlson@gmail.com)
70
+ Copyright (c) 2010-2019 Brian Carlson (brian.m.carlson@gmail.com)
71
71
 
72
72
  Permission is hereby granted, free of charge, to any person obtaining a copy
73
73
  of this software and associated documentation files (the "Software"), to deal
package/SPONSORS.md CHANGED
@@ -2,6 +2,7 @@ node-postgres is made possible by the helpful contributors from the community we
2
2
 
3
3
  # Leaders
4
4
  - [MadKudu](https://www.madkudu.com) - [@madkudu](https://twitter.com/madkudu)
5
+ - [Third Iron](https://thirdiron.com/)
5
6
 
6
7
  # Supporters
7
8
  - John Fawcett
@@ -17,3 +18,5 @@ node-postgres is made possible by the helpful contributors from the community we
17
18
  - Benjie Gillam
18
19
  - David Hanson
19
20
  - Franklin Davenport
21
+ - [Eventbot](https://geteventbot.com/)
22
+ - Chuck T
package/lib/client.js CHANGED
@@ -10,6 +10,7 @@
10
10
  var EventEmitter = require('events').EventEmitter
11
11
  var util = require('util')
12
12
  var utils = require('./utils')
13
+ var sasl = require('./sasl')
13
14
  var pgPass = require('pgpass')
14
15
  var TypeOverrides = require('./type-overrides')
15
16
 
@@ -126,6 +127,28 @@ Client.prototype._connect = function (callback) {
126
127
  con.password(utils.postgresMd5PasswordHash(self.user, self.password, msg.salt))
127
128
  }))
128
129
 
130
+ // password request handling (SASL)
131
+ var saslSession
132
+ con.on('authenticationSASL', checkPgPass(function (msg) {
133
+ saslSession = sasl.startSession(msg.mechanisms)
134
+
135
+ con.sendSASLInitialResponseMessage(saslSession.mechanism, saslSession.response)
136
+ }))
137
+
138
+ // password request handling (SASL)
139
+ con.on('authenticationSASLContinue', function (msg) {
140
+ sasl.continueSession(saslSession, self.password, msg.data)
141
+
142
+ con.sendSCRAMClientFinalMessage(saslSession.response)
143
+ })
144
+
145
+ // password request handling (SASL)
146
+ con.on('authenticationSASLFinal', function (msg) {
147
+ sasl.finalizeSession(saslSession, msg.data)
148
+
149
+ saslSession = null
150
+ })
151
+
129
152
  con.once('backendKeyData', function (msg) {
130
153
  self.processID = msg.processID
131
154
  self.secretKey = msg.secretKey
@@ -276,7 +299,7 @@ Client.prototype._attachListeners = function (con) {
276
299
  // it again on the same client
277
300
  con.on('parseComplete', function (msg) {
278
301
  if (self.activeQuery.name) {
279
- con.parsedStatements[self.activeQuery.name] = true
302
+ con.parsedStatements[self.activeQuery.name] = self.activeQuery.text
280
303
  }
281
304
  })
282
305
 
package/lib/connection.js CHANGED
@@ -101,6 +101,7 @@ Connection.prototype.connect = function (port, host) {
101
101
  key: self.ssl.key,
102
102
  passphrase: self.ssl.passphrase,
103
103
  cert: self.ssl.cert,
104
+ secureOptions: self.ssl.secureOptions,
104
105
  NPNProtocols: self.ssl.NPNProtocols
105
106
  })
106
107
  self.attachListeners(self.stream)
@@ -190,6 +191,24 @@ Connection.prototype.password = function (password) {
190
191
  this._send(0x70, this.writer.addCString(password))
191
192
  }
192
193
 
194
+ Connection.prototype.sendSASLInitialResponseMessage = function (mechanism, initialResponse) {
195
+ // 0x70 = 'p'
196
+ this.writer
197
+ .addCString(mechanism)
198
+ .addInt32(Buffer.byteLength(initialResponse))
199
+ .addString(initialResponse)
200
+
201
+ this._send(0x70)
202
+ }
203
+
204
+ Connection.prototype.sendSCRAMClientFinalMessage = function (additionalData) {
205
+ // 0x70 = 'p'
206
+ this.writer
207
+ .addString(additionalData)
208
+
209
+ this._send(0x70)
210
+ }
211
+
193
212
  Connection.prototype._send = function (code, more) {
194
213
  if (!this.stream.writable) {
195
214
  return false
@@ -420,25 +439,53 @@ Connection.prototype.parseMessage = function (buffer) {
420
439
  }
421
440
 
422
441
  Connection.prototype.parseR = function (buffer, length) {
423
- var code = 0
442
+ var code = this.parseInt32(buffer)
443
+
424
444
  var msg = new Message('authenticationOk', length)
425
- if (msg.length === 8) {
426
- code = this.parseInt32(buffer)
427
- if (code === 3) {
428
- msg.name = 'authenticationCleartextPassword'
429
- }
430
- return msg
431
- }
432
- if (msg.length === 12) {
433
- code = this.parseInt32(buffer)
434
- if (code === 5) { // md5 required
435
- msg.name = 'authenticationMD5Password'
436
- msg.salt = Buffer.alloc(4)
437
- buffer.copy(msg.salt, 0, this.offset, this.offset + 4)
438
- this.offset += 4
445
+
446
+ switch (code) {
447
+ case 0: // AuthenticationOk
448
+ return msg
449
+ case 3: // AuthenticationCleartextPassword
450
+ if (msg.length === 8) {
451
+ msg.name = 'authenticationCleartextPassword'
452
+ return msg
453
+ }
454
+ break
455
+ case 5: // AuthenticationMD5Password
456
+ if (msg.length === 12) {
457
+ msg.name = 'authenticationMD5Password'
458
+ msg.salt = Buffer.alloc(4)
459
+ buffer.copy(msg.salt, 0, this.offset, this.offset + 4)
460
+ this.offset += 4
461
+ return msg
462
+ }
463
+
464
+ break
465
+ case 10: // AuthenticationSASL
466
+ msg.name = 'authenticationSASL'
467
+ msg.mechanisms = []
468
+ do {
469
+ var mechanism = this.parseCString(buffer)
470
+
471
+ if (mechanism) {
472
+ msg.mechanisms.push(mechanism)
473
+ }
474
+ } while (mechanism)
475
+
476
+ return msg
477
+ case 11: // AuthenticationSASLContinue
478
+ msg.name = 'authenticationSASLContinue'
479
+ msg.data = this.readString(buffer, length - 4)
480
+
481
+ return msg
482
+ case 12: // AuthenticationSASLFinal
483
+ msg.name = 'authenticationSASLFinal'
484
+ msg.data = this.readString(buffer, length - 4)
485
+
439
486
  return msg
440
- }
441
487
  }
488
+
442
489
  throw new Error('Unknown authenticationOk message type' + util.inspect(msg))
443
490
  }
444
491
 
@@ -139,12 +139,16 @@ NativeQuery.prototype.submit = function (client) {
139
139
  // check if the client has already executed this named query
140
140
  // if so...just execute it again - skip the planning phase
141
141
  if (client.namedQueries[this.name]) {
142
+ if (this.text && client.namedQueries[this.name] !== this.text) {
143
+ const err = new Error(`Prepared statements must be unique - '${this.name}' was used for a different statement`)
144
+ return after(err)
145
+ }
142
146
  return client.native.execute(this.name, values, after)
143
147
  }
144
148
  // plan the named query the first time, then execute it
145
149
  return client.native.prepare(this.name, this.text, values.length, function (err) {
146
150
  if (err) return after(err)
147
- client.namedQueries[self.name] = true
151
+ client.namedQueries[self.name] = self.text
148
152
  return self.native.execute(self.name, values, after)
149
153
  })
150
154
  } else if (this.values) {
package/lib/query.js CHANGED
@@ -148,6 +148,10 @@ Query.prototype.submit = function (connection) {
148
148
  if (typeof this.text !== 'string' && typeof this.name !== 'string') {
149
149
  return new Error('A query must have either text or a name. Supplying neither is unsupported.')
150
150
  }
151
+ const previous = connection.parsedStatements[this.name]
152
+ if (this.text && previous && this.text !== previous) {
153
+ return new Error(`Prepared statements must be unique - '${this.name}' was used for a different statement`)
154
+ }
151
155
  if (this.values && !Array.isArray(this.values)) {
152
156
  return new Error('Query values must be an array')
153
157
  }
package/lib/sasl.js ADDED
@@ -0,0 +1,146 @@
1
+ const crypto = require('crypto')
2
+
3
+ function startSession (mechanisms) {
4
+ if (mechanisms.indexOf('SCRAM-SHA-256') === -1) {
5
+ throw new Error('SASL: Only mechanism SCRAM-SHA-256 is currently supported')
6
+ }
7
+
8
+ const clientNonce = crypto.randomBytes(18).toString('base64')
9
+
10
+ return {
11
+ mechanism: 'SCRAM-SHA-256',
12
+ clientNonce,
13
+ response: 'n,,n=*,r=' + clientNonce,
14
+ message: 'SASLInitialResponse'
15
+ }
16
+ }
17
+
18
+ function continueSession (session, password, serverData) {
19
+ if (session.message !== 'SASLInitialResponse') {
20
+ throw new Error('SASL: Last message was not SASLInitialResponse')
21
+ }
22
+
23
+ const sv = extractVariablesFromFirstServerMessage(serverData)
24
+
25
+ if (!sv.nonce.startsWith(session.clientNonce)) {
26
+ throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: server nonce does not start with client nonce')
27
+ }
28
+
29
+ var saltBytes = Buffer.from(sv.salt, 'base64')
30
+
31
+ var saltedPassword = Hi(password, saltBytes, sv.iteration)
32
+
33
+ var clientKey = createHMAC(saltedPassword, 'Client Key')
34
+ var storedKey = crypto.createHash('sha256').update(clientKey).digest()
35
+
36
+ var clientFirstMessageBare = 'n=*,r=' + session.clientNonce
37
+ var serverFirstMessage = 'r=' + sv.nonce + ',s=' + sv.salt + ',i=' + sv.iteration
38
+
39
+ var clientFinalMessageWithoutProof = 'c=biws,r=' + sv.nonce
40
+
41
+ var authMessage = clientFirstMessageBare + ',' + serverFirstMessage + ',' + clientFinalMessageWithoutProof
42
+
43
+ var clientSignature = createHMAC(storedKey, authMessage)
44
+ var clientProofBytes = xorBuffers(clientKey, clientSignature)
45
+ var clientProof = clientProofBytes.toString('base64')
46
+
47
+ var serverKey = createHMAC(saltedPassword, 'Server Key')
48
+ var serverSignatureBytes = createHMAC(serverKey, authMessage)
49
+
50
+ session.message = 'SASLResponse'
51
+ session.serverSignature = serverSignatureBytes.toString('base64')
52
+ session.response = clientFinalMessageWithoutProof + ',p=' + clientProof
53
+ }
54
+
55
+ function finalizeSession (session, serverData) {
56
+ if (session.message !== 'SASLResponse') {
57
+ throw new Error('SASL: Last message was not SASLResponse')
58
+ }
59
+
60
+ var serverSignature
61
+
62
+ String(serverData).split(',').forEach(function (part) {
63
+ switch (part[0]) {
64
+ case 'v':
65
+ serverSignature = part.substr(2)
66
+ break
67
+ }
68
+ })
69
+
70
+ if (serverSignature !== session.serverSignature) {
71
+ throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not match')
72
+ }
73
+ }
74
+
75
+ function extractVariablesFromFirstServerMessage (data) {
76
+ var nonce, salt, iteration
77
+
78
+ String(data).split(',').forEach(function (part) {
79
+ switch (part[0]) {
80
+ case 'r':
81
+ nonce = part.substr(2)
82
+ break
83
+ case 's':
84
+ salt = part.substr(2)
85
+ break
86
+ case 'i':
87
+ iteration = parseInt(part.substr(2), 10)
88
+ break
89
+ }
90
+ })
91
+
92
+ if (!nonce) {
93
+ throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing')
94
+ }
95
+
96
+ if (!salt) {
97
+ throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: salt missing')
98
+ }
99
+
100
+ if (!iteration) {
101
+ throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: iteration missing')
102
+ }
103
+
104
+ return {
105
+ nonce,
106
+ salt,
107
+ iteration
108
+ }
109
+ }
110
+
111
+ function xorBuffers (a, b) {
112
+ if (!Buffer.isBuffer(a)) a = Buffer.from(a)
113
+ if (!Buffer.isBuffer(b)) b = Buffer.from(b)
114
+ var res = []
115
+ if (a.length > b.length) {
116
+ for (var i = 0; i < b.length; i++) {
117
+ res.push(a[i] ^ b[i])
118
+ }
119
+ } else {
120
+ for (var j = 0; j < a.length; j++) {
121
+ res.push(a[j] ^ b[j])
122
+ }
123
+ }
124
+ return Buffer.from(res)
125
+ }
126
+
127
+ function createHMAC (key, msg) {
128
+ return crypto.createHmac('sha256', key).update(msg).digest()
129
+ }
130
+
131
+ function Hi (password, saltBytes, iterations) {
132
+ var ui1 = createHMAC(password, Buffer.concat([saltBytes, Buffer.from([0, 0, 0, 1])]))
133
+ var ui = ui1
134
+ for (var i = 0; i < iterations - 1; i++) {
135
+ ui1 = createHMAC(password, ui1)
136
+ ui = xorBuffers(ui, ui1)
137
+ }
138
+
139
+ return ui
140
+ }
141
+
142
+ module.exports = {
143
+ startSession,
144
+ continueSession,
145
+ finalizeSession
146
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pg",
3
- "version": "7.7.1",
3
+ "version": "7.9.0",
4
4
  "description": "PostgreSQL client - pure javascript & libpq with the same API",
5
5
  "keywords": [
6
6
  "database",
@@ -20,10 +20,10 @@
20
20
  "main": "./lib",
21
21
  "dependencies": {
22
22
  "buffer-writer": "2.0.0",
23
- "packet-reader": "0.3.1",
23
+ "packet-reader": "1.0.0",
24
24
  "pg-connection-string": "0.1.3",
25
25
  "pg-pool": "^2.0.4",
26
- "pg-types": "~1.12.1",
26
+ "pg-types": "~2.0.0",
27
27
  "pgpass": "1.x",
28
28
  "semver": "4.3.2"
29
29
  },
package/.travis.yml DELETED
@@ -1,41 +0,0 @@
1
- language: node_js
2
- sudo: false
3
- dist: trusty
4
- before_script:
5
- - node script/create-test-tables.js pg://postgres@127.0.0.1:5432/postgres
6
- env:
7
- - CC=clang CXX=clang++ npm_config_clang=1 PGUSER=postgres PGDATABASE=postgres
8
-
9
- matrix:
10
- include:
11
- - node_js: "lts/boron"
12
- addons:
13
- postgresql: "9.6"
14
- - node_js: "lts/argon"
15
- addons:
16
- postgresql: "9.6"
17
- - node_js: "10"
18
- addons:
19
- postgresql: "9.6"
20
- - node_js: "11"
21
- addons:
22
- postgresql: "9.6"
23
- - node_js: "lts/carbon"
24
- addons:
25
- postgresql: "9.1"
26
- dist: precise
27
- - node_js: "lts/carbon"
28
- addons:
29
- postgresql: "9.2"
30
- - node_js: "lts/carbon"
31
- addons:
32
- postgresql: "9.3"
33
- - node_js: "lts/carbon"
34
- addons:
35
- postgresql: "9.4"
36
- - node_js: "lts/carbon"
37
- addons:
38
- postgresql: "9.5"
39
- - node_js: "lts/carbon"
40
- addons:
41
- postgresql: "9.6"