/** * Enhanced Password Validation Component * Provides real-time password strength validation and feedback * * Usage: * new PasswordValidator({ * passwordField: '#password', * confirmField: '#verify_password', * strengthIndicator: '#password-strength', * requirementsContainer: '.password-checklist' * }); */ class PasswordValidator { constructor(options = {}) { this.options = { passwordField: '#password', confirmField: '#verify_password', strengthIndicator: '#password-strength', strengthText: '.password-strength-text', requirementsContainer: '.password-checklist', matchIndicator: '.password-match-indicator', matchText: '.password-match-text', toggleButtons: '.password-toggle', minLength: 8, maxLength: 128, requireUppercase: true, requireLowercase: true, requireNumbers: true, requireSpecialChars: true, checkCommonPasswords: true, ...options }; this.translations = { very_weak: 'Very Weak', weak: 'Weak', fair: 'Fair', good: 'Good', strong: 'Strong', match: 'Passwords match', no_match: 'Passwords do not match', ...options.translations }; this.init(); } init() { this.bindEvents(); this.initializeToggles(); } bindEvents() { const passwordField = $(this.options.passwordField); const confirmField = $(this.options.confirmField); // Real-time password validation passwordField.on('input', (e) => { const password = $(e.target).val(); this.validatePassword(password); this.checkPasswordMatch(); }); // Real-time confirmation validation confirmField.on('input', () => { this.checkPasswordMatch(); }); // Form submission validation passwordField.closest('form').on('submit', (e) => { if (!this.isFormValid()) { e.preventDefault(); this.showValidationErrors(); } }); } initializeToggles() { $(this.options.toggleButtons).on('click', (e) => { const button = $(e.currentTarget); const targetSelector = button.data('target'); const target = $(targetSelector); const icon = button.find('i'); if (target.attr('type') === 'password') { target.attr('type', 'text'); icon.removeClass('fa-eye').addClass('fa-eye-slash'); } else { target.attr('type', 'password'); icon.removeClass('fa-eye-slash').addClass('fa-eye'); } }); } validatePassword(password) { const checks = { length: password.length >= this.options.minLength && password.length <= this.options.maxLength, uppercase: this.options.requireUppercase ? /[A-Z]/.test(password) : true, lowercase: this.options.requireLowercase ? /[a-z]/.test(password) : true, number: this.options.requireNumbers ? /[0-9]/.test(password) : true, special: this.options.requireSpecialChars ? /[^A-Za-z0-9]/.test(password) : true, notCommon: this.options.checkCommonPasswords ? !this.isCommonPassword(password) : true }; this.updateRequirementIndicators(checks); const strength = this.calculateStrength(password, checks); this.updateStrengthIndicator(strength); return { isValid: Object.values(checks).every(Boolean), strength: strength, checks: checks }; } updateRequirementIndicators(checks) { const container = $(this.options.requirementsContainer); container.find('.requirement').each((index, element) => { const requirement = $(element); const rule = requirement.data('rule'); const icon = requirement.find('i'); if (checks[rule]) { requirement.addClass('valid'); icon.removeClass('fa-times text-danger').addClass('fa-check text-success'); } else { requirement.removeClass('valid'); icon.removeClass('fa-check text-success').addClass('fa-times text-danger'); } }); } calculateStrength(password, checks) { if (!password) return 0; let score = 0; const passedChecks = Object.values(checks).filter(Boolean).length; const totalChecks = Object.keys(checks).length; // Base score from requirement checks (60% of total) score += (passedChecks / totalChecks) * 60; // Length bonus (20% of total) if (password.length >= 16) score += 20; else if (password.length >= 12) score += 15; else if (password.length >= 10) score += 10; else if (password.length >= 8) score += 5; // Character variety bonus (10% of total) const uniqueChars = new Set(password.toLowerCase()).size; const varietyRatio = uniqueChars / password.length; if (varietyRatio > 0.8) score += 10; else if (varietyRatio > 0.6) score += 7; else if (varietyRatio > 0.4) score += 5; // Pattern penalties (10% of total) if (!this.hasSequentialChars(password)) score += 5; if (!this.hasRepeatedChars(password)) score += 5; // Convert score to 0-5 scale return Math.min(Math.floor(score / 20), 5); } updateStrengthIndicator(strength) { const indicator = $(this.options.strengthIndicator); const textElement = $(this.options.strengthText); const strengthLevels = [ this.translations.very_weak, this.translations.weak, this.translations.fair, this.translations.good, this.translations.strong ]; indicator.attr('data-strength', strength); textElement.text(strengthLevels[strength] || strengthLevels[0]); // Update text color const colorClasses = ['text-danger', 'text-danger', 'text-warning', 'text-info', 'text-success']; textElement.removeClass('text-danger text-warning text-info text-success') .addClass(colorClasses[strength] || 'text-danger'); } checkPasswordMatch() { const password = $(this.options.passwordField).val(); const confirmPassword = $(this.options.confirmField).val(); const matchIndicator = $(this.options.matchIndicator); const matchText = $(this.options.matchText); if (!confirmPassword) { matchIndicator.hide(); return false; } matchIndicator.show(); const isMatch = password === confirmPassword; if (isMatch) { matchText.removeClass('text-danger').addClass('text-success') .text(this.translations.match); } else { matchText.removeClass('text-success').addClass('text-danger') .text(this.translations.no_match); } return isMatch; } isFormValid() { const password = $(this.options.passwordField).val(); const validation = this.validatePassword(password); const passwordsMatch = this.checkPasswordMatch(); return validation.isValid && passwordsMatch; } showValidationErrors() { const password = $(this.options.passwordField).val(); const validation = this.validatePassword(password); if (!validation.isValid) { // Scroll to password field and highlight issues $(this.options.passwordField)[0].scrollIntoView({ behavior: 'smooth', block: 'center' }); // Add shake animation to password field $(this.options.passwordField).addClass('shake'); setTimeout(() => { $(this.options.passwordField).removeClass('shake'); }, 600); } } // Utility methods isCommonPassword(password) { const commonPasswords = [ 'password', 'password123', '123456', '123456789', 'qwerty', 'abc123', 'password1', 'admin', 'administrator', 'root', 'guest', 'test', 'demo', 'welcome', 'login', 'user', '12345678', '1234567890', 'qwerty123', 'letmein', 'monkey', 'dragon', 'master', 'shadow', 'superman' ]; return commonPasswords.includes(password.toLowerCase()); } hasSequentialChars(password) { const lower = password.toLowerCase(); for (let i = 0; i < lower.length - 2; i++) { const char1 = lower.charCodeAt(i); const char2 = lower.charCodeAt(i + 1); const char3 = lower.charCodeAt(i + 2); if ((char2 === char1 + 1 && char3 === char2 + 1) || (char2 === char1 - 1 && char3 === char2 - 1)) { return true; } } return false; } hasRepeatedChars(password) { return /(.)\1{3,}/.test(password); } // Public API methods getPasswordStrength() { const password = $(this.options.passwordField).val(); return this.calculateStrength(password, this.getChecks(password)); } getChecks(password) { return { length: password.length >= this.options.minLength && password.length <= this.options.maxLength, uppercase: this.options.requireUppercase ? /[A-Z]/.test(password) : true, lowercase: this.options.requireLowercase ? /[a-z]/.test(password) : true, number: this.options.requireNumbers ? /[0-9]/.test(password) : true, special: this.options.requireSpecialChars ? /[^A-Za-z0-9]/.test(password) : true, notCommon: this.options.checkCommonPasswords ? !this.isCommonPassword(password) : true }; } reset() { $(this.options.strengthIndicator).attr('data-strength', 0); $(this.options.strengthText).text(''); $(this.options.matchIndicator).hide(); $(this.options.requirementsContainer + ' .requirement') .removeClass('valid') .find('i') .removeClass('fa-check text-success') .addClass('fa-times text-danger'); } } // Auto-initialize if jQuery is available if (typeof $ !== 'undefined') { $(document).ready(function() { // Auto-initialize on pages with password fields if ($('.password-field, .password-strength-container').length > 0) { window.passwordValidator = new PasswordValidator(); } }); } // Export for module systems if (typeof module !== 'undefined' && module.exports) { module.exports = PasswordValidator; } // Global fallback if (typeof window !== 'undefined') { window.PasswordValidator = PasswordValidator; }