Skip to content

Commit bb8745b

Browse files
authored
Fix SASL to bubble up errors, enable SASL tests in CI, and add informative empty SASL password message (#2901)
* Enable SASL tests in GitHub actions CI * Add SASL test to ensure that client password is a string * Fix SASL error handling to emit and bubble up errors * Add informative error when SASL password is empty string
1 parent f82f39c commit bb8745b

File tree

5 files changed

+94
-7
lines changed

5 files changed

+94
-7
lines changed

.github/workflows/ci.yml

+14-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ jobs:
1515
POSTGRES_USER: postgres
1616
POSTGRES_PASSWORD: postgres
1717
POSTGRES_DB: ci_db_test
18+
POSTGRES_HOST_AUTH_METHOD: 'md5'
1819
ports:
1920
- 5432:5432
2021
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
@@ -23,7 +24,19 @@ jobs:
2324
node: ['10', '12', '14', '16', '18']
2425
os: [ubuntu-latest, windows-latest, macos-latest]
2526
name: Node.js ${{ matrix.node }} (${{ matrix.os }})
27+
env:
28+
PGUSER: postgres
29+
PGHOST: localhost
30+
PGPASSWORD: postgres
31+
PGDATABASE: ci_db_test
32+
PGTESTNOSSL: 'true'
33+
SCRAM_TEST_PGUSER: scram_test
34+
SCRAM_TEST_PGPASSWORD: test4scram
2635
steps:
36+
- run: |
37+
psql \
38+
-c "SET password_encryption = 'scram-sha-256'" \
39+
-c "CREATE ROLE scram_test LOGIN PASSWORD 'test4scram'"
2740
- uses: actions/checkout@v3
2841
with:
2942
persist-credentials: false
@@ -34,4 +47,4 @@ jobs:
3447
cache: yarn
3548
- run: yarn install
3649
# TODO(bmc): get ssl tests working in ci
37-
- run: PGTESTNOSSL=true PGUSER=postgres PGPASSWORD=postgres PGDATABASE=ci_db_test yarn test
50+
- run: yarn test

packages/pg/lib/client.js

+18-6
Original file line numberDiff line numberDiff line change
@@ -247,19 +247,31 @@ class Client extends EventEmitter {
247247

248248
_handleAuthSASL(msg) {
249249
this._checkPgPass(() => {
250-
this.saslSession = sasl.startSession(msg.mechanisms)
251-
this.connection.sendSASLInitialResponseMessage(this.saslSession.mechanism, this.saslSession.response)
250+
try {
251+
this.saslSession = sasl.startSession(msg.mechanisms)
252+
this.connection.sendSASLInitialResponseMessage(this.saslSession.mechanism, this.saslSession.response)
253+
} catch (err) {
254+
this.connection.emit('error', err)
255+
}
252256
})
253257
}
254258

255259
_handleAuthSASLContinue(msg) {
256-
sasl.continueSession(this.saslSession, this.password, msg.data)
257-
this.connection.sendSCRAMClientFinalMessage(this.saslSession.response)
260+
try {
261+
sasl.continueSession(this.saslSession, this.password, msg.data)
262+
this.connection.sendSCRAMClientFinalMessage(this.saslSession.response)
263+
} catch (err) {
264+
this.connection.emit('error', err)
265+
}
258266
}
259267

260268
_handleAuthSASLFinal(msg) {
261-
sasl.finalizeSession(this.saslSession, msg.data)
262-
this.saslSession = null
269+
try {
270+
sasl.finalizeSession(this.saslSession, msg.data)
271+
this.saslSession = null
272+
} catch (err) {
273+
this.connection.emit('error', err)
274+
}
263275
}
264276

265277
_handleBackendKeyData(msg) {

packages/pg/lib/sasl.js

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ function continueSession(session, password, serverData) {
2323
if (typeof password !== 'string') {
2424
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string')
2525
}
26+
if (password === '') {
27+
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a non-empty string')
28+
}
2629
if (typeof serverData !== 'string') {
2730
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: serverData must be a string')
2831
}

packages/pg/test/integration/client/sasl-scram-tests.js

+21
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,24 @@ suite.testAsync('sasl/scram fails when password is wrong', async () => {
7373
)
7474
assert.ok(usingSasl, 'Should be using SASL for authentication')
7575
})
76+
77+
suite.testAsync('sasl/scram fails when password is empty', async () => {
78+
const client = new pg.Client({
79+
...config,
80+
// We use a password function here so the connection defaults do not
81+
// override the empty string value with one from process.env.PGPASSWORD
82+
password: () => '',
83+
})
84+
let usingSasl = false
85+
client.connection.once('authenticationSASL', () => {
86+
usingSasl = true
87+
})
88+
await assert.rejects(
89+
() => client.connect(),
90+
{
91+
message: 'SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a non-empty string',
92+
},
93+
'Error code should be for a password error'
94+
)
95+
assert.ok(usingSasl, 'Should be using SASL for authentication')
96+
})

packages/pg/test/unit/client/sasl-scram-tests.js

+38
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,44 @@ test('sasl/scram', function () {
8080
)
8181
})
8282

83+
test('fails when client password is not a string', function () {
84+
for(const badPasswordValue of [null, undefined, 123, new Date(), {}]) {
85+
assert.throws(
86+
function () {
87+
sasl.continueSession(
88+
{
89+
message: 'SASLInitialResponse',
90+
clientNonce: 'a',
91+
},
92+
badPasswordValue,
93+
'r=1,i=1'
94+
)
95+
},
96+
{
97+
message: 'SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string',
98+
}
99+
)
100+
}
101+
})
102+
103+
test('fails when client password is an empty string', function () {
104+
assert.throws(
105+
function () {
106+
sasl.continueSession(
107+
{
108+
message: 'SASLInitialResponse',
109+
clientNonce: 'a',
110+
},
111+
'',
112+
'r=1,i=1'
113+
)
114+
},
115+
{
116+
message: 'SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a non-empty string',
117+
}
118+
)
119+
})
120+
83121
test('fails when iteration is missing in server message', function () {
84122
assert.throws(
85123
function () {

0 commit comments

Comments
 (0)