pg-password-util
Version:
Client-side encoding of PostgreSQL user passwords for use in CREATE USER and ALTER USER
122 lines • 4.54 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.alterUserPassword = exports.genAlterUserPasswordSql = exports.encodePassword = exports.encodeScramSha256 = exports.encodeMd5 = void 0;
const crypto_1 = require("crypto");
const pgFormat = require("pg-format");
function hmacSha256(key, text) {
return (0, crypto_1.createHmac)('sha256', key).update(text).digest();
}
function sha256(text) {
return (0, crypto_1.createHash)('sha256').update(text).digest();
}
/**
* Encode the password for md5 authentication.
* This should only be used for legacy servers.
*/
function encodeMd5(opts) {
const { username, password } = opts;
if (!username) {
throw new Error('A username is required');
}
if (!password) {
throw new Error('A non-empty password is required');
}
const hash = (0, crypto_1.createHash)('md5');
hash.update(password);
hash.update(username); // PostgreSQL uses the username as a salt
return 'md5' + hash.digest('hex');
}
exports.encodeMd5 = encodeMd5;
const DEFAULT_SCRAM_SALT_SIZE = 16;
const DFEAULT_SCRAM_ITERATIONS = 4096;
/**
* Encode the password for SCRAM-SHA-256 authentication.
* The iterations and salt are optional.
* If not provided, the defaults will be used that match the defaults for the PostgreSQL server.
*/
function encodeScramSha256(opts) {
const salt = opts.salt || (0, crypto_1.randomBytes)(DEFAULT_SCRAM_SALT_SIZE);
if (!Buffer.isBuffer(salt)) {
throw new Error('salt must be a Buffer');
}
const iterations = opts.iterations || DFEAULT_SCRAM_ITERATIONS;
if (!Number.isSafeInteger(iterations) || iterations <= 0) {
throw new Error('iterations must be a positive integer: ' + iterations);
}
const { password } = opts;
if (!password) {
throw new Error('A non-empty password is required');
}
const saltedPassword = (0, crypto_1.pbkdf2Sync)(password, salt, iterations, 32, 'sha256');
const clientKey = hmacSha256(saltedPassword, 'Client Key');
const storedKey = sha256(clientKey);
const serverKey = hmacSha256(saltedPassword, 'Server Key');
return [
'SCRAM-SHA-256',
'$' + iterations,
':' + salt.toString('base64'),
'$' + storedKey.toString('base64'),
':' + serverKey.toString('base64')
].join('');
}
exports.encodeScramSha256 = encodeScramSha256;
/**
* Encode the password using the specified password encryption mechanism.
* This function takes the username as once of it's options, but it is only used for the md5 mechanism.
* @param opts Encoding options
* @returns the encoded password as an unescaped string literal
*/
function encodePassword(opts) {
const { username, password, passwordEncryption, } = opts;
switch (passwordEncryption) {
case 'on':
case 'off':
case 'md5':
return encodeMd5({
username,
password,
});
case 'scram-sha-256':
return encodeScramSha256({
password,
});
}
throw new Error('Unhandled passwordEncryption: ' + passwordEncryption);
}
exports.encodePassword = encodePassword;
/**
* Generate SQL for changing a user's password.
* A specific passwordEncryption must be specified.
* @returns a SQL statement that can be executed to update the user's password to the encoded value
*/
function genAlterUserPasswordSql(opts) {
const { username, password, passwordEncryption, } = opts;
const encodedPassword = encodePassword({
username,
password,
passwordEncryption: passwordEncryption,
});
return pgFormat('ALTER USER %I PASSWORD %L', username, encodedPassword);
}
exports.genAlterUserPasswordSql = genAlterUserPasswordSql;
/**
* Use the provided DB client to change the user's password.
* If the passwordEncryption is not specified, the database will be queried for the current password_encryption.
*/
async function alterUserPassword(client, opts) {
let { passwordEncryption } = opts;
if (!passwordEncryption) {
// Unspecified so use the server default
const result = await client.query('SHOW password_encryption');
passwordEncryption = result.rows[0].password_encryption;
}
const { username, password, } = opts;
const sql = genAlterUserPasswordSql({
username,
password,
passwordEncryption,
});
await client.query(sql);
}
exports.alterUserPassword = alterUserPassword;
//# sourceMappingURL=index.js.map