diff --git a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs index b99a7ec..7789c7b 100644 --- a/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs +++ b/DotBased.AspNet.Authority/AuthorityProviderExtensions.cs @@ -21,9 +21,11 @@ public static class AuthorityProviderExtensions services.AddOptions(); services.Configure(optionsAction); } + services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped, PasswordOptionsValidator>(); + services.TryAddScoped, PasswordEqualsValidator>(); services.TryAddScoped, UserValidator>(); /*services.TryAddScoped(); services.TryAddScoped(); @@ -44,4 +46,20 @@ public static class AuthorityProviderExtensions { return builder; } + + private static Type GetBaseGenericArgumentType(Type baseType) + { + var userGenericBaseTypeDefinition = typeof(TModel).BaseType?.GetGenericTypeDefinition(); + if (userGenericBaseTypeDefinition != null && userGenericBaseTypeDefinition == baseType) + { + var userBaseGenericArguments = userGenericBaseTypeDefinition.GetGenericArguments(); + if (userBaseGenericArguments.Length <= 0) + { + throw new ArgumentException("Base implementation does not have the required generic argument.", nameof(TModel)); + } + + return userBaseGenericArguments[0]; + } + throw new ArgumentException($"Given object {typeof(TModel).Name} does not have the base implementation type of: {baseType.Name}", nameof(TModel)); + } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs index bbc919f..7f7f87a 100644 --- a/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs +++ b/DotBased.AspNet.Authority/Models/Authority/AuthorityUser.cs @@ -16,10 +16,13 @@ public class AuthorityUser : AuthorityUser } } -public abstract class AuthorityUser where TKey : IEquatable +public abstract class AuthorityUser : AuthorityUserBase where TKey : IEquatable { public TKey Id { get; set; } - +} + +public abstract class AuthorityUserBase +{ public bool Enabled { get; set; } public bool Confirmed { get; set; } diff --git a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs index 23c4c6e..0aa9893 100644 --- a/DotBased.AspNet.Authority/Repositories/IUserRepository.cs +++ b/DotBased.AspNet.Authority/Repositories/IUserRepository.cs @@ -1,9 +1,9 @@ namespace DotBased.AspNet.Authority.Repositories; -public interface IUserRepository where TUser : class where TId : IEquatable +public interface IUserRepository where TUser : class { - public Task GetUserByIdAsync(TId id); - public Task GetUserIdAsync(TUser user); + public Task GetUserByIdAsync(string id); + public Task GetUserIdAsync(TUser user); public Task SetVersion(TUser user, long version); public Task SetSecurityVersion(TUser user, long version); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Services/AuthorityManager.cs b/DotBased.AspNet.Authority/Services/AuthorityManager.cs index 2cf9d19..d5e607a 100644 --- a/DotBased.AspNet.Authority/Services/AuthorityManager.cs +++ b/DotBased.AspNet.Authority/Services/AuthorityManager.cs @@ -32,6 +32,7 @@ public class AuthorityManager public long GenerateVersion() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + /// /// Protect or unprotect the properties with the diff --git a/DotBased.AspNet.Authority/Services/AuthorityUserManager.cs b/DotBased.AspNet.Authority/Services/AuthorityUserManager.cs index a02c8f3..a874760 100644 --- a/DotBased.AspNet.Authority/Services/AuthorityUserManager.cs +++ b/DotBased.AspNet.Authority/Services/AuthorityUserManager.cs @@ -1,17 +1,24 @@ +using DotBased.AspNet.Authority.Crypto; +using DotBased.AspNet.Authority.Models.Validation; +using DotBased.AspNet.Authority.Repositories; using DotBased.AspNet.Authority.Validators; using DotBased.Logging; namespace DotBased.AspNet.Authority.Services; -public class AuthorityUserManager +public class AuthorityUserManager where TUser : class { public AuthorityUserManager( AuthorityManager manager, + IUserRepository userRepository, + IPasswordHasher passwordHasher, IEnumerable>? passwordValidators, IEnumerable>? userValidators) { _logger = LogService.RegisterLogger>(); AuthorityManager = manager; + UserRepository = userRepository; + PasswordHasher = passwordHasher; if (passwordValidators != null) PasswordValidators = passwordValidators; if (userValidators != null) @@ -20,9 +27,24 @@ public class AuthorityUserManager private readonly ILogger _logger; public AuthorityManager AuthorityManager { get; } + public IUserRepository UserRepository { get; } + + public IPasswordHasher PasswordHasher { get; } public IEnumerable> PasswordValidators { get; } = []; public IEnumerable> UserValidators { get; } = []; - - + + public async Task ValidatePasswordAsync(TUser user, string password) + { + List errors = []; + foreach (var validator in PasswordValidators) + { + var validatorResult = await validator.ValidatePasswordAsync(this, user, password); + if (!validatorResult.Success) + { + errors.AddRange(validatorResult.Errors); + } + } + return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); + } } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs index 07590ae..035038d 100644 --- a/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs +++ b/DotBased.AspNet.Authority/Validators/IPasswordValidator.cs @@ -3,7 +3,7 @@ using DotBased.AspNet.Authority.Services; namespace DotBased.AspNet.Authority.Validators; -public interface IPasswordValidator +public interface IPasswordValidator where TUser : class { - public Task ValidatePasswordAsync(AuthorityUserManager userManager, string password); + public Task ValidatePasswordAsync(AuthorityUserManager userManager, TUser user, string password); } \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs new file mode 100644 index 0000000..1d5071a --- /dev/null +++ b/DotBased.AspNet.Authority/Validators/PasswordEqualsValidator.cs @@ -0,0 +1,27 @@ +using DotBased.AspNet.Authority.Models.Authority; +using DotBased.AspNet.Authority.Models.Validation; +using DotBased.AspNet.Authority.Services; + +namespace DotBased.AspNet.Authority.Validators; + +public class PasswordEqualsValidator : IPasswordValidator where TUser : class +{ + private const string ValidatorId = "Authority.Validator.Password.Equals"; + private const string ValidationBase = "Authority.Validation.Password"; + public async Task ValidatePasswordAsync(AuthorityUserManager userManager, TUser user, string password) + { + if (user == null || user is not AuthorityUserBase authorityUser) + { + throw new ArgumentException("Invalid user given!", nameof(user)); + } + + List errors = []; + var hashedPassword = await userManager.PasswordHasher.HashPasswordAsync(password); + if (authorityUser.PasswordHash != null && authorityUser.PasswordHash.Equals(hashedPassword, StringComparison.Ordinal)) + { + errors.Add(new ValidationError(ValidatorId, $"{ValidationBase}.InUse", "User uses this password already!")); + } + + return errors.Count > 0 ? ValidationResult.Failed(errors) : ValidationResult.Ok(); + } +} \ No newline at end of file diff --git a/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs index 1360d3e..6bfcad0 100644 --- a/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs +++ b/DotBased.AspNet.Authority/Validators/PasswordOptionsValidator.cs @@ -8,12 +8,12 @@ namespace DotBased.AspNet.Authority.Validators; /// Validates the password against the options that is configured. /// /// The user model used. -public class PasswordOptionsValidator : IPasswordValidator +public class PasswordOptionsValidator : IPasswordValidator where TUser : class { private const string ValidatorId = "Authority.Validator.Password.Options"; private const string ValidationBase = "Authority.Validation.Password"; - public async Task ValidatePasswordAsync(AuthorityUserManager userManager, string password) + public async Task ValidatePasswordAsync(AuthorityUserManager userManager, TUser user, string password) { if (userManager == null) {