@foal/core
Version:
Full-featured Node.js framework, with no complexity
67 lines (66 loc) • 3.04 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PasswordService = exports.PASSWORD_ITERATIONS = void 0;
const crypto_1 = require("crypto");
const util_1 = require("util");
const utils_1 = require("./utils");
exports.PASSWORD_ITERATIONS = 600000;
/**
* Service for password hashing and verification.
*
* @export
*/
class PasswordService {
/**
* Hash a password using the PBKDF2 algorithm.
*
* Configured to use PBKDF2 + HMAC + SHA256.
* The result is a 64 byte binary string.
*
* The random salt is 16 bytes long.
* The number of iterations is 600,000.
* The length key is 32 bytes long.
*
* @param {string} plainTextPassword - The password to hash.
* @returns {Promise<string>} The derived key with the algorithm name, the number of iterations and the salt.
*/
async hashPassword(plainTextPassword) {
const saltBuffer = await (0, util_1.promisify)(crypto_1.randomBytes)(16);
const iterations = exports.PASSWORD_ITERATIONS;
const keylen = 32;
const digest = 'sha256';
const derivedKeyBuffer = await (0, util_1.promisify)(crypto_1.pbkdf2)(plainTextPassword, saltBuffer, iterations, keylen, digest);
const salt = saltBuffer.toString('base64');
const derivedKey = derivedKeyBuffer.toString('base64');
return `pbkdf2_${digest}$${iterations}$${salt}$${derivedKey}`;
}
/**
* Compare a plain text password and a hash to see if they match.
*
* @param {string} plainTextPassword - The password in clear text.
* @param {string} passwordHash - The password hash generated by the `hashPassword` method.
* @param {VerifyPasswordOptions} [options] - Optional configuration object.
* @returns {Promise<boolean>} True if the hash and the password match. False otherwise.
*/
async verifyPassword(plainTextPassword, passwordHash, options) {
const { digestAlgorithm, iterations, salt, derivedKey, keyLength } = (0, utils_1.decomposePbkdf2PasswordHash)(passwordHash);
const password = await (0, util_1.promisify)(crypto_1.pbkdf2)(plainTextPassword, salt, iterations, keyLength, digestAlgorithm);
const isValid = (0, crypto_1.timingSafeEqual)(password, derivedKey);
if (isValid && this.passwordHashNeedsToBeRefreshed(passwordHash) && options?.onPasswordUpgrade) {
const newHash = await this.hashPassword(plainTextPassword);
await options.onPasswordUpgrade(newHash);
}
return isValid;
}
/**
* Check if a password hash needs to be refreshed.
*
* @param {string} passwordHash - The password hash to check.
* @returns {boolean} True if the password hash needs to be refreshed. False otherwise.
*/
passwordHashNeedsToBeRefreshed(passwordHash) {
const { iterations } = (0, utils_1.decomposePbkdf2PasswordHash)(passwordHash);
return iterations < exports.PASSWORD_ITERATIONS;
}
}
exports.PasswordService = PasswordService;