Skip to content

Commit f776327

Browse files
authored
Remove compatibility code for unsupported versions of Node (<16) (#3678)
* Remove compatibility code for unsupported versions of Node (<16) * cleanup: Remove remaining `unhandledRejection` handlers in pg tests Unhandled rejections are errors by default in all supported versions of Node.
1 parent f252870 commit f776327

6 files changed

Lines changed: 123 additions & 196 deletions

File tree

packages/pg/lib/crypto/utils-legacy.js

Lines changed: 0 additions & 43 deletions
This file was deleted.

packages/pg/lib/crypto/utils-webcrypto.js

Lines changed: 0 additions & 89 deletions
This file was deleted.

packages/pg/lib/crypto/utils.js

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,89 @@
1-
'use strict'
2-
3-
const useLegacyCrypto = parseInt(process.versions && process.versions.node && process.versions.node.split('.')[0]) < 15
4-
if (useLegacyCrypto) {
5-
// We are on an old version of Node.js that requires legacy crypto utilities.
6-
module.exports = require('./utils-legacy')
7-
} else {
8-
module.exports = require('./utils-webcrypto')
1+
const nodeCrypto = require('crypto')
2+
3+
module.exports = {
4+
postgresMd5PasswordHash,
5+
randomBytes,
6+
deriveKey,
7+
sha256,
8+
hashByName,
9+
hmacSha256,
10+
md5,
11+
}
12+
13+
/**
14+
* The Web Crypto API - grabbed from the Node.js library or the global
15+
* @type Crypto
16+
*/
17+
// eslint-disable-next-line no-undef
18+
const webCrypto = nodeCrypto.webcrypto || globalThis.crypto
19+
/**
20+
* The SubtleCrypto API for low level crypto operations.
21+
* @type SubtleCrypto
22+
*/
23+
const subtleCrypto = webCrypto.subtle
24+
const textEncoder = new TextEncoder()
25+
26+
/**
27+
*
28+
* @param {*} length
29+
* @returns
30+
*/
31+
function randomBytes(length) {
32+
return webCrypto.getRandomValues(Buffer.alloc(length))
33+
}
34+
35+
async function md5(string) {
36+
try {
37+
return nodeCrypto.createHash('md5').update(string, 'utf-8').digest('hex')
38+
} catch (e) {
39+
// `createHash()` failed so we are probably not in Node.js, use the WebCrypto API instead.
40+
// Note that the MD5 algorithm on WebCrypto is not available in Node.js.
41+
// This is why we cannot just use WebCrypto in all environments.
42+
const data = typeof string === 'string' ? textEncoder.encode(string) : string
43+
const hash = await subtleCrypto.digest('MD5', data)
44+
return Array.from(new Uint8Array(hash))
45+
.map((b) => b.toString(16).padStart(2, '0'))
46+
.join('')
47+
}
48+
}
49+
50+
// See AuthenticationMD5Password at https://www.postgresql.org/docs/current/static/protocol-flow.html
51+
async function postgresMd5PasswordHash(user, password, salt) {
52+
const inner = await md5(password + user)
53+
const outer = await md5(Buffer.concat([Buffer.from(inner), salt]))
54+
return 'md5' + outer
55+
}
56+
57+
/**
58+
* Create a SHA-256 digest of the given data
59+
* @param {Buffer} data
60+
*/
61+
async function sha256(text) {
62+
return await subtleCrypto.digest('SHA-256', text)
63+
}
64+
65+
async function hashByName(hashName, text) {
66+
return await subtleCrypto.digest(hashName, text)
67+
}
68+
69+
/**
70+
* Sign the message with the given key
71+
* @param {ArrayBuffer} keyBuffer
72+
* @param {string} msg
73+
*/
74+
async function hmacSha256(keyBuffer, msg) {
75+
const key = await subtleCrypto.importKey('raw', keyBuffer, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])
76+
return await subtleCrypto.sign('HMAC', key, textEncoder.encode(msg))
77+
}
78+
79+
/**
80+
* Derive a key from the password and salt
81+
* @param {string} password
82+
* @param {Uint8Array} salt
83+
* @param {number} iterations
84+
*/
85+
async function deriveKey(password, salt, iterations) {
86+
const key = await subtleCrypto.importKey('raw', textEncoder.encode(password), 'PBKDF2', false, ['deriveBits'])
87+
const params = { name: 'PBKDF2', hash: 'SHA-256', salt: salt, iterations: iterations }
88+
return await subtleCrypto.deriveBits(params, key, 32 * 8, ['deriveBits'])
989
}

packages/pg/lib/utils.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,14 @@
22

33
const defaults = require('./defaults')
44

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

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

1110
return '"' + escaped + '"'
1211
}
1312

14-
// Node.js v4 does not support those Buffer.from params
15-
const bufferFrom =
16-
Buffer.from(new Uint8Array(1).buffer, 0, 0).length === 0
17-
? Buffer.from
18-
: (arrayBuffer, byteOffset, length) => Buffer.from(arrayBuffer).slice(byteOffset, byteOffset + length)
19-
2013
// convert a JS array to a postgres array literal
2114
// uses comma separator so won't work for types like box that use
2215
// a different array separator.
@@ -33,7 +26,7 @@ function arrayString(val) {
3326
result += arrayString(item)
3427
} else if (ArrayBuffer.isView(item)) {
3528
if (!(item instanceof Buffer)) {
36-
item = bufferFrom(item.buffer, item.byteOffset, item.byteLength)
29+
item = Buffer.from(item.buffer, item.byteOffset, item.byteLength)
3730
}
3831
result += '\\\\x' + item.toString('hex')
3932
} else {
@@ -58,7 +51,7 @@ const prepareValue = function (val, seen) {
5851
return val
5952
}
6053
if (ArrayBuffer.isView(val)) {
61-
return bufferFrom(val.buffer, val.byteOffset, val.byteLength)
54+
return Buffer.from(val.buffer, val.byteOffset, val.byteLength)
6255
}
6356
if (isDate(val)) {
6457
if (defaults.parseInputDatesAsUTC) {

packages/pg/test/integration/client/async-stack-trace-tests.js

Lines changed: 32 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,41 @@
22
const helper = require('../test-helper')
33
const pg = helper.pg
44

5-
process.on('unhandledRejection', function (e) {
6-
console.error(e, e.stack)
7-
process.exit(1)
8-
})
9-
105
const suite = new helper.Suite()
116

12-
// these tests will only work for if --async-stack-traces is on, which is the default starting in node 16.
13-
const NODE_MAJOR_VERSION = +process.versions.node.split('.')[0]
14-
if (NODE_MAJOR_VERSION >= 16) {
15-
suite.test('promise API async stack trace in pool', async function outerFunction() {
16-
async function innerFunction() {
17-
const pool = new pg.Pool()
18-
await pool.query('SELECT test from nonexistent')
19-
}
20-
try {
21-
await innerFunction()
22-
throw Error('should have errored')
23-
} catch (e) {
24-
const stack = e.stack
25-
if (!e.stack.includes('innerFunction') || !e.stack.includes('outerFunction')) {
26-
throw Error('async stack trace does not contain wanted values: ' + stack, { cause: e })
27-
}
7+
suite.test('promise API async stack trace in pool', async function outerFunction() {
8+
async function innerFunction() {
9+
const pool = new pg.Pool()
10+
await pool.query('SELECT test from nonexistent')
11+
}
12+
try {
13+
await innerFunction()
14+
throw Error('should have errored')
15+
} catch (e) {
16+
const stack = e.stack
17+
if (!e.stack.includes('innerFunction') || !e.stack.includes('outerFunction')) {
18+
throw Error('async stack trace does not contain wanted values: ' + stack, { cause: e })
2819
}
29-
})
20+
}
21+
})
3022

31-
suite.test('promise API async stack trace in client', async function outerFunction() {
32-
async function innerFunction() {
33-
const client = new pg.Client()
34-
await client.connect()
35-
try {
36-
await client.query('SELECT test from nonexistent')
37-
} finally {
38-
client.end()
39-
}
40-
}
23+
suite.test('promise API async stack trace in client', async function outerFunction() {
24+
async function innerFunction() {
25+
const client = new pg.Client()
26+
await client.connect()
4127
try {
42-
await innerFunction()
43-
throw Error('should have errored')
44-
} catch (e) {
45-
const stack = e.stack
46-
if (!e.stack.includes('innerFunction') || !e.stack.includes('outerFunction')) {
47-
throw Error('async stack trace does not contain wanted values: ' + stack, { cause: e })
48-
}
28+
await client.query('SELECT test from nonexistent')
29+
} finally {
30+
client.end()
4931
}
50-
})
51-
}
32+
}
33+
try {
34+
await innerFunction()
35+
throw Error('should have errored')
36+
} catch (e) {
37+
const stack = e.stack
38+
if (!e.stack.includes('innerFunction') || !e.stack.includes('outerFunction')) {
39+
throw Error('async stack trace does not contain wanted values: ' + stack, { cause: e })
40+
}
41+
}
42+
})

packages/pg/test/integration/client/query-as-promise-tests.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@ const helper = require('../test-helper')
44
const pg = helper.pg
55
const assert = require('assert')
66

7-
process.on('unhandledRejection', function (e) {
8-
console.error(e, e.stack)
9-
process.exit(1)
10-
})
11-
127
const suite = new helper.Suite()
138

149
suite.test('promise API', (cb) => {

0 commit comments

Comments
 (0)