UNPKG

pg-password-util

Version:

Client-side encoding of PostgreSQL user passwords for use in CREATE USER and ALTER USER

122 lines 4.54 kB
"use strict"; 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