Skip to content

fix(pg-connection-string): get closer to libpq semantics for sslmode #2709

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

Merged
merged 9 commits into from
Apr 20, 2025
9 changes: 8 additions & 1 deletion packages/pg-connection-string/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,14 @@ Query parameters follow a `?` character, including the following special query p
* `host=<host>` - sets `host` property, overriding the URL's host
* `encoding=<encoding>` - sets the `client_encoding` property
* `ssl=1`, `ssl=true`, `ssl=0`, `ssl=false` - sets `ssl` to true or false, accordingly
* `sslmode=<sslmode>`
* `sslcompat=libpq` - use libpq semantics for `sslmode`
* `sslmode=<sslmode>` when `sslcompat=libpq`
* `sslmode=disable` - sets `ssl` to false
* `sslmode=prefer` - sets `ssl` to `{ rejectUnauthorized: false }`
* `sslmode=require` - sets `ssl` to `{ rejectUnauthorized: false }` unless `sslrootcert` is specified, in which case it behaves like `verify-ca`
* `sslmode=verify-ca` - sets `ssl` to `{ checkServerIdentity: no-op }` (verify CA, but not server identity). This verifies the presented certificate against the effective CA, i.e. the one specified in sslrootcert or the system CA if sslrootcert was not specified.
* `sslmode=verify-full` - sets `ssl` to `{}` (verify CA and server identity)
* `sslmode=<sslmode>` when `sslcompat` is not set
* `sslmode=disable` - sets `ssl` to false
* `sslmode=no-verify` - sets `ssl` to `{ rejectUnauthorized: false }`
* `sslmode=prefer`, `sslmode=require`, `sslmode=verify-ca`, `sslmode=verify-full` - sets `ssl` to true
Expand Down
55 changes: 42 additions & 13 deletions packages/pg-connection-string/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,20 +87,49 @@ function parse(str) {
config.ssl.ca = fs.readFileSync(config.sslrootcert).toString()
}

switch (config.sslmode) {
case 'disable': {
config.ssl = false
break
}
case 'prefer':
case 'require':
case 'verify-ca':
case 'verify-full': {
break
if (config.sslcompat === 'libpq') {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than using a value from the connection string, I think it'd be better to have parse take an option: parse(str: string, useLibpqCompat: boolean) — that way connection URLs don't have non-standard parameters?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then every application using this library has to implement this out-of-band, right? That's simply not going to happen, so from a user perspective this effectively wouldn't fix the issue of not supporting standard libpq connection strings.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be an application concern, not a user choice?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many libraries that wrap pg accept a DSN. Those libraries would have to be updated to have whatever function that accepts the DSN also accept this extra parameter.

switch (config.sslmode) {
case 'disable': {
config.ssl = false
break
}
case 'prefer': {
config.ssl.rejectUnauthorized = false
break
}
case 'require': {
if (config.sslrootcert) {
// If a root CA is specified, behavior of `sslmode=require` will be the same as that of `verify-ca`
config.ssl.checkServerIdentity = function () {}
} else {
config.ssl.rejectUnauthorized = false
}
break
}
case 'verify-ca': {
config.ssl.checkServerIdentity = function () {}
break
}
case 'verify-full': {
break
}
}
case 'no-verify': {
config.ssl.rejectUnauthorized = false
break
} else {
switch (config.sslmode) {
case 'disable': {
config.ssl = false
break
}
case 'prefer':
case 'require':
case 'verify-ca':
case 'verify-full': {
break
}
case 'no-verify': {
config.ssl.rejectUnauthorized = false
break
}
}
}

Expand Down
55 changes: 46 additions & 9 deletions packages/pg-connection-string/test/parse.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'

var chai = require('chai')
var expect = chai.expect
chai.should()

var parse = require('../').parse
Expand Down Expand Up @@ -255,22 +256,50 @@ describe('parse', function () {
subject.ssl.should.eql(false)
})

it('configuration parameter sslmode=prefer', function () {
var connectionString = 'pg:///?sslmode=prefer'
it('configuration parameter sslmode=prefer with libpq compatibility', function () {
var connectionString = 'pg:///?sslmode=prefer&sslcompat=libpq'
var subject = parse(connectionString)
subject.ssl.should.eql({})
subject.ssl.should.eql({
rejectUnauthorized: false,
})
})

it('configuration parameter sslmode=require', function () {
var connectionString = 'pg:///?sslmode=require'
it('configuration parameter sslmode=require with libpq compatibility', function () {
var connectionString = 'pg:///?sslmode=require&sslcompat=libpq'
var subject = parse(connectionString)
subject.ssl.should.eql({})
subject.ssl.should.eql({
rejectUnauthorized: false,
})
})

it('configuration parameter sslmode=verify-ca', function () {
var connectionString = 'pg:///?sslmode=verify-ca'
it('configuration parameter sslmode=verify-ca with libpq compatibility', function () {
var connectionString = 'pg:///?sslmode=verify-ca&sslcompat=libpq'
var subject = parse(connectionString)
subject.ssl.should.eql({})
subject.ssl.should.have.property('checkServerIdentity').that.is.a('function')
expect(subject.ssl.checkServerIdentity()).to.be.undefined
})

it('configuration parameter sslmode=prefer with libpq compatibility', function () {
var connectionString = 'pg:///?sslmode=prefer&sslcompat=libpq'
var subject = parse(connectionString)
subject.ssl.should.eql({
rejectUnauthorized: false,
})
})

it('configuration parameter sslmode=require with libpq compatibility', function () {
var connectionString = 'pg:///?sslmode=require&sslcompat=libpq'
var subject = parse(connectionString)
subject.ssl.should.eql({
rejectUnauthorized: false,
})
})

it('configuration parameter sslmode=verify-ca with libpq compatibility', function () {
var connectionString = 'pg:///?sslmode=verify-ca&sslcompat=libpq'
var subject = parse(connectionString)
subject.ssl.should.have.property('checkServerIdentity').that.is.a('function')
expect(subject.ssl.checkServerIdentity()).be.undefined
})

it('configuration parameter sslmode=verify-full', function () {
Expand All @@ -287,6 +316,14 @@ describe('parse', function () {
})
})

it('configuration parameter ssl=true and sslmode=require still work with sslrootcert=/path/to/ca with libpq compatibility', function () {
var connectionString = 'pg:///?ssl=true&sslrootcert=' + __dirname + '/example.ca&sslmode=require&sslcompat=libpq'
var subject = parse(connectionString)
subject.ssl.should.have.property('ca', 'example ca\n')
subject.ssl.should.have.property('checkServerIdentity').that.is.a('function')
expect(subject.ssl.checkServerIdentity()).be.undefined
})

it('allow other params like max, ...', function () {
var subject = parse('pg://myhost/db?max=18&min=4')
subject.max.should.equal('18')
Expand Down
Loading