export function validatePassword(password) {
const length = password.length;
const hasUpper = /[A-Z]/.test(password);
const hasLower = /[a-z]/.test(password);
const hasDigit = /[0-9]/.test(password);
const hasSpecial = /[^a-zA-Z0-9]/.test(password);
let lengthScore;
if (length < 8) {
lengthScore = 0;
} else if (length < 12) {
lengthScore = 1;
} else if (length < 16) {
lengthScore = 2;
} else {
lengthScore = 3;
}
const score = lengthScore
+ (hasUpper ? 1 : 0)
+ (hasLower ? 1 : 0)
+ (hasDigit ? 1 : 0)
+ (hasSpecial ? 1 : 0);
const suggestions = [];
if (length < 12) {
suggestions.push("Use at least 12 characters");
}
if (!hasUpper) {
suggestions.push("Add uppercase letters");
}
if (!hasLower) {
suggestions.push("Add lowercase letters");
}
if (!hasDigit) {
suggestions.push("Add numbers");
}
if (!hasSpecial) {
suggestions.push("Add special characters (!@#$%^&*)");
}
let strength;
if (score <= 2) {
strength = 'weak';
} else if (score <= 4) {
strength = 'fair';
} else if (score <= 6) {
strength = 'good';
} else {
strength = 'strong';
}
const entropyBits = estimateEntropy(password);
return {
strength,
score,
entropyBits,
suggestions,
checks: {
hasLowercase: hasLower,
hasUppercase: hasUpper,
hasDigit: hasDigit,
hasSpecial: hasSpecial,
length: length,
meetsMinLength: length >= 12,
},
};
}
function estimateEntropy(password) {
if (!password || password.length === 0) {
return 0.0;
}
const hasLower = /[a-z]/.test(password);
const hasUpper = /[A-Z]/.test(password);
const hasDigit = /[0-9]/.test(password);
const hasSpecial = /[^a-zA-Z0-9]/.test(password);
let poolSize = 0;
if (hasLower) poolSize += 26;
if (hasUpper) poolSize += 26;
if (hasDigit) poolSize += 10;
if (hasSpecial) poolSize += 32;
if (poolSize === 0) {
poolSize = 26; }
const bitsPerChar = Math.log2(poolSize);
return bitsPerChar * password.length;
}
export function getStrengthColor(strength) {
const colors = {
weak: '#ef4444', fair: '#f59e0b', good: '#3b82f6', strong: '#22c55e', };
return colors[strength] || colors.weak;
}
export function getStrengthPercent(strength) {
const percents = {
weak: 25,
fair: 50,
good: 75,
strong: 100,
};
return percents[strength] || 25;
}
export function getStrengthLabel(strength) {
return strength.charAt(0).toUpperCase() + strength.slice(1);
}
export function createStrengthMeter(passwordInput, options = {}) {
const {
meterContainer,
suggestionsList,
labelElement,
onValidate,
} = options;
let meterBar = null;
if (meterContainer) {
meterBar = meterContainer.querySelector('.strength-bar');
if (!meterBar) {
meterBar = document.createElement('div');
meterBar.className = 'strength-bar';
meterContainer.appendChild(meterBar);
}
}
function handleInput() {
const validation = validatePassword(passwordInput.value);
update(validation);
if (onValidate) {
onValidate(validation);
}
}
function update(validation) {
const { strength, suggestions } = validation;
const color = getStrengthColor(strength);
const percent = getStrengthPercent(strength);
if (meterBar) {
meterBar.style.width = `${percent}%`;
meterBar.style.backgroundColor = color;
meterBar.dataset.strength = strength;
}
if (labelElement) {
labelElement.textContent = getStrengthLabel(strength);
labelElement.style.color = color;
}
if (suggestionsList) {
suggestionsList.innerHTML = suggestions
.map(s => `<li>${escapeHtml(s)}</li>`)
.join('');
}
}
passwordInput.addEventListener('input', handleInput);
return {
update: handleInput,
destroy: () => {
passwordInput.removeEventListener('input', handleInput);
},
getValidation: () => validatePassword(passwordInput.value),
};
}
function escapeHtml(str) {
const escapeMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
};
return str.replace(/[&<>"']/g, char => escapeMap[char]);
}
if (typeof window !== 'undefined') {
window.PasswordStrength = {
validatePassword,
getStrengthColor,
getStrengthPercent,
getStrengthLabel,
createStrengthMeter,
};
}