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
This commit is contained in:
Sehrope Sarkuni 2023-01-23 13:03:51 -05:00 committed by GitHub
parent f82f39c20c
commit bb8745b215
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 7 deletions

View File

@ -15,6 +15,7 @@ jobs:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: ci_db_test
POSTGRES_HOST_AUTH_METHOD: 'md5'
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
@ -23,7 +24,19 @@ jobs:
node: ['10', '12', '14', '16', '18']
os: [ubuntu-latest, windows-latest, macos-latest]
name: Node.js ${{ matrix.node }} (${{ matrix.os }})
env:
PGUSER: postgres
PGHOST: localhost
PGPASSWORD: postgres
PGDATABASE: ci_db_test
PGTESTNOSSL: 'true'
SCRAM_TEST_PGUSER: scram_test
SCRAM_TEST_PGPASSWORD: test4scram
steps:
- run: |
psql \
-c "SET password_encryption = 'scram-sha-256'" \
-c "CREATE ROLE scram_test LOGIN PASSWORD 'test4scram'"
- uses: actions/checkout@v3
with:
persist-credentials: false
@ -34,4 +47,4 @@ jobs:
cache: yarn
- run: yarn install
# TODO(bmc): get ssl tests working in ci
- run: PGTESTNOSSL=true PGUSER=postgres PGPASSWORD=postgres PGDATABASE=ci_db_test yarn test
- run: yarn test

View File

@ -247,19 +247,31 @@ class Client extends EventEmitter {
_handleAuthSASL(msg) {
this._checkPgPass(() => {
this.saslSession = sasl.startSession(msg.mechanisms)
this.connection.sendSASLInitialResponseMessage(this.saslSession.mechanism, this.saslSession.response)
try {
this.saslSession = sasl.startSession(msg.mechanisms)
this.connection.sendSASLInitialResponseMessage(this.saslSession.mechanism, this.saslSession.response)
} catch (err) {
this.connection.emit('error', err)
}
})
}
_handleAuthSASLContinue(msg) {
sasl.continueSession(this.saslSession, this.password, msg.data)
this.connection.sendSCRAMClientFinalMessage(this.saslSession.response)
try {
sasl.continueSession(this.saslSession, this.password, msg.data)
this.connection.sendSCRAMClientFinalMessage(this.saslSession.response)
} catch (err) {
this.connection.emit('error', err)
}
}
_handleAuthSASLFinal(msg) {
sasl.finalizeSession(this.saslSession, msg.data)
this.saslSession = null
try {
sasl.finalizeSession(this.saslSession, msg.data)
this.saslSession = null
} catch (err) {
this.connection.emit('error', err)
}
}
_handleBackendKeyData(msg) {

View File

@ -23,6 +23,9 @@ function continueSession(session, password, serverData) {
if (typeof password !== 'string') {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string')
}
if (password === '') {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a non-empty string')
}
if (typeof serverData !== 'string') {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: serverData must be a string')
}

View File

@ -73,3 +73,24 @@ suite.testAsync('sasl/scram fails when password is wrong', async () => {
)
assert.ok(usingSasl, 'Should be using SASL for authentication')
})
suite.testAsync('sasl/scram fails when password is empty', async () => {
const client = new pg.Client({
...config,
// We use a password function here so the connection defaults do not
// override the empty string value with one from process.env.PGPASSWORD
password: () => '',
})
let usingSasl = false
client.connection.once('authenticationSASL', () => {
usingSasl = true
})
await assert.rejects(
() => client.connect(),
{
message: 'SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a non-empty string',
},
'Error code should be for a password error'
)
assert.ok(usingSasl, 'Should be using SASL for authentication')
})

View File

@ -80,6 +80,44 @@ test('sasl/scram', function () {
)
})
test('fails when client password is not a string', function () {
for(const badPasswordValue of [null, undefined, 123, new Date(), {}]) {
assert.throws(
function () {
sasl.continueSession(
{
message: 'SASLInitialResponse',
clientNonce: 'a',
},
badPasswordValue,
'r=1,i=1'
)
},
{
message: 'SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string',
}
)
}
})
test('fails when client password is an empty string', function () {
assert.throws(
function () {
sasl.continueSession(
{
message: 'SASLInitialResponse',
clientNonce: 'a',
},
'',
'r=1,i=1'
)
},
{
message: 'SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a non-empty string',
}
)
})
test('fails when iteration is missing in server message', function () {
assert.throws(
function () {