pg 7.8.1 → 7.11.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 +10 -0
- package/README.md +1 -1
- package/SPONSORS.md +1 -0
- package/lib/client.js +39 -3
- package/lib/connection-parameters.js +19 -2
- package/lib/connection.js +66 -19
- package/lib/defaults.js +7 -1
- package/lib/native/query.js +5 -1
- package/lib/query.js +10 -1
- package/lib/result.js +3 -4
- package/lib/sasl.js +146 -0
- package/lib/utils.js +17 -4
- package/package.json +1 -1
- package/.travis.yml +0 -38
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ 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.11.0
|
|
8
|
+
- Add support for [connection_timeout](https://github.com/brianc/node-postgres/pull/1847/files#diff-5391bde944956870128be1136e7bc176R63) and [keepalives_idle](https://github.com/brianc/node-postgres/pull/1847).
|
|
9
|
+
|
|
10
|
+
### 7.10.0
|
|
11
|
+
- Add support for [per-query types](https://github.com/brianc/node-postgres/pull/1825).
|
|
12
|
+
|
|
13
|
+
### 7.9.0
|
|
14
|
+
|
|
15
|
+
- Add support for [sasl/scram authentication](https://github.com/brianc/node-postgres/pull/1835).
|
|
16
|
+
|
|
7
17
|
### 7.8.0
|
|
8
18
|
|
|
9
19
|
- Add support for passing [secureOptions](https://github.com/brianc/node-postgres/pull/1804) SSL config.
|
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-
|
|
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
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
|
|
|
@@ -43,6 +44,7 @@ var Client = function (config) {
|
|
|
43
44
|
stream: c.stream,
|
|
44
45
|
ssl: this.connectionParameters.ssl,
|
|
45
46
|
keepAlive: c.keepAlive || false,
|
|
47
|
+
keepAliveInitialDelayMillis: c.keepAliveInitialDelayMillis || 0,
|
|
46
48
|
encoding: this.connectionParameters.client_encoding || 'utf8'
|
|
47
49
|
})
|
|
48
50
|
this.queryQueue = []
|
|
@@ -50,6 +52,7 @@ var Client = function (config) {
|
|
|
50
52
|
this.processID = null
|
|
51
53
|
this.secretKey = null
|
|
52
54
|
this.ssl = this.connectionParameters.ssl || false
|
|
55
|
+
this._connectionTimeoutMillis = c.connectionTimeoutMillis || 0
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
util.inherits(Client, EventEmitter)
|
|
@@ -82,6 +85,14 @@ Client.prototype._connect = function (callback) {
|
|
|
82
85
|
}
|
|
83
86
|
this._connecting = true
|
|
84
87
|
|
|
88
|
+
var connectionTimeoutHandle
|
|
89
|
+
if (this._connectionTimeoutMillis > 0) {
|
|
90
|
+
connectionTimeoutHandle = setTimeout(() => {
|
|
91
|
+
con._ending = true
|
|
92
|
+
con.stream.destroy(new Error('timeout expired'))
|
|
93
|
+
}, this._connectionTimeoutMillis)
|
|
94
|
+
}
|
|
95
|
+
|
|
85
96
|
if (this.host && this.host.indexOf('/') === 0) {
|
|
86
97
|
con.connect(this.host + '/.s.PGSQL.' + this.port)
|
|
87
98
|
} else {
|
|
@@ -126,6 +137,28 @@ Client.prototype._connect = function (callback) {
|
|
|
126
137
|
con.password(utils.postgresMd5PasswordHash(self.user, self.password, msg.salt))
|
|
127
138
|
}))
|
|
128
139
|
|
|
140
|
+
// password request handling (SASL)
|
|
141
|
+
var saslSession
|
|
142
|
+
con.on('authenticationSASL', checkPgPass(function (msg) {
|
|
143
|
+
saslSession = sasl.startSession(msg.mechanisms)
|
|
144
|
+
|
|
145
|
+
con.sendSASLInitialResponseMessage(saslSession.mechanism, saslSession.response)
|
|
146
|
+
}))
|
|
147
|
+
|
|
148
|
+
// password request handling (SASL)
|
|
149
|
+
con.on('authenticationSASLContinue', function (msg) {
|
|
150
|
+
sasl.continueSession(saslSession, self.password, msg.data)
|
|
151
|
+
|
|
152
|
+
con.sendSCRAMClientFinalMessage(saslSession.response)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// password request handling (SASL)
|
|
156
|
+
con.on('authenticationSASLFinal', function (msg) {
|
|
157
|
+
sasl.finalizeSession(saslSession, msg.data)
|
|
158
|
+
|
|
159
|
+
saslSession = null
|
|
160
|
+
})
|
|
161
|
+
|
|
129
162
|
con.once('backendKeyData', function (msg) {
|
|
130
163
|
self.processID = msg.processID
|
|
131
164
|
self.secretKey = msg.secretKey
|
|
@@ -136,6 +169,7 @@ Client.prototype._connect = function (callback) {
|
|
|
136
169
|
return
|
|
137
170
|
}
|
|
138
171
|
this._connectionError = true
|
|
172
|
+
clearTimeout(connectionTimeoutHandle)
|
|
139
173
|
if (callback) {
|
|
140
174
|
return callback(err)
|
|
141
175
|
}
|
|
@@ -173,6 +207,7 @@ Client.prototype._connect = function (callback) {
|
|
|
173
207
|
con.removeListener('errorMessage', connectingErrorHandler)
|
|
174
208
|
con.on('error', connectedErrorHandler)
|
|
175
209
|
con.on('errorMessage', connectedErrorMessageHandler)
|
|
210
|
+
clearTimeout(connectionTimeoutHandle)
|
|
176
211
|
|
|
177
212
|
// process possible callback argument to Client#connect
|
|
178
213
|
if (callback) {
|
|
@@ -276,7 +311,7 @@ Client.prototype._attachListeners = function (con) {
|
|
|
276
311
|
// it again on the same client
|
|
277
312
|
con.on('parseComplete', function (msg) {
|
|
278
313
|
if (self.activeQuery.name) {
|
|
279
|
-
con.parsedStatements[self.activeQuery.name] =
|
|
314
|
+
con.parsedStatements[self.activeQuery.name] = self.activeQuery.text
|
|
280
315
|
}
|
|
281
316
|
})
|
|
282
317
|
|
|
@@ -455,8 +490,9 @@ Client.prototype.query = function (config, values, callback) {
|
|
|
455
490
|
if (this.binary && !query.binary) {
|
|
456
491
|
query.binary = true
|
|
457
492
|
}
|
|
458
|
-
|
|
459
|
-
|
|
493
|
+
|
|
494
|
+
if (query._result && !query._result._types) {
|
|
495
|
+
query._result._types = this._types
|
|
460
496
|
}
|
|
461
497
|
|
|
462
498
|
if (!this._queryable) {
|
|
@@ -66,6 +66,22 @@ var ConnectionParameters = function (config) {
|
|
|
66
66
|
this.fallback_application_name = val('fallback_application_name', config, false)
|
|
67
67
|
this.statement_timeout = val('statement_timeout', config, false)
|
|
68
68
|
this.query_timeout = val('query_timeout', config, false)
|
|
69
|
+
|
|
70
|
+
if (config.connectionTimeoutMillis === undefined) {
|
|
71
|
+
this.connect_timeout = process.env.PGCONNECT_TIMEOUT || 0
|
|
72
|
+
} else {
|
|
73
|
+
this.connect_timeout = Math.floor(config.connectionTimeoutMillis / 1000)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (config.keepAlive === false) {
|
|
77
|
+
this.keepalives = 0
|
|
78
|
+
} else if (config.keepAlive === true) {
|
|
79
|
+
this.keepalives = 1
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (typeof config.keepAliveInitialDelayMillis === 'number') {
|
|
83
|
+
this.keepalives_idle = Math.floor(config.keepAliveInitialDelayMillis / 1000)
|
|
84
|
+
}
|
|
69
85
|
}
|
|
70
86
|
|
|
71
87
|
// Convert arg to a string, surround in single quotes, and escape single quotes and backslashes
|
|
@@ -75,7 +91,7 @@ var quoteParamValue = function (value) {
|
|
|
75
91
|
|
|
76
92
|
var add = function (params, config, paramName) {
|
|
77
93
|
var value = config[paramName]
|
|
78
|
-
if (value) {
|
|
94
|
+
if (value !== undefined && value !== null) {
|
|
79
95
|
params.push(paramName + '=' + quoteParamValue(value))
|
|
80
96
|
}
|
|
81
97
|
}
|
|
@@ -87,8 +103,9 @@ ConnectionParameters.prototype.getLibpqConnectionString = function (cb) {
|
|
|
87
103
|
add(params, this, 'port')
|
|
88
104
|
add(params, this, 'application_name')
|
|
89
105
|
add(params, this, 'fallback_application_name')
|
|
106
|
+
add(params, this, 'connect_timeout')
|
|
90
107
|
|
|
91
|
-
var ssl = typeof this.ssl === 'object' ? this.ssl : { sslmode: this.ssl }
|
|
108
|
+
var ssl = typeof this.ssl === 'object' ? this.ssl : this.ssl ? { sslmode: this.ssl } : {}
|
|
92
109
|
add(params, ssl, 'sslmode')
|
|
93
110
|
add(params, ssl, 'sslca')
|
|
94
111
|
add(params, ssl, 'sslkey')
|
package/lib/connection.js
CHANGED
|
@@ -21,6 +21,7 @@ var Connection = function (config) {
|
|
|
21
21
|
config = config || {}
|
|
22
22
|
this.stream = config.stream || new net.Socket()
|
|
23
23
|
this._keepAlive = config.keepAlive
|
|
24
|
+
this._keepAliveInitialDelayMillis = config.keepAliveInitialDelayMillis
|
|
24
25
|
this.lastBuffer = false
|
|
25
26
|
this.lastOffset = 0
|
|
26
27
|
this.buffer = null
|
|
@@ -47,17 +48,17 @@ var Connection = function (config) {
|
|
|
47
48
|
util.inherits(Connection, EventEmitter)
|
|
48
49
|
|
|
49
50
|
Connection.prototype.connect = function (port, host) {
|
|
51
|
+
var self = this
|
|
52
|
+
|
|
50
53
|
if (this.stream.readyState === 'closed') {
|
|
51
54
|
this.stream.connect(port, host)
|
|
52
55
|
} else if (this.stream.readyState === 'open') {
|
|
53
56
|
this.emit('connect')
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
var self = this
|
|
57
|
-
|
|
58
59
|
this.stream.on('connect', function () {
|
|
59
60
|
if (self._keepAlive) {
|
|
60
|
-
self.stream.setKeepAlive(true)
|
|
61
|
+
self.stream.setKeepAlive(true, self._keepAliveInitialDelayMillis)
|
|
61
62
|
}
|
|
62
63
|
self.emit('connect')
|
|
63
64
|
})
|
|
@@ -191,6 +192,24 @@ Connection.prototype.password = function (password) {
|
|
|
191
192
|
this._send(0x70, this.writer.addCString(password))
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
Connection.prototype.sendSASLInitialResponseMessage = function (mechanism, initialResponse) {
|
|
196
|
+
// 0x70 = 'p'
|
|
197
|
+
this.writer
|
|
198
|
+
.addCString(mechanism)
|
|
199
|
+
.addInt32(Buffer.byteLength(initialResponse))
|
|
200
|
+
.addString(initialResponse)
|
|
201
|
+
|
|
202
|
+
this._send(0x70)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
Connection.prototype.sendSCRAMClientFinalMessage = function (additionalData) {
|
|
206
|
+
// 0x70 = 'p'
|
|
207
|
+
this.writer
|
|
208
|
+
.addString(additionalData)
|
|
209
|
+
|
|
210
|
+
this._send(0x70)
|
|
211
|
+
}
|
|
212
|
+
|
|
194
213
|
Connection.prototype._send = function (code, more) {
|
|
195
214
|
if (!this.stream.writable) {
|
|
196
215
|
return false
|
|
@@ -421,25 +440,53 @@ Connection.prototype.parseMessage = function (buffer) {
|
|
|
421
440
|
}
|
|
422
441
|
|
|
423
442
|
Connection.prototype.parseR = function (buffer, length) {
|
|
424
|
-
var code =
|
|
443
|
+
var code = this.parseInt32(buffer)
|
|
444
|
+
|
|
425
445
|
var msg = new Message('authenticationOk', length)
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
msg
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
msg.
|
|
438
|
-
|
|
439
|
-
|
|
446
|
+
|
|
447
|
+
switch (code) {
|
|
448
|
+
case 0: // AuthenticationOk
|
|
449
|
+
return msg
|
|
450
|
+
case 3: // AuthenticationCleartextPassword
|
|
451
|
+
if (msg.length === 8) {
|
|
452
|
+
msg.name = 'authenticationCleartextPassword'
|
|
453
|
+
return msg
|
|
454
|
+
}
|
|
455
|
+
break
|
|
456
|
+
case 5: // AuthenticationMD5Password
|
|
457
|
+
if (msg.length === 12) {
|
|
458
|
+
msg.name = 'authenticationMD5Password'
|
|
459
|
+
msg.salt = Buffer.alloc(4)
|
|
460
|
+
buffer.copy(msg.salt, 0, this.offset, this.offset + 4)
|
|
461
|
+
this.offset += 4
|
|
462
|
+
return msg
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
break
|
|
466
|
+
case 10: // AuthenticationSASL
|
|
467
|
+
msg.name = 'authenticationSASL'
|
|
468
|
+
msg.mechanisms = []
|
|
469
|
+
do {
|
|
470
|
+
var mechanism = this.parseCString(buffer)
|
|
471
|
+
|
|
472
|
+
if (mechanism) {
|
|
473
|
+
msg.mechanisms.push(mechanism)
|
|
474
|
+
}
|
|
475
|
+
} while (mechanism)
|
|
476
|
+
|
|
477
|
+
return msg
|
|
478
|
+
case 11: // AuthenticationSASLContinue
|
|
479
|
+
msg.name = 'authenticationSASLContinue'
|
|
480
|
+
msg.data = this.readString(buffer, length - 4)
|
|
481
|
+
|
|
482
|
+
return msg
|
|
483
|
+
case 12: // AuthenticationSASLFinal
|
|
484
|
+
msg.name = 'authenticationSASLFinal'
|
|
485
|
+
msg.data = this.readString(buffer, length - 4)
|
|
486
|
+
|
|
440
487
|
return msg
|
|
441
|
-
}
|
|
442
488
|
}
|
|
489
|
+
|
|
443
490
|
throw new Error('Unknown authenticationOk message type' + util.inspect(msg))
|
|
444
491
|
}
|
|
445
492
|
|
package/lib/defaults.js
CHANGED
|
@@ -58,7 +58,13 @@ module.exports = {
|
|
|
58
58
|
statement_timeout: false,
|
|
59
59
|
|
|
60
60
|
// max miliseconds to wait for query to complete (client side)
|
|
61
|
-
query_timeout: false
|
|
61
|
+
query_timeout: false,
|
|
62
|
+
|
|
63
|
+
connect_timeout: 0,
|
|
64
|
+
|
|
65
|
+
keepalives: 1,
|
|
66
|
+
|
|
67
|
+
keepalives_idle: 0
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
var pgTypes = require('pg-types')
|
package/lib/native/query.js
CHANGED
|
@@ -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] =
|
|
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
|
}
|
|
@@ -190,7 +194,12 @@ Query.prototype.prepare = function (connection) {
|
|
|
190
194
|
}
|
|
191
195
|
|
|
192
196
|
if (self.values) {
|
|
193
|
-
|
|
197
|
+
try {
|
|
198
|
+
self.values = self.values.map(utils.prepareValue)
|
|
199
|
+
} catch (err) {
|
|
200
|
+
this.handleError(err, connection)
|
|
201
|
+
return
|
|
202
|
+
}
|
|
194
203
|
}
|
|
195
204
|
|
|
196
205
|
// http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY
|
package/lib/result.js
CHANGED
|
@@ -12,13 +12,14 @@ var types = require('pg-types')
|
|
|
12
12
|
// result object returned from query
|
|
13
13
|
// in the 'end' event and also
|
|
14
14
|
// passed as second argument to provided callback
|
|
15
|
-
var Result = function (rowMode) {
|
|
15
|
+
var Result = function (rowMode, types) {
|
|
16
16
|
this.command = null
|
|
17
17
|
this.rowCount = null
|
|
18
18
|
this.oid = null
|
|
19
19
|
this.rows = []
|
|
20
20
|
this.fields = []
|
|
21
21
|
this._parsers = []
|
|
22
|
+
this._types = types
|
|
22
23
|
this.RowCtor = null
|
|
23
24
|
this.rowAsArray = rowMode === 'array'
|
|
24
25
|
if (this.rowAsArray) {
|
|
@@ -94,11 +95,9 @@ Result.prototype.addFields = function (fieldDescriptions) {
|
|
|
94
95
|
for (var i = 0; i < fieldDescriptions.length; i++) {
|
|
95
96
|
var desc = fieldDescriptions[i]
|
|
96
97
|
this.fields.push(desc)
|
|
97
|
-
var parser = this.
|
|
98
|
+
var parser = (this._types || types).getTypeParser(desc.dataTypeID, desc.format || 'text')
|
|
98
99
|
this._parsers.push(parser)
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
Result.prototype._getTypeParser = types.getTypeParser
|
|
103
|
-
|
|
104
103
|
module.exports = Result
|
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/lib/utils.js
CHANGED
|
@@ -97,7 +97,12 @@ function pad (number, digits) {
|
|
|
97
97
|
|
|
98
98
|
function dateToString (date) {
|
|
99
99
|
var offset = -date.getTimezoneOffset()
|
|
100
|
-
|
|
100
|
+
|
|
101
|
+
var year = date.getFullYear()
|
|
102
|
+
var isBCYear = year < 1
|
|
103
|
+
if (isBCYear) year = Math.abs(year) + 1 // negative years are 1 off their BC representation
|
|
104
|
+
|
|
105
|
+
var ret = pad(year, 4) + '-' +
|
|
101
106
|
pad(date.getMonth() + 1, 2) + '-' +
|
|
102
107
|
pad(date.getDate(), 2) + 'T' +
|
|
103
108
|
pad(date.getHours(), 2) + ':' +
|
|
@@ -110,11 +115,17 @@ function dateToString (date) {
|
|
|
110
115
|
offset *= -1
|
|
111
116
|
} else { ret += '+' }
|
|
112
117
|
|
|
113
|
-
|
|
118
|
+
ret += pad(Math.floor(offset / 60), 2) + ':' + pad(offset % 60, 2)
|
|
119
|
+
if (isBCYear) ret += ' BC'
|
|
120
|
+
return ret
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
function dateToStringUTC (date) {
|
|
117
|
-
var
|
|
124
|
+
var year = date.getUTCFullYear()
|
|
125
|
+
var isBCYear = year < 1
|
|
126
|
+
if (isBCYear) year = Math.abs(year) + 1 // negative years are 1 off their BC representation
|
|
127
|
+
|
|
128
|
+
var ret = pad(year, 4) + '-' +
|
|
118
129
|
pad(date.getUTCMonth() + 1, 2) + '-' +
|
|
119
130
|
pad(date.getUTCDate(), 2) + 'T' +
|
|
120
131
|
pad(date.getUTCHours(), 2) + ':' +
|
|
@@ -122,7 +133,9 @@ function dateToStringUTC (date) {
|
|
|
122
133
|
pad(date.getUTCSeconds(), 2) + '.' +
|
|
123
134
|
pad(date.getUTCMilliseconds(), 3)
|
|
124
135
|
|
|
125
|
-
|
|
136
|
+
ret += '+00:00'
|
|
137
|
+
if (isBCYear) ret += ' BC'
|
|
138
|
+
return ret
|
|
126
139
|
}
|
|
127
140
|
|
|
128
141
|
function normalizeQueryConfig (config, values, callback) {
|
package/package.json
CHANGED
package/.travis.yml
DELETED
|
@@ -1,38 +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: "lts/carbon"
|
|
21
|
-
addons:
|
|
22
|
-
postgresql: "9.1"
|
|
23
|
-
dist: precise
|
|
24
|
-
- node_js: "lts/carbon"
|
|
25
|
-
addons:
|
|
26
|
-
postgresql: "9.2"
|
|
27
|
-
- node_js: "lts/carbon"
|
|
28
|
-
addons:
|
|
29
|
-
postgresql: "9.3"
|
|
30
|
-
- node_js: "lts/carbon"
|
|
31
|
-
addons:
|
|
32
|
-
postgresql: "9.4"
|
|
33
|
-
- node_js: "lts/carbon"
|
|
34
|
-
addons:
|
|
35
|
-
postgresql: "9.5"
|
|
36
|
-
- node_js: "lts/carbon"
|
|
37
|
-
addons:
|
|
38
|
-
postgresql: "9.6"
|