using Microsoft.AspNetCore.Identity; namespace Data.SeaHavenIndustries { /// /// The single password rule for every surface that sets a password: at least /// six characters with one uppercase letter, one number, and one special /// character. Lowercase letters are deliberately not required so the server /// accepts exactly what the four-item checklist in the web app marks as met. /// public static class IdentityPasswordPolicy { public const int MinimumLength = 6; public static void Apply(PasswordOptions options) { ArgumentNullException.ThrowIfNull(options); options.RequiredLength = MinimumLength; options.RequireUppercase = true; options.RequireDigit = true; options.RequireNonAlphanumeric = true; options.RequireLowercase = false; options.RequiredUniqueChars = 1; } private static readonly HashSet PolicyErrorCodes = new(StringComparer.Ordinal) { nameof(IdentityErrorDescriber.PasswordTooShort), nameof(IdentityErrorDescriber.PasswordRequiresUpper), nameof(IdentityErrorDescriber.PasswordRequiresLower), nameof(IdentityErrorDescriber.PasswordRequiresDigit), nameof(IdentityErrorDescriber.PasswordRequiresNonAlphanumeric), nameof(IdentityErrorDescriber.PasswordRequiresUniqueChars) }; /// /// True when Identity refused the password itself. Other failures, such as a /// concurrency conflict, must not be reported to the user as a weak password. /// public static bool IsPolicyRejection(IdentityResult result) { ArgumentNullException.ThrowIfNull(result); return result.Errors.Any(error => PolicyErrorCodes.Contains(error.Code)); } } }