Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix SASL to bubble up errors, enable SASL tests in CI, and add informative empty SASL password message #2901

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
24 changes: 18 additions & 6 deletions packages/pg/lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions packages/pg/lib/sasl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}
Expand Down
21 changes: 21 additions & 0 deletions packages/pg/test/integration/client/sasl-scram-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
38 changes: 38 additions & 0 deletions packages/pg/test/unit/client/sasl-scram-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down