From 14d9b454134dfed848cf82f01d787ca9ee038762 Mon Sep 17 00:00:00 2001 From: csimonapastore Date: Mon, 16 Jun 2025 22:43:02 +0200 Subject: [PATCH] Added strong password validation --- MainProject.Tests/MainProject.Tests.csproj | 13 ++-- MainProject.Tests/Utils/PasswordUtils_Test.cs | 62 +++++++++++++++++++ MainProject/Enum/PasswordValidationEnum.cs | 10 +++ MainProject/MainProject.csproj | 18 +++--- MainProject/Utils/PasswordUtils.cs | 60 ++++++++++++++++++ 5 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 MainProject.Tests/Utils/PasswordUtils_Test.cs create mode 100644 MainProject/Enum/PasswordValidationEnum.cs create mode 100644 MainProject/Utils/PasswordUtils.cs diff --git a/MainProject.Tests/MainProject.Tests.csproj b/MainProject.Tests/MainProject.Tests.csproj index 45f0343..b083669 100644 --- a/MainProject.Tests/MainProject.Tests.csproj +++ b/MainProject.Tests/MainProject.Tests.csproj @@ -10,12 +10,15 @@ - - - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + - - + + diff --git a/MainProject.Tests/Utils/PasswordUtils_Test.cs b/MainProject.Tests/Utils/PasswordUtils_Test.cs new file mode 100644 index 0000000..2f2d6e6 --- /dev/null +++ b/MainProject.Tests/Utils/PasswordUtils_Test.cs @@ -0,0 +1,62 @@ +using BasicDotnetTemplate.MainProject.Utils; +using BasicDotnetTemplate.MainProject.Models.Common; +using BasicDotnetTemplate.MainProject.Enum; + + +namespace BasicDotnetTemplate.MainProject.Tests; + +[TestClass] +public class PasswordUtils_Test +{ + [TestMethod] + public void PasswordValidation_Valid() + { + try + { + List errors = PasswordUtils.ValidatePassword("#aBcDeFgHi01245#"); + Assert.IsTrue(errors == null || errors.Count == 0); + } + catch (Exception exception) + { + Assert.Fail($"An exception was thrown: {exception}"); + } + } + + [TestMethod] + public void PasswordValidation_Invalid() + { + try + { + List errors = PasswordUtils.ValidatePassword("aAa1#"); + Assert.IsTrue(errors.Contains(PasswordValidationEnum.MIN_LENGTH)); + Assert.IsTrue(errors.Contains(PasswordValidationEnum.MIN_UPPER)); + Assert.IsTrue(errors.Contains(PasswordValidationEnum.MIN_NUMBER)); + Assert.IsTrue(errors.Contains(PasswordValidationEnum.MIN_SPECIAL)); + Assert.IsTrue(errors.Contains(PasswordValidationEnum.IDENTICAL_CHARS)); + Assert.IsTrue(!errors.Contains(PasswordValidationEnum.MIN_LOWER)); + } + catch (Exception exception) + { + Assert.Fail($"An exception was thrown: {exception}"); + } + } + + [TestMethod] + public void PasswordValidation_ToLowerInvalid() + { + try + { + List errors = PasswordUtils.ValidatePassword("AaBC0*TGH1#"); + Assert.IsTrue(errors.Contains(PasswordValidationEnum.MIN_LOWER)); + } + catch (Exception exception) + { + Assert.Fail($"An exception was thrown: {exception}"); + } + } + +} + + + + diff --git a/MainProject/Enum/PasswordValidationEnum.cs b/MainProject/Enum/PasswordValidationEnum.cs new file mode 100644 index 0000000..b821805 --- /dev/null +++ b/MainProject/Enum/PasswordValidationEnum.cs @@ -0,0 +1,10 @@ +namespace BasicDotnetTemplate.MainProject.Enum; +public static class PasswordValidationEnum +{ + public const string MIN_LENGTH = "Password must be at least 8 characters long"; + public const string MIN_UPPER = "Password must have at least 2 uppercase letters"; + public const string MIN_LOWER = "Password must have at least 2 lowercase letters"; + public const string MIN_NUMBER = "Password must be at least 2 numbers"; + public const string MIN_SPECIAL = "Password must be at least 2 special characters"; + public const string IDENTICAL_CHARS = "Password cannot have 3 or more consecutive identical characters"; +} diff --git a/MainProject/MainProject.csproj b/MainProject/MainProject.csproj index 24c681d..801b05c 100644 --- a/MainProject/MainProject.csproj +++ b/MainProject/MainProject.csproj @@ -30,21 +30,21 @@ all - + - - + + - - + + - - - - + + + + diff --git a/MainProject/Utils/PasswordUtils.cs b/MainProject/Utils/PasswordUtils.cs new file mode 100644 index 0000000..25e28e1 --- /dev/null +++ b/MainProject/Utils/PasswordUtils.cs @@ -0,0 +1,60 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using BasicDotnetTemplate.MainProject.Enum; +using BasicDotnetTemplate.MainProject.Models.Settings; + +namespace BasicDotnetTemplate.MainProject.Utils; +public partial class PasswordUtils +{ + private const int MIN_LENGTH = 8; + private const int MIN_UPPER = 2; + private const int MIN_LOWER = 2; + private const int MIN_NUMBER = 2; + private const int MIN_SPECIAL = 2; + + [GeneratedRegex("[A-Z]")] + private static partial Regex RegexUpper(); + + [GeneratedRegex("[a-z]")] + private static partial Regex RegexLower(); + + [GeneratedRegex("[0-9]")] + private static partial Regex RegexNumber(); + + [GeneratedRegex("[^a-zA-Z0-9]")] + private static partial Regex RegexSpecial(); + + [GeneratedRegex(@"(\S)\1{2,}", RegexOptions.IgnoreCase | RegexOptions.Compiled)] + private static partial Regex RegexIdenticalChars(); + + public static List ValidatePassword(string password) + { + List errors = []; + + if (password.Length < 8) + errors.Add(PasswordValidationEnum.MIN_LENGTH); + + if (RegexUpper().Matches(password).Count < MIN_UPPER) + errors.Add(PasswordValidationEnum.MIN_UPPER); + + if (RegexLower().Matches(password).Count < MIN_LOWER) + errors.Add(PasswordValidationEnum.MIN_LOWER); + + if (RegexNumber().Matches(password).Count < MIN_NUMBER) + errors.Add(PasswordValidationEnum.MIN_NUMBER); + + if (RegexSpecial().Matches(password).Count < MIN_SPECIAL) + errors.Add(PasswordValidationEnum.MIN_SPECIAL); + + if (RegexIdenticalChars().IsMatch(password)) + errors.Add(PasswordValidationEnum.IDENTICAL_CHARS); + + return errors; + } + + +} +