Skip to content
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
43 changes: 0 additions & 43 deletions packages/pg/lib/crypto/utils-legacy.js

This file was deleted.

89 changes: 0 additions & 89 deletions packages/pg/lib/crypto/utils-webcrypto.js

This file was deleted.

96 changes: 88 additions & 8 deletions packages/pg/lib/crypto/utils.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,89 @@
'use strict'

const useLegacyCrypto = parseInt(process.versions && process.versions.node && process.versions.node.split('.')[0]) < 15
if (useLegacyCrypto) {
// We are on an old version of Node.js that requires legacy crypto utilities.
module.exports = require('./utils-legacy')
} else {
module.exports = require('./utils-webcrypto')
const nodeCrypto = require('crypto')

module.exports = {
postgresMd5PasswordHash,
randomBytes,
deriveKey,
sha256,
hashByName,
hmacSha256,
md5,
}

/**
* The Web Crypto API - grabbed from the Node.js library or the global
* @type Crypto
*/
// eslint-disable-next-line no-undef
const webCrypto = nodeCrypto.webcrypto || globalThis.crypto
/**
* The SubtleCrypto API for low level crypto operations.
* @type SubtleCrypto
*/
const subtleCrypto = webCrypto.subtle
const textEncoder = new TextEncoder()

/**
*
* @param {*} length
* @returns
*/
function randomBytes(length) {
return webCrypto.getRandomValues(Buffer.alloc(length))
}

async function md5(string) {
try {
return nodeCrypto.createHash('md5').update(string, 'utf-8').digest('hex')
} catch (e) {
// `createHash()` failed so we are probably not in Node.js, use the WebCrypto API instead.
// Note that the MD5 algorithm on WebCrypto is not available in Node.js.
// This is why we cannot just use WebCrypto in all environments.
const data = typeof string === 'string' ? textEncoder.encode(string) : string
const hash = await subtleCrypto.digest('MD5', data)
return Array.from(new Uint8Array(hash))
.map((b) => b.toString(16).padStart(2, '0'))
.join('')
}
}

// See AuthenticationMD5Password at https://www.postgresql.org/docs/current/static/protocol-flow.html
async function postgresMd5PasswordHash(user, password, salt) {
const inner = await md5(password + user)
const outer = await md5(Buffer.concat([Buffer.from(inner), salt]))
return 'md5' + outer
}

/**
* Create a SHA-256 digest of the given data
* @param {Buffer} data
*/
async function sha256(text) {
return await subtleCrypto.digest('SHA-256', text)
}
Comment on lines +57 to +63

async function hashByName(hashName, text) {
return await subtleCrypto.digest(hashName, text)
}

/**
* Sign the message with the given key
* @param {ArrayBuffer} keyBuffer
* @param {string} msg
*/
async function hmacSha256(keyBuffer, msg) {
const key = await subtleCrypto.importKey('raw', keyBuffer, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])
return await subtleCrypto.sign('HMAC', key, textEncoder.encode(msg))
}

/**
* Derive a key from the password and salt
* @param {string} password
* @param {Uint8Array} salt
* @param {number} iterations
*/
async function deriveKey(password, salt, iterations) {
const key = await subtleCrypto.importKey('raw', textEncoder.encode(password), 'PBKDF2', false, ['deriveBits'])
const params = { name: 'PBKDF2', hash: 'SHA-256', salt: salt, iterations: iterations }
return await subtleCrypto.deriveBits(params, key, 32 * 8, ['deriveBits'])
}
13 changes: 3 additions & 10 deletions packages/pg/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,14 @@

const defaults = require('./defaults')

const util = require('util')
const { isDate } = util.types || util // Node 8 doesn't have `util.types`
const { isDate } = require('util/types')

function escapeElement(elementRepresentation) {
const escaped = elementRepresentation.replace(/\\/g, '\\\\').replace(/"/g, '\\"')

return '"' + escaped + '"'
}

// Node.js v4 does not support those Buffer.from params
const bufferFrom =
Buffer.from(new Uint8Array(1).buffer, 0, 0).length === 0
? Buffer.from
: (arrayBuffer, byteOffset, length) => Buffer.from(arrayBuffer).slice(byteOffset, byteOffset + length)

// convert a JS array to a postgres array literal
// uses comma separator so won't work for types like box that use
// a different array separator.
Expand All @@ -33,7 +26,7 @@ function arrayString(val) {
result += arrayString(item)
} else if (ArrayBuffer.isView(item)) {
if (!(item instanceof Buffer)) {
item = bufferFrom(item.buffer, item.byteOffset, item.byteLength)
item = Buffer.from(item.buffer, item.byteOffset, item.byteLength)
}
result += '\\\\x' + item.toString('hex')
} else {
Expand All @@ -58,7 +51,7 @@ const prepareValue = function (val, seen) {
return val
}
if (ArrayBuffer.isView(val)) {
return bufferFrom(val.buffer, val.byteOffset, val.byteLength)
return Buffer.from(val.buffer, val.byteOffset, val.byteLength)
}
if (isDate(val)) {
if (defaults.parseInputDatesAsUTC) {
Expand Down
73 changes: 32 additions & 41 deletions packages/pg/test/integration/client/async-stack-trace-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,41 @@
const helper = require('../test-helper')
const pg = helper.pg

process.on('unhandledRejection', function (e) {
console.error(e, e.stack)
process.exit(1)
})

const suite = new helper.Suite()

// these tests will only work for if --async-stack-traces is on, which is the default starting in node 16.
const NODE_MAJOR_VERSION = +process.versions.node.split('.')[0]
if (NODE_MAJOR_VERSION >= 16) {
suite.test('promise API async stack trace in pool', async function outerFunction() {
async function innerFunction() {
const pool = new pg.Pool()
await pool.query('SELECT test from nonexistent')
}
try {
await innerFunction()
throw Error('should have errored')
} catch (e) {
const stack = e.stack
if (!e.stack.includes('innerFunction') || !e.stack.includes('outerFunction')) {
throw Error('async stack trace does not contain wanted values: ' + stack, { cause: e })
}
suite.test('promise API async stack trace in pool', async function outerFunction() {
async function innerFunction() {
const pool = new pg.Pool()
await pool.query('SELECT test from nonexistent')
}
try {
await innerFunction()
throw Error('should have errored')
} catch (e) {
const stack = e.stack
if (!e.stack.includes('innerFunction') || !e.stack.includes('outerFunction')) {
throw Error('async stack trace does not contain wanted values: ' + stack, { cause: e })
}
})
}
})

suite.test('promise API async stack trace in client', async function outerFunction() {
async function innerFunction() {
const client = new pg.Client()
await client.connect()
try {
await client.query('SELECT test from nonexistent')
} finally {
client.end()
}
}
suite.test('promise API async stack trace in client', async function outerFunction() {
async function innerFunction() {
const client = new pg.Client()
await client.connect()
try {
await innerFunction()
throw Error('should have errored')
} catch (e) {
const stack = e.stack
if (!e.stack.includes('innerFunction') || !e.stack.includes('outerFunction')) {
throw Error('async stack trace does not contain wanted values: ' + stack, { cause: e })
}
await client.query('SELECT test from nonexistent')
} finally {
client.end()
}
})
}
}
try {
await innerFunction()
throw Error('should have errored')
} catch (e) {
const stack = e.stack
if (!e.stack.includes('innerFunction') || !e.stack.includes('outerFunction')) {
throw Error('async stack trace does not contain wanted values: ' + stack, { cause: e })
}
}
})
5 changes: 0 additions & 5 deletions packages/pg/test/integration/client/query-as-promise-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@ const helper = require('../test-helper')
const pg = helper.pg
const assert = require('assert')

process.on('unhandledRejection', function (e) {
console.error(e, e.stack)
process.exit(1)
})

const suite = new helper.Suite()

suite.test('promise API', (cb) => {
Expand Down
Loading