Skip to content

Commit 0894a3c

Browse files
sehropebrianc
authored andcommitted
feat: Add dynamic retrieval for client password (#1926)
* feat: Add dynamic retrieval for client password Adds option to specify a function for a client password. When the client is connected, if the value of password is a function then it is invoked to get the password to use for that connection. The function must return either a string or a Promise that resolves to a string. If the function throws or rejects with an error then it will be bubbled up to the client. * test: Add testAsync() helper to Suite Add testAsync() helper function to Suite to simplify running tests that return a Promise. The test action is executed and if a syncronous error is thrown then it is immediately considered failed. If the Promise resolves successfully then the test is considered successful. If the Promise rejects with an Error then the test is considered failed. * test: Add tests for dynamic password * test: Simplify testAsync error handling * fix: Clean up dynamic password error handling and misc style * test: Remove extra semicolons * test: Change testAsync(...) calls to use arrow functions * fix: Wrap self.password() invocation in an arrow function * test: Add a comment to testAsync(...)
1 parent 0acaf9d commit 0894a3c

File tree

3 files changed

+145
-1
lines changed

3 files changed

+145
-1
lines changed

lib/client.js

+18-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,24 @@ Client.prototype._connect = function (callback) {
114114

115115
function checkPgPass (cb) {
116116
return function (msg) {
117-
if (self.password !== null) {
117+
if (typeof self.password === 'function') {
118+
self._Promise.resolve()
119+
.then(() => self.password())
120+
.then(pass => {
121+
if (pass !== undefined) {
122+
if (typeof pass !== 'string') {
123+
con.emit('error', new TypeError('Password must be a string'))
124+
return
125+
}
126+
self.connectionParameters.password = self.password = pass
127+
} else {
128+
self.connectionParameters.password = self.password = null
129+
}
130+
cb(msg)
131+
}).catch(err => {
132+
con.emit('error', err)
133+
})
134+
} else if (self.password !== null) {
118135
cb(msg)
119136
} else {
120137
pgPass(self.connectionParameters, function (pass) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
'use strict'
2+
const assert = require('assert')
3+
const helper = require('./../test-helper')
4+
const suite = new helper.Suite()
5+
const pg = require('../../../lib/index')
6+
const Client = pg.Client;
7+
8+
const password = process.env.PGPASSWORD || null
9+
const sleep = millis => new Promise(resolve => setTimeout(resolve, millis))
10+
11+
suite.testAsync('Get password from a sync function', () => {
12+
let wasCalled = false
13+
function getPassword() {
14+
wasCalled = true
15+
return password
16+
}
17+
const client = new Client({
18+
password: getPassword,
19+
})
20+
return client.connect()
21+
.then(() => {
22+
assert.ok(wasCalled, 'Our password function should have been called')
23+
return client.end()
24+
})
25+
})
26+
27+
suite.testAsync('Throw error from a sync function', () => {
28+
let wasCalled = false
29+
const myError = new Error('Oops!')
30+
function getPassword() {
31+
wasCalled = true
32+
throw myError
33+
}
34+
const client = new Client({
35+
password: getPassword,
36+
})
37+
let wasThrown = false
38+
return client.connect()
39+
.catch(err => {
40+
assert.equal(err, myError, 'Our sync error should have been thrown')
41+
wasThrown = true
42+
})
43+
.then(() => {
44+
assert.ok(wasCalled, 'Our password function should have been called')
45+
assert.ok(wasThrown, 'Our error should have been thrown')
46+
return client.end()
47+
})
48+
})
49+
50+
suite.testAsync('Get password from a function asynchronously', () => {
51+
let wasCalled = false
52+
function getPassword() {
53+
wasCalled = true
54+
return sleep(100).then(() => password)
55+
}
56+
const client = new Client({
57+
password: getPassword,
58+
})
59+
return client.connect()
60+
.then(() => {
61+
assert.ok(wasCalled, 'Our password function should have been called')
62+
return client.end()
63+
})
64+
})
65+
66+
suite.testAsync('Throw error from an async function', () => {
67+
let wasCalled = false
68+
const myError = new Error('Oops!')
69+
function getPassword() {
70+
wasCalled = true
71+
return sleep(100).then(() => {
72+
throw myError
73+
})
74+
}
75+
const client = new Client({
76+
password: getPassword,
77+
})
78+
let wasThrown = false
79+
return client.connect()
80+
.catch(err => {
81+
assert.equal(err, myError, 'Our async error should have been thrown')
82+
wasThrown = true
83+
})
84+
.then(() => {
85+
assert.ok(wasCalled, 'Our password function should have been called')
86+
assert.ok(wasThrown, 'Our error should have been thrown')
87+
return client.end()
88+
})
89+
})
90+
91+
suite.testAsync('Password function must return a string', () => {
92+
let wasCalled = false
93+
function getPassword() {
94+
wasCalled = true
95+
// Return a password that is not a string
96+
return 12345
97+
}
98+
const client = new Client({
99+
password: getPassword,
100+
})
101+
let wasThrown = false
102+
return client.connect()
103+
.catch(err => {
104+
assert.ok(err instanceof TypeError, 'A TypeError should have been thrown')
105+
assert.equal(err.message, 'Password must be a string')
106+
wasThrown = true
107+
})
108+
.then(() => {
109+
assert.ok(wasCalled, 'Our password function should have been called')
110+
assert.ok(wasThrown, 'Our error should have been thrown')
111+
return client.end()
112+
})
113+
})

test/suite.js

+14
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,20 @@ class Suite {
7272
const test = new Test(name, cb)
7373
this._queue.push(test)
7474
}
75+
76+
/**
77+
* Run an async test that can return a Promise. If the Promise resolves
78+
* successfully then the test will pass. If the Promise rejects with an
79+
* error then the test will be considered failed.
80+
*/
81+
testAsync (name, action) {
82+
const test = new Test(name, cb => {
83+
Promise.resolve()
84+
.then(action)
85+
.then(() => cb(null), cb)
86+
})
87+
this._queue.push(test)
88+
}
7589
}
7690

7791
process.on('unhandledRejection', (e) => {

0 commit comments

Comments
 (0)