using framework_library; using System; using System.Collections.Generic; using System.Linq; namespace framework_business { public class PasswordValidationResult { public bool IsValid { get; set; } public List Errors { get; } = new List(); public string GetSummary() { return Errors.Count == 0 ? string.Empty : string.Join(" ", Errors); } } /// /// Server-side password complexity rules for SHOU-1 (Phase 4). /// public static class PasswordPolicy { private const int DefaultMinLength = 12; private const int MinAllowedLength = 8; private const int MaxAllowedLength = 128; private static readonly HashSet CommonPasswords = new HashSet(StringComparer.OrdinalIgnoreCase) { "password", "password1", "password123", "123456", "12345678", "123456789", "1234567890", "qwerty", "qwerty123", "letmein", "welcome", "admin", "administrator", "login", "passw0rd", "changeme", "secret", "iloveyou", "monkey", "dragon", "football", "baseball", "master", "sunshine", "princess", "shadow", "trustno1", "ev0luti0ns0ftware" }; public static int GetMinLength() { string configured = Environment.GetEnvironmentVariable("PASSWORD_MIN_LENGTH"); if (string.IsNullOrEmpty(configured)) configured = ConfigResolver.GetAppSetting("PASSWORD_MIN_LENGTH"); if (int.TryParse(configured, out int length)) { if (length < MinAllowedLength) return MinAllowedLength; if (length > MaxAllowedLength) return MaxAllowedLength; return length; } return DefaultMinLength; } public static string GetRequirementsMessage() { return "Password must be at least " + GetMinLength() + " characters and include upper case, lower case, a digit, and a special character."; } public static PasswordValidationResult Validate(string password, string email = null) { PasswordValidationResult result = new PasswordValidationResult { IsValid = true }; if (string.IsNullOrEmpty(password)) { result.IsValid = false; result.Errors.Add("Password is required."); return result; } int minLength = GetMinLength(); if (password.Length < minLength) { result.IsValid = false; result.Errors.Add("Password must be at least " + minLength + " characters."); } if (!password.Any(char.IsUpper)) { result.IsValid = false; result.Errors.Add("Password must include an upper case letter."); } if (!password.Any(char.IsLower)) { result.IsValid = false; result.Errors.Add("Password must include a lower case letter."); } if (!password.Any(char.IsDigit)) { result.IsValid = false; result.Errors.Add("Password must include a digit."); } if (!password.Any(IsSpecialCharacter)) { result.IsValid = false; result.Errors.Add("Password must include a special character."); } if (CommonPasswords.Contains(password)) { result.IsValid = false; result.Errors.Add("Password is too common."); } string emailLocalPart = GetEmailLocalPart(email); if (!string.IsNullOrEmpty(emailLocalPart) && emailLocalPart.Length >= 3 && password.IndexOf(emailLocalPart, StringComparison.OrdinalIgnoreCase) >= 0) { result.IsValid = false; result.Errors.Add("Password must not contain your email address."); } return result; } public static void Enforce(string password, string email = null) { PasswordValidationResult result = Validate(password, email); if (!result.IsValid) throw new InvalidOperationException(result.GetSummary()); } private static bool IsSpecialCharacter(char c) { return !char.IsLetterOrDigit(c); } private static string GetEmailLocalPart(string email) { if (string.IsNullOrWhiteSpace(email)) return string.Empty; string trimmed = email.Trim(); int at = trimmed.IndexOf('@'); return at > 0 ? trimmed.Substring(0, at) : trimmed; } } }