UNPKG

@foal/core

Version:

Full-featured Node.js framework, with no complexity

67 lines (66 loc) 3.04 kB
"use strict"; 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;