2025-03-23 13:14:14 +00:00

93 lines
2.9 KiB
JavaScript

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;