/** * Password Policy Configuration * Enforces strong password requirements */ const PASSWORD_POLICY = { minLength: 12, maxLength: 128, requireUppercase: true, requireLowercase: true, requireNumbers: true, requireSpecialChars: true, specialChars: '!@#$%^&*()_+-=[]{}|;:,.<>?', preventCommonPasswords: true, preventUserInfo: true, // Don't allow username/email in password maxRepeatingChars: 3, historyCount: 5 // Remember last 5 passwords }; const ACCOUNT_LOCKOUT = { maxFailedAttempts: 5, lockoutDuration: 30 * 60 * 1000, // 30 minutes resetAfterSuccess: true, notifyOnLockout: true }; const PASSWORD_EXPIRY = { enabled: true, expiryDays: 90, warningDays: 14, gracePeriodDays: 7 }; const SESSION_POLICY = { maxConcurrentSessions: 3, absoluteTimeout: 24 * 60 * 60 * 1000, // 24 hours idleTimeout: 2 * 60 * 60 * 1000, // 2 hours refreshTokenRotation: true }; // Common passwords to block (top 100 most common) const COMMON_PASSWORDS = [ '123456', 'password', '12345678', 'qwerty', '123456789', '12345', '1234', '111111', '1234567', 'dragon', '123123', 'baseball', 'iloveyou', 'trustno1', '1234567890', 'sunshine', 'master', '123321', '666666', 'photoshop', '1111111', 'princess', 'azerty', '000000', 'access', '696969', 'batman', '121212', 'letmein', 'qwertyuiop', 'admin', 'welcome', 'monkey', 'login', 'abc123', 'starwars', 'shadow', 'ashley', 'football', 'superman', 'michael', 'ninja', 'mustang', 'password1', 'passw0rd', 'password123' ]; /** * Validates password against policy * @param {string} password - Password to validate * @param {object} userData - User data (username, email) to prevent personal info * @returns {object} - {valid: boolean, errors: string[]} */ function validatePassword(password, userData = {}) { const errors = []; // Length check if (password.length < PASSWORD_POLICY.minLength) { errors.push(`Password must be at least ${PASSWORD_POLICY.minLength} characters long`); } if (password.length > PASSWORD_POLICY.maxLength) { errors.push(`Password must not exceed ${PASSWORD_POLICY.maxLength} characters`); } // Character requirements if (PASSWORD_POLICY.requireUppercase && !/[A-Z]/.test(password)) { errors.push('Password must contain at least one uppercase letter'); } if (PASSWORD_POLICY.requireLowercase && !/[a-z]/.test(password)) { errors.push('Password must contain at least one lowercase letter'); } if (PASSWORD_POLICY.requireNumbers && !/\d/.test(password)) { errors.push('Password must contain at least one number'); } if (PASSWORD_POLICY.requireSpecialChars) { const specialCharsRegex = new RegExp(`[${PASSWORD_POLICY.specialChars.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}]`); if (!specialCharsRegex.test(password)) { errors.push('Password must contain at least one special character (!@#$%^&*...)'); } } // Repeating characters const repeatingRegex = new RegExp(`(.)\\1{${PASSWORD_POLICY.maxRepeatingChars},}`); if (repeatingRegex.test(password)) { errors.push(`Password cannot contain more than ${PASSWORD_POLICY.maxRepeatingChars} repeating characters`); } // Common passwords if (PASSWORD_POLICY.preventCommonPasswords) { const lowerPassword = password.toLowerCase(); if (COMMON_PASSWORDS.some(common => lowerPassword.includes(common))) { errors.push('Password is too common or easily guessable'); } } // User info in password if (PASSWORD_POLICY.preventUserInfo && userData) { const lowerPassword = password.toLowerCase(); if (userData.username && lowerPassword.includes(userData.username.toLowerCase())) { errors.push('Password cannot contain your username'); } if (userData.email) { const emailParts = userData.email.split('@')[0].toLowerCase(); if (lowerPassword.includes(emailParts)) { errors.push('Password cannot contain your email address'); } } } return { valid: errors.length === 0, errors, strength: calculatePasswordStrength(password) }; } /** * Calculate password strength score (0-100) */ function calculatePasswordStrength(password) { let score = 0; // Length score (0-30 points) score += Math.min(30, password.length * 2); // Character variety (0-40 points) if (/[a-z]/.test(password)) score += 10; if (/[A-Z]/.test(password)) score += 10; if (/\d/.test(password)) score += 10; if (/[^a-zA-Z0-9]/.test(password)) score += 10; // Patterns (0-30 points) const hasNoRepeats = !/(.)\\1{2,}/.test(password); const hasNoSequence = !/(?:abc|bcd|cde|123|234|345)/i.test(password); const hasMixedCase = /[a-z]/.test(password) && /[A-Z]/.test(password); if (hasNoRepeats) score += 10; if (hasNoSequence) score += 10; if (hasMixedCase) score += 10; return Math.min(100, score); } /** * Get password strength label */ function getStrengthLabel(score) { if (score >= 80) return { label: 'Strong', color: 'success' }; if (score >= 60) return { label: 'Good', color: 'info' }; if (score >= 40) return { label: 'Fair', color: 'warning' }; return { label: 'Weak', color: 'error' }; } module.exports = { PASSWORD_POLICY, ACCOUNT_LOCKOUT, PASSWORD_EXPIRY, SESSION_POLICY, validatePassword, calculatePasswordStrength, getStrengthLabel };