const crypto = require('crypto'); const axios = require('axios'); class HaveIBeenPwnedAPI { constructor(config = {}) { this.baseUrl = 'https://api.pwnedpasswords.com'; this.userAgent = config.userAgent || 'PasswordSecurityChecker'; } /** * Generates a SHA-1 hash of the password * @param {string} password - The password to hash * @returns {string} The uppercase SHA-1 hash */ generateHash(password) { return crypto .createHash('sha1') .update(password) .digest('hex') .toUpperCase(); } /** * Checks if a password has been exposed in known data breaches * @param {string} password - The password to check * @returns {Promise<{isCompromised: boolean, timesExposed: number}>} */ async checkPassword(password) { try { // Generate hash and get first 5 characters for k-anonymity const hash = this.generateHash(password); const hashPrefix = hash.substring(0, 5); const hashSuffix = hash.substring(5); // Make request to HIBP API const response = await axios.get(`${this.baseUrl}/range/${hashPrefix}`, { headers: { 'User-Agent': this.userAgent } }); // Parse response and check if password hash suffix exists const hashes = response.data.split('\n'); const match = hashes.find(h => h.split(':')[0] === hashSuffix); if (match) { const timesExposed = parseInt(match.split(':')[1]); return { isCompromised: true, timesExposed }; } return { isCompromised: false, timesExposed: 0 }; } catch (error) { throw new Error(`Failed to check password: ${error.message}`); } } /** * Validates a password against HIBP and custom rules * @param {string} password - The password to validate * @param {Object} options - Validation options * @param {number} options.maxExposures - Maximum allowed exposures (default: 0) * @returns {Promise<{isValid: boolean, reason?: string}>} */ async validatePassword(password, options = { maxExposures: 0 }) { try { const result = await this.checkPassword(password); if (result.timesExposed > options.maxExposures) { return { isValid: false, reason: `Password has been exposed ${result.timesExposed} times in data breaches` }; } return { isValid: true }; } catch (error) { throw new Error(`Password validation failed: ${error.message}`); } } } module.exports = HaveIBeenPwnedAPI;