package validators import ( "regexp" "strings" ) var ( hasUpper = regexp.MustCompile(`[A-Z]`) hasLower = regexp.MustCompile(`[a-z]`) hasNumber = regexp.MustCompile(`[0-9]`) hasSpecial = regexp.MustCompile(`[!@#$%^&*(),.?":{}|<>]`) // Common weak passwords list commonPasswords = []string{ "password", "12345678", "123456789", "1234567890", "qwerty", "abc123", "monkey", "1234567", "letmein", "trustno1", "dragon", "baseball", "iloveyou", "master", "sunshine", "ashley", "bailey", "passw0rd", "shadow", "123123", "654321", "superman", "qazwsx", "michael", "football", "welcome", "jesus", "ninja", "mustang", "password1", "admin", "1234", "12345", "123456", } ) // PasswordValidator valide la force d'un mot de passe type PasswordValidator struct { MinLength int } // NewPasswordValidator crée une nouvelle instance de PasswordValidator func NewPasswordValidator() *PasswordValidator { return &PasswordValidator{MinLength: 12} } // PasswordStrength représente le résultat de la validation d'un mot de passe type PasswordStrength struct { Valid bool Score int Details []string } // Validate valide la force d'un mot de passe selon les règles définies // BE-SEC-006: Implement comprehensive password strength validation func (v *PasswordValidator) Validate(password string) (PasswordStrength, error) { strength := PasswordStrength{ Valid: true, Details: []string{}, } // Length check if len(password) < v.MinLength { strength.Valid = false strength.Details = append(strength.Details, "Password must be at least 12 characters long") return strength, nil } // Maximum length check (prevent DoS) if len(password) > 128 { strength.Valid = false strength.Details = append(strength.Details, "Password must be less than 128 characters") return strength, nil } // Check for common weak passwords // Only reject if password IS exactly a common password // This is less aggressive than checking if password contains common words, // which would reject valid passwords like "VerySecurePassword123!@#" passwordLower := strings.ToLower(password) for _, common := range commonPasswords { // Exact match only if passwordLower == common { strength.Valid = false strength.Details = append(strength.Details, "Password contains common words or patterns") return strength, nil } } // Check for repetitive patterns (e.g., "aaaa", "1234", "abcd") if hasRepetitivePattern(password) { strength.Valid = false strength.Details = append(strength.Details, "Password contains repetitive patterns") return strength, nil } // Check for sequential patterns (e.g., "1234", "abcd", "qwerty") if hasSequentialPattern(password) { strength.Valid = false strength.Details = append(strength.Details, "Password contains sequential patterns") return strength, nil } // Upper case check if !hasUpper.MatchString(password) { strength.Valid = false strength.Details = append(strength.Details, "Must contain uppercase letter") } else { strength.Score++ } // Lower case check if !hasLower.MatchString(password) { strength.Valid = false strength.Details = append(strength.Details, "Must contain lowercase letter") } else { strength.Score++ } // Number check if !hasNumber.MatchString(password) { strength.Valid = false strength.Details = append(strength.Details, "Must contain number") } else { strength.Score++ } // Special character check if !hasSpecial.MatchString(password) { strength.Valid = false strength.Details = append(strength.Details, "Must contain special character") } else { strength.Score++ } return strength, nil } // hasRepetitivePattern checks if password contains repetitive characters func hasRepetitivePattern(password string) bool { if len(password) < 4 { return false } // Check for 4+ consecutive identical characters count := 1 for i := 1; i < len(password); i++ { if password[i] == password[i-1] { count++ if count >= 4 { return true } } else { count = 1 } } return false } // hasSequentialPattern checks if password contains sequential characters func hasSequentialPattern(password string) bool { if len(password) < 4 { return false } passwordLower := strings.ToLower(password) // Check for sequential patterns (e.g., "1234", "abcd", "qwerty") sequences := []string{ "0123456789", "9876543210", "abcdefghijklmnopqrstuvwxyz", "zyxwvutsrqponmlkjihgfedcba", "qwertyuiop", "poiuytrewq", "asdfghjkl", "lkjhgfdsa", "zxcvbnm", "mnbvcxz", } for _, seq := range sequences { for i := 0; i <= len(seq)-4; i++ { subseq := seq[i : i+4] if strings.Contains(passwordLower, subseq) { return true } } } return false } // ValidatePasswordChange validates a new password against the old password // BE-SEC-006: Ensure new password is sufficiently different from old password func (v *PasswordValidator) ValidatePasswordChange(newPassword, oldPassword string) (PasswordStrength, error) { strength, err := v.Validate(newPassword) if err != nil || !strength.Valid { return strength, err } // Check if new password is too similar to old password if oldPassword != "" { similarity := calculateSimilarity(newPassword, oldPassword) if similarity > 0.7 { strength.Valid = false strength.Details = append(strength.Details, "New password is too similar to the old password") return strength, nil } // Check if new password contains old password if strings.Contains(strings.ToLower(newPassword), strings.ToLower(oldPassword)) { strength.Valid = false strength.Details = append(strength.Details, "New password cannot contain the old password") return strength, nil } } return strength, nil } // calculateSimilarity calculates the similarity ratio between two strings func calculateSimilarity(s1, s2 string) float64 { if len(s1) == 0 && len(s2) == 0 { return 1.0 } if len(s1) == 0 || len(s2) == 0 { return 0.0 } // Simple Levenshtein-like similarity maxLen := len(s1) if len(s2) > maxLen { maxLen = len(s2) } matches := 0 minLen := len(s1) if len(s2) < minLen { minLen = len(s2) } for i := 0; i < minLen; i++ { if s1[i] == s2[i] { matches++ } } return float64(matches) / float64(maxLen) }