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

allow user & database to be provided dynamically #882

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -994,9 +994,9 @@ const sql = postgres('postgres://username:password@host:port/database', {

Note that `max_lifetime = 60 * (30 + Math.random() * 30)` by default. This resolves to an interval between 30 and 60 minutes to optimize for the benefits of prepared statements **and** working nicely with Linux's OOM killer.

### Dynamic passwords
### Dynamic credentials

When clients need to use alternative authentication schemes such as access tokens or connections to databases with rotating passwords, provide either a synchronous or asynchronous function that will resolve the dynamic password value at connection time.
When clients need to use alternative authentication schemes such as access tokens or connections to databases with rotating usernames, database names or passwords, provide either a synchronous or asynchronous function that will resolve the dynamic value at connection time.

```js
const sql = postgres(url, {
Expand Down
26 changes: 14 additions & 12 deletions src/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,8 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
const {
ssl,
max,
user,
host,
port,
database,
parsers,
transform,
onnotice,
Expand All @@ -71,6 +69,9 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
const sent = Queue()
, id = uid++
, backend = { pid: null, secret: null }
, User = Resolve(options.user)
, Database = Resolve(options.database)
, Pass = Resolve(options.pass)
, idleTimer = timer(end, options.idle_timeout)
, lifeTimer = timer(end, options.max_lifetime)
, connectTimer = timer(connectTimedOut, options.connect_timeout)
Expand Down Expand Up @@ -353,7 +354,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
setTimeout(connect, closedDate ? closedDate + delay - performance.now() : 0)
}

function connected() {
async function connected() {
try {
statements = {}
needsTypes = options.fetch_types
Expand All @@ -362,7 +363,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
lifeTimer.start()
socket.on('data', data)
keep_alive && socket.setKeepAlive && socket.setKeepAlive(true, 1000 * keep_alive)
const s = StartupMessage()
const s = await StartupMessage()
write(s)
} catch (err) {
error(err)
Expand Down Expand Up @@ -665,10 +666,11 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
}

async function AuthenticationMD5Password(x) {
const [pass, user] = await Promise.all([Pass(), User()])
const payload = 'md5' + (
await md5(
md5(
Buffer.concat([
Buffer.from(await md5((await Pass()) + user)),
Buffer.from(md5(pass + user)),
x.subarray(9)
])
)
Expand Down Expand Up @@ -720,11 +722,10 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
socket.destroy()
}

function Pass() {
return Promise.resolve(typeof options.pass === 'function'
? options.pass()
: options.pass
)
function Resolve(opt) {
return async () => typeof opt === 'function'
? opt.apply(options, [options])
: opt
}

function NoData() {
Expand Down Expand Up @@ -967,7 +968,8 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
])
}

function StartupMessage() {
async function StartupMessage() {
const [user, database] = await Promise.all([User(), Database()])
return cancelMessage || b().inc(4).i16(3).z(2).str(
Object.entries(Object.assign({
user,
Expand Down
10 changes: 7 additions & 3 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ interface BaseOptions<T extends Record<string, postgres.PostgresType>> {
* Name of database to connect to
* @default process.env['PGDATABASE'] || options.user
*/
database: string;
database: Resolver<T, string>;
/**
* Username of database user
* @default process.env['PGUSERNAME'] || process.env['PGUSER'] || require('os').userInfo().username
*/
user: string;
user: Resolver<T, string>;
/**
* How to deal with ssl (can be a tls.connect option object)
* @default false
Expand Down Expand Up @@ -125,6 +125,10 @@ interface BaseOptions<T extends Record<string, postgres.PostgresType>> {
}


type Resolver<T extends Record<string, postgres.PostgresType>, U> =
| U
| ((this: postgres.Options<T>, options: postgres.Options<T>) => U)

declare const PRIVATE: unique symbol;

declare class NotAPromise {
Expand Down Expand Up @@ -356,7 +360,7 @@ declare namespace postgres {
* Password of database user
* @default process.env['PGPASSWORD']
*/
password?: string | (() => string | Promise<string>) | undefined;
password?: Resolver<T, string> | undefined;
/** Name of database to connect to (an alias for `database`) */
db?: Options<T>['database'] | undefined;
/** Username of database user (an alias for `user`) */
Expand Down