diff --git a/README.md b/README.md index 92c5bd4..5faf44d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # Wilcommerce.Auth -Wilcommerce Authentication and Authorization package +Wilcommerce Authentication and Authorization package.
+It uses AspNetCore Identity framework to manage sign in and user persistence. ## Installation Nuget package available here [https://www.nuget.org/packages/Wilcommerce.Auth](https://www.nuget.org/packages/Wilcommerce.Auth) ## Models -The **Models** namespace contains all the classes representing the components used for creating authentication tokens. +The **Models** namespace contains the user class. ## Read models This namespace contains the interface which gives a readonly access to the components. @@ -16,9 +17,6 @@ The **Services** namespace contains a set of components which gives a simple acc ## Commands **Commands** namespace contains all the actions available on this package. -## Repository -This namespace contains the interface which defines the persistence of the components. - ## Events In the **Events** namespace are defined all the events that could happen after an action made. diff --git a/Wilcommerce.Auth.Test/Models/UserTest.cs b/Wilcommerce.Auth.Test/Models/UserTest.cs new file mode 100644 index 0000000..b2896a9 --- /dev/null +++ b/Wilcommerce.Auth.Test/Models/UserTest.cs @@ -0,0 +1,129 @@ +using System; +using Wilcommerce.Auth.Models; +using Xunit; + +namespace Wilcommerce.Auth.Test.Models +{ + public class UserTest + { + #region Administrator tests + [Theory] + [InlineData(null)] + [InlineData("")] + public void AdministratorFactory_Should_Throw_ArgumentNull_Exception_If_Name_IsEmpty(string value) + { + var ex = Assert.Throws(() => User.CreateAsAdministrator( + value, + "admin@email.com", + true)); + + Assert.Equal("name", ex.ParamName); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void AdministratorFactory_Should_Throw_ArgumentNull_Exception_If_Email_IsEmpty(string value) + { + var ex = Assert.Throws(() => User.CreateAsAdministrator( + "Administrator", + value, + true)); + + Assert.Equal("email", ex.ParamName); + } + + #endregion + + #region Customer tests + [Theory] + [InlineData(null)] + [InlineData("")] + public void CustomerFactory_Should_Throw_ArgumentNull_Exception_If_Name_IsEmpty(string value) + { + var ex = Assert.Throws(() => User.CreateAsCustomer( + value, + "customer@email.com")); + + Assert.Equal("name", ex.ParamName); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void CustomerFactory_Should_Throw_ArgumentNull_Exception_If_Email_IsEmpty(string value) + { + var ex = Assert.Throws(() => User.CreateAsCustomer( + "Customer", + value)); + + Assert.Equal("email", ex.ParamName); + } + #endregion + + [Fact] + public void Enable_Should_Active_User_And_Set_Date_To_Null() + { + var user = User.CreateAsAdministrator( + "Admin", + "admin@email.com", + false); + + user.Enable(); + + Assert.True(user.IsActive); + Assert.Null(user.DisabledOn); + } + + [Fact] + public void Disable_Should_Set_Date_To_Now() + { + var user = User.CreateAsAdministrator( + "Admin", + "admin@email.com", + true); + + user.Disable(); + + Assert.False(user.IsActive); + Assert.Equal(DateTime.Today, ((DateTime)user.DisabledOn).Date); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void ChangeName_Should_Throw_ArgumentNullException_If_Name_IsEmpty(string value) + { + var user = User.CreateAsAdministrator( + "Admin", + "admin@email.com", + true); + + var ex = Assert.Throws(() => user.ChangeName(value)); + Assert.Equal("name", ex.ParamName); + } + + [Fact] + public void SetProfileImage_Should_Throw_ArgumentNullException_If_Image_IsNull() + { + var user = User.CreateAsAdministrator( + "Admin", + "admin@email.com", + true); + + var ex = Assert.Throws(() => user.SetProfileImage(null)); + Assert.Equal("profileImage", ex.ParamName); + } + + [Fact] + public void Constructor_Should_Initialize_Empty_ProfileImage() + { + var admin = User.CreateAsAdministrator( + "Administrator", + "admin@email.com", + true); + + Assert.NotNull(admin.ProfileImage); + } + } +} diff --git a/Wilcommerce.Auth.Test/Models/UserTokenTest.cs b/Wilcommerce.Auth.Test/Models/UserTokenTest.cs deleted file mode 100644 index 35063b5..0000000 --- a/Wilcommerce.Auth.Test/Models/UserTokenTest.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Moq; -using System; -using Wilcommerce.Auth.Models; -using Wilcommerce.Core.Common.Domain.Models; -using Xunit; - -namespace Wilcommerce.Auth.Test.Models -{ - public class UserTokenTest - { - [Fact] - public void PasswordRecovery_Should_Throw_ArgumentNullException_If_User_IsNull() - { - var ex = Assert.Throws(() => UserToken.PasswordRecovery(null, "", DateTime.Now)); - Assert.Equal("user", ex.ParamName); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - public void PasswordRecovery_Should_Throw_ArgumentNullException_If_Token_IsEmpty(string value) - { - var user = User.CreateAsAdministrator("Admin", "admin@admin.com", "password", new Mock>().Object); - - var ex = Assert.Throws(() => UserToken.PasswordRecovery(user, value, DateTime.Now)); - Assert.Equal("token", ex.ParamName); - } - - [Fact] - public void PasswordRecovery_Should_Throw_ArgumentException_If_ExpirationDate_IsPreviousThan_Now() - { - var user = User.CreateAsAdministrator("Admin", "admin@admin.com", "password", new Mock>().Object); - string token = "token"; - - var ex = Assert.Throws(() => UserToken.PasswordRecovery(user, token, DateTime.Now.AddDays(-1))); - Assert.Equal("expirationDate", ex.ParamName); - } - - [Fact] - public void PasswordRecovery_Should_Create_A_PasswordRecovery_Token() - { - var user = User.CreateAsAdministrator("Admin", "admin@admin.com", "password", new Mock>().Object); - string token = "token"; - var expirationDate = DateTime.Now.AddDays(10); - - var userToken = UserToken.PasswordRecovery(user, token, expirationDate); - Assert.Equal(TokenTypes.PasswordRecovery, userToken.TokenType); - } - - [Fact] - public void Registration_Should_Throw_ArgumentNullException_If_User_IsNull() - { - var ex = Assert.Throws(() => UserToken.Registration(null, "", DateTime.Now)); - Assert.Equal("user", ex.ParamName); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - public void Registration_Should_Throw_ArgumentNullException_If_Token_IsEmpty(string value) - { - var user = User.CreateAsAdministrator("Admin", "admin@admin.com", "password", new Mock>().Object); - - var ex = Assert.Throws(() => UserToken.Registration(user, value, DateTime.Now)); - Assert.Equal("token", ex.ParamName); - } - - [Fact] - public void Registration_Should_Throw_ArgumentException_If_ExpirationDate_IsPreviousThan_Now() - { - var user = User.CreateAsAdministrator("Admin", "admin@admin.com", "password", new Mock>().Object); - string token = "token"; - - var ex = Assert.Throws(() => UserToken.Registration(user, token, DateTime.Now.AddDays(-1))); - Assert.Equal("expirationDate", ex.ParamName); - } - - [Fact] - public void Registration_Should_Create_A_Registration_Token() - { - var user = User.CreateAsAdministrator("Admin", "admin@admin.com", "password", new Mock>().Object); - string token = "token"; - var expirationDate = DateTime.Now.AddDays(10); - - var userToken = UserToken.Registration(user, token, expirationDate); - Assert.Equal(TokenTypes.Registration, userToken.TokenType); - } - - [Fact] - public void SetAsExpired_Should_Throw_InvalidOperationException_If_Token_Is_Already_Expired() - { - var user = User.CreateAsAdministrator("Admin", "admin@admin.com", "password", new Mock>().Object); - string token = "token"; - var expirationDate = DateTime.Now.AddDays(10); - - var userToken = UserToken.Registration(user, token, expirationDate); - userToken.SetAsExpired(); - - var ex = Assert.Throws(() => userToken.SetAsExpired()); - Assert.Equal($"Token already expired on {userToken.ExpirationDate.ToString()}", ex.Message); - } - - [Fact] - public void SetAsExpired_Should_Set_ExpirationDate_To_Today() - { - var user = User.CreateAsAdministrator("Admin", "admin@admin.com", "password", new Mock>().Object); - string token = "token"; - var expirationDate = DateTime.Now.AddDays(10); - - var userToken = UserToken.Registration(user, token, expirationDate); - userToken.SetAsExpired(); - - Assert.True(userToken.IsExpired); - Assert.Equal(DateTime.Now.ToString("yyyy-MM-dd"), userToken.ExpirationDate.ToString("yyyy-MM-dd")); - } - } -} diff --git a/Wilcommerce.Auth.Test/Services/IdentityFactoryTest.cs b/Wilcommerce.Auth.Test/Services/IdentityFactoryTest.cs deleted file mode 100644 index dd4846c..0000000 --- a/Wilcommerce.Auth.Test/Services/IdentityFactoryTest.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Moq; -using System.Security.Claims; -using Wilcommerce.Auth.Services; -using Wilcommerce.Auth.Services.Interfaces; -using Wilcommerce.Core.Common.Domain.Models; -using Xunit; - -namespace Wilcommerce.Auth.Test.Services -{ - public class IdentityFactoryTest - { - private IIdentityFactory _factory; - - public IdentityFactoryTest() - { - _factory = new IdentityFactory(); - } - - [Fact] - public void CreateIdentity_Identity_Name_Must_Match_User_Email() - { - var user = User.CreateAsAdministrator("User", "admin@admin.com", "1234", new Mock>().Object); - var principal = _factory.CreateIdentity(user); - - Assert.Equal(user.Email, principal.Identity.Name); - } - - [Fact] - public void AdministratorUser_Must_Have_Administrator_As_Role() - { - var user = User.CreateAsAdministrator("User", "admin@admin.com", "1234", new Mock>().Object); - var principal = _factory.CreateIdentity(user); - - Assert.True(principal.IsInRole(AuthenticationDefaults.AdministratorRole)); - } - - [Fact] - public void CustomerUser_Must_Have_Customer_As_Role() - { - var user = User.CreateAsCustomer("Customer", "customer@customer.com", "1234", new Mock>().Object); - var principal = _factory.CreateIdentity(user); - - Assert.True(principal.IsInRole(AuthenticationDefaults.CustomerRole)); - } - - [Fact] - public void CreateIdentity_NameIdentifier_Must_Match_User_Id() - { - var user = User.CreateAsAdministrator("User", "admin@admin.com", "1234", new Mock>().Object); - var principal = _factory.CreateIdentity(user); - - Assert.Equal(principal.FindFirstValue(ClaimTypes.NameIdentifier), user.Id.ToString()); - } - - [Fact] - public void CreateIdentity_Email_Must_Match_User_Email() - { - var user = User.CreateAsAdministrator("User", "admin@admin.com", "1234", new Mock>().Object); - var principal = _factory.CreateIdentity(user); - - Assert.Equal(principal.FindFirstValue(ClaimTypes.Email), user.Email); - } - - [Fact] - public void CreateIdentity_GivenName_Must_Match_User_Name() - { - var user = User.CreateAsAdministrator("User", "admin@admin.com", "1234", new Mock>().Object); - var principal = _factory.CreateIdentity(user); - - Assert.Equal(principal.FindFirstValue(ClaimTypes.GivenName), user.Name); - } - } -} diff --git a/Wilcommerce.Auth.Test/Wilcommerce.Auth.Test.csproj b/Wilcommerce.Auth.Test/Wilcommerce.Auth.Test.csproj index 5f6743a..edd7f44 100644 --- a/Wilcommerce.Auth.Test/Wilcommerce.Auth.Test.csproj +++ b/Wilcommerce.Auth.Test/Wilcommerce.Auth.Test.csproj @@ -17,10 +17,10 @@ - - - - + + + + diff --git a/src/Wilcommerce.Auth/Commands/Handlers/Interfaces/IRecoverPasswordCommandHandler.cs b/src/Wilcommerce.Auth/Commands/Handlers/Interfaces/IRecoverPasswordCommandHandler.cs deleted file mode 100644 index 00e2b62..0000000 --- a/src/Wilcommerce.Auth/Commands/Handlers/Interfaces/IRecoverPasswordCommandHandler.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Wilcommerce.Core.Infrastructure; - -namespace Wilcommerce.Auth.Commands.Handlers.Interfaces -{ - /// - /// Performs the password recovery action - /// - public interface IRecoverPasswordCommandHandler : ICommandHandlerAsync - { - } -} diff --git a/src/Wilcommerce.Auth/Commands/Handlers/Interfaces/IValidatePasswordRecoveryCommandHandler.cs b/src/Wilcommerce.Auth/Commands/Handlers/Interfaces/IValidatePasswordRecoveryCommandHandler.cs deleted file mode 100644 index cbbadad..0000000 --- a/src/Wilcommerce.Auth/Commands/Handlers/Interfaces/IValidatePasswordRecoveryCommandHandler.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Wilcommerce.Core.Infrastructure; - -namespace Wilcommerce.Auth.Commands.Handlers.Interfaces -{ - /// - /// Performs the validation for the password recovery request - /// - public interface IValidatePasswordRecoveryCommandHandler : ICommandHandlerAsync - { - } -} diff --git a/src/Wilcommerce.Auth/Commands/Handlers/RecoverPasswordCommandHandler.cs b/src/Wilcommerce.Auth/Commands/Handlers/RecoverPasswordCommandHandler.cs deleted file mode 100644 index a6a2717..0000000 --- a/src/Wilcommerce.Auth/Commands/Handlers/RecoverPasswordCommandHandler.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Threading.Tasks; -using Wilcommerce.Auth.Events.User; -using Wilcommerce.Auth.Models; -using Wilcommerce.Auth.Repository; - -namespace Wilcommerce.Auth.Commands.Handlers -{ - /// - /// Implementation of - /// - public class RecoverPasswordCommandHandler : Interfaces.IRecoverPasswordCommandHandler - { - /// - /// Get the event bus - /// - public Core.Infrastructure.IEventBus EventBus { get; } - - /// - /// Get the authentication repository - /// - public IRepository Repository { get; } - - /// - /// Construct the command handler - /// - /// The repository instance - /// The event bus instance - public RecoverPasswordCommandHandler(IRepository repository, Core.Infrastructure.IEventBus eventBus) - { - Repository = repository ?? throw new ArgumentNullException(nameof(repository)); - EventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); - } - - /// - /// Recover the password for the user - /// - /// The command to execute - /// - public async Task Handle(RecoverPasswordCommand command) - { - try - { - var userToken = UserToken.PasswordRecovery(command.UserInfo, command.Token, DateTime.Now.AddDays(AuthenticationDefaults.ExpirationDays)); - - Repository.Add(userToken); - await Repository.SaveChangesAsync(); - - var @event = new PasswordRecoveryRequestedEvent(userToken.UserId, command.UserInfo.Email, userToken.Id, userToken.Token, userToken.ExpirationDate); - EventBus.RaiseEvent(@event); - } - catch - { - throw; - } - } - } -} diff --git a/src/Wilcommerce.Auth/Commands/Handlers/ValidatePasswordRecoveryCommandHandler.cs b/src/Wilcommerce.Auth/Commands/Handlers/ValidatePasswordRecoveryCommandHandler.cs deleted file mode 100644 index af624f1..0000000 --- a/src/Wilcommerce.Auth/Commands/Handlers/ValidatePasswordRecoveryCommandHandler.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Wilcommerce.Auth.Events.User; -using Wilcommerce.Auth.Models; -using Wilcommerce.Auth.ReadModels; -using Wilcommerce.Auth.Repository; - -namespace Wilcommerce.Auth.Commands.Handlers -{ - /// - /// Implementation of - /// - public class ValidatePasswordRecoveryCommandHandler : Interfaces.IValidatePasswordRecoveryCommandHandler - { - /// - /// Get the authentication database - /// - public IAuthDatabase Database { get; } - - /// - /// Get the authentication repository - /// - public IRepository Repository { get; } - - /// - /// Get the event bus - /// - public Core.Infrastructure.IEventBus EventBus { get; } - - /// - /// Construct the command handler - /// - /// The database instance - /// The repository instance - /// The event bus instance - public ValidatePasswordRecoveryCommandHandler(IAuthDatabase database, IRepository repository, Core.Infrastructure.IEventBus eventBus) - { - Database = database ?? throw new ArgumentNullException(nameof(database)); - Repository = repository ?? throw new ArgumentNullException(nameof(repository)); - EventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); - } - - /// - /// Validate the password recovery request - /// - /// The command to execute - /// - public async Task Handle(ValidatePasswordRecoveryCommand command) - { - try - { - var userToken = Database.Tokens - .NotExpired() - .ByTokenType(TokenTypes.PasswordRecovery) - .FirstOrDefault(t => t.Token == command.Token); - - if (userToken == null) - { - throw new InvalidOperationException("Invalid token"); - } - - var token = await Repository.GetByKeyAsync(userToken.Id); - token.SetAsExpired(); - - await Repository.SaveChangesAsync(); - - var @event = new PasswordRecoveryValidatedEvent(userToken.UserId, userToken.Token); - EventBus.RaiseEvent(@event); - } - catch - { - throw; - } - } - } -} diff --git a/src/Wilcommerce.Auth/Commands/RecoverPasswordCommand.cs b/src/Wilcommerce.Auth/Commands/RecoverPasswordCommand.cs deleted file mode 100644 index fbd8066..0000000 --- a/src/Wilcommerce.Auth/Commands/RecoverPasswordCommand.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Wilcommerce.Core.Common.Domain.Models; -using Wilcommerce.Core.Infrastructure; - -namespace Wilcommerce.Auth.Commands -{ - /// - /// Recover the user password - /// - public class RecoverPasswordCommand : ICommand - { - /// - /// Get the user information - /// - public User UserInfo { get; } - - /// - /// Get the recovery token - /// - public string Token { get; } - - /// - /// Construct the command - /// - /// The user information - /// The recovery token - public RecoverPasswordCommand(User user, string token) - { - UserInfo = user; - Token = token; - } - } -} diff --git a/src/Wilcommerce.Auth/Commands/User/ChangeUserInfoCommand.cs b/src/Wilcommerce.Auth/Commands/User/ChangeUserInfoCommand.cs new file mode 100644 index 0000000..389756a --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/ChangeUserInfoCommand.cs @@ -0,0 +1,40 @@ +using System; +using Wilcommerce.Core.Common.Models; +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Commands.User +{ + /// + /// Update the user information + /// + public class ChangeUserInfoCommand : ICommand + { + /// + /// Get the user's id + /// + public Guid UserId { get; } + + /// + /// Get the user's name + /// + public string Name { get; } + + /// + /// Get the user's profile image + /// + public Image ProfileImage { get; } + + /// + /// Construct the change user info command + /// + /// The user id + /// The user name + /// The user profile image + public ChangeUserInfoCommand(Guid userId, string name, Image profileImage) + { + UserId = userId; + Name = name; + ProfileImage = profileImage; + } + } +} diff --git a/src/Wilcommerce.Auth/Commands/User/CreateNewAdministratorCommand.cs b/src/Wilcommerce.Auth/Commands/User/CreateNewAdministratorCommand.cs new file mode 100644 index 0000000..f635cab --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/CreateNewAdministratorCommand.cs @@ -0,0 +1,45 @@ +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Commands.User +{ + /// + /// Create a new administrator + /// + public class CreateNewAdministratorCommand : ICommand + { + /// + /// Get the administrator name + /// + public string Name { get; } + + /// + /// Get the administrator email + /// + public string Email { get; } + + /// + /// Get the administrator password + /// + public string Password { get; } + + /// + /// Get whether the administrator is active + /// + public bool IsActive { get; } + + /// + /// Construct the command + /// + /// The adminsitrator name + /// The adminsitrator email + /// The adminsitrator password + /// Whether the user is active + public CreateNewAdministratorCommand(string name, string email, string password, bool isActive) + { + Name = name; + Email = email; + Password = password; + IsActive = isActive; + } + } +} diff --git a/src/Wilcommerce.Auth/Commands/User/DisableUserCommand.cs b/src/Wilcommerce.Auth/Commands/User/DisableUserCommand.cs new file mode 100644 index 0000000..c7fd932 --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/DisableUserCommand.cs @@ -0,0 +1,25 @@ +using System; +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Commands.User +{ + /// + /// Disable the user + /// + public class DisableUserCommand : ICommand + { + /// + /// Get the user id + /// + public Guid UserId { get; } + + /// + /// Construct the command + /// + /// The user id + public DisableUserCommand(Guid userId) + { + UserId = UserId; + } + } +} diff --git a/src/Wilcommerce.Auth/Commands/User/EnableUserCommand.cs b/src/Wilcommerce.Auth/Commands/User/EnableUserCommand.cs new file mode 100644 index 0000000..4d7bede --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/EnableUserCommand.cs @@ -0,0 +1,25 @@ +using System; +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Commands.User +{ + /// + /// Enable the user + /// + public class EnableUserCommand : ICommand + { + /// + /// Get the user id + /// + public Guid UserId { get; } + + /// + /// Construct the command + /// + /// The user id + public EnableUserCommand(Guid userId) + { + UserId = UserId; + } + } +} diff --git a/src/Wilcommerce.Auth/Commands/User/Handlers/ChangeUserInfoCommandHandler.cs b/src/Wilcommerce.Auth/Commands/User/Handlers/ChangeUserInfoCommandHandler.cs new file mode 100644 index 0000000..aac497b --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/Handlers/ChangeUserInfoCommandHandler.cs @@ -0,0 +1,70 @@ +using Microsoft.AspNetCore.Identity; +using System; +using System.Threading.Tasks; +using Wilcommerce.Auth.Events.User; +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Commands.User.Handlers +{ + /// + /// Implementation of + /// + public class ChangeUserInfoCommandHandler : Interfaces.IChangeUserInfoCommandHandler + { + /// + /// Get the user manager instance + /// + public UserManager UserManager { get; } + + /// + /// Get the event bus instance + /// + public IEventBus EventBus { get; } + + /// + /// Construct the command handler + /// + /// The user manager + /// The event bus + public ChangeUserInfoCommandHandler(UserManager userManager, IEventBus eventBus) + { + UserManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); + EventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); + } + + /// + /// Change the user's information + /// + /// The command to execute + /// + public async Task Handle(ChangeUserInfoCommand command) + { + try + { + var user = await UserManager.FindByIdAsync(command.UserId.ToString()); + if (command.Name != user.Name) + { + user.ChangeName(command.Name); + } + + if (command.ProfileImage != user.ProfileImage) + { + user.SetProfileImage(command.ProfileImage); + } + + var result = await UserManager.UpdateAsync(user); + if (!result.Succeeded) + { + throw new ApplicationException("Error while changing the user's info"); + } + + var @event = new UserInfoChangedEvent(command.UserId, command.Name, command.ProfileImage); + EventBus.RaiseEvent(@event); + } + catch + { + throw; + } + } + } +} diff --git a/src/Wilcommerce.Auth/Commands/User/Handlers/CreateNewAdministratorCommandHandler.cs b/src/Wilcommerce.Auth/Commands/User/Handlers/CreateNewAdministratorCommandHandler.cs new file mode 100644 index 0000000..70495c0 --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/Handlers/CreateNewAdministratorCommandHandler.cs @@ -0,0 +1,70 @@ +using Microsoft.AspNetCore.Identity; +using System; +using System.Threading.Tasks; +using Wilcommerce.Auth.Events.User; +using Wilcommerce.Auth.Services.Interfaces; + +namespace Wilcommerce.Auth.Commands.User.Handlers +{ + /// + /// Implementation of + /// + public class CreateNewAdministratorCommandHandler : Interfaces.ICreateNewAdministratorCommandHandler + { + /// + /// Get the user mananager instance + /// + public UserManager UserManager { get; } + + /// + /// The event bus instance + /// + public Core.Infrastructure.IEventBus EventBus { get; } + + /// + /// Get the role factory instance + /// + public IRoleFactory RoleFactory { get; } + + /// + /// Construct the command handler + /// + /// The user manager + /// The event bus + /// The role factory + public CreateNewAdministratorCommandHandler(UserManager userManager, Core.Infrastructure.IEventBus eventBus, IRoleFactory roleFactory) + { + UserManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); + EventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); + RoleFactory = roleFactory ?? throw new ArgumentNullException(nameof(roleFactory)); + } + + /// + /// Create a new administrator + /// + /// The command to execute + /// + public async Task Handle(CreateNewAdministratorCommand command) + { + try + { + var administrator = Models.User.CreateAsAdministrator(command.Name, command.Email, command.IsActive); + var result = await UserManager.CreateAsync(administrator, command.Password); + if (!result.Succeeded) + { + throw new InvalidOperationException(string.Join(",", result.Errors)); + } + + var role = await RoleFactory.Administrator(); + await UserManager.AddToRoleAsync(administrator, role.Name); + + var @event = new NewAdministratorCreatedEvent(administrator.Id, administrator.Name, administrator.Email); + EventBus.RaiseEvent(@event); + } + catch + { + throw; + } + } + } +} diff --git a/src/Wilcommerce.Auth/Commands/User/Handlers/DisableUserCommandHandler.cs b/src/Wilcommerce.Auth/Commands/User/Handlers/DisableUserCommandHandler.cs new file mode 100644 index 0000000..a7a8746 --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/Handlers/DisableUserCommandHandler.cs @@ -0,0 +1,62 @@ +using Microsoft.AspNetCore.Identity; +using System; +using System.Threading.Tasks; +using Wilcommerce.Auth.Events.User; +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Commands.User.Handlers +{ + /// + /// Implementation of + /// + public class DisableUserCommandHandler : Interfaces.IDisableUserCommandHandler + { + /// + /// Get the user manager instance + /// + public UserManager UserManager { get; } + + /// + /// Get the event bus instance + /// + public IEventBus EventBus { get; } + + /// + /// Construct the command handler + /// + /// The user manager + /// The event bus + public DisableUserCommandHandler(UserManager userManager, IEventBus eventBus) + { + UserManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); + EventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); + } + + /// + /// Disable the user + /// + /// The command to execute + /// + public async Task Handle(DisableUserCommand command) + { + try + { + var user = await UserManager.FindByIdAsync(command.UserId.ToString()); + user.Disable(); + + var result = await UserManager.UpdateAsync(user); + if (!result.Succeeded) + { + throw new ApplicationException("Error while disabling the user"); + } + + var @event = new UserDisabledEvent(user.Id); + EventBus.RaiseEvent(@event); + } + catch + { + throw; + } + } + } +} diff --git a/src/Wilcommerce.Auth/Commands/User/Handlers/EnableUserCommandHandler.cs b/src/Wilcommerce.Auth/Commands/User/Handlers/EnableUserCommandHandler.cs new file mode 100644 index 0000000..46ed5b2 --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/Handlers/EnableUserCommandHandler.cs @@ -0,0 +1,62 @@ +using Microsoft.AspNetCore.Identity; +using System; +using System.Threading.Tasks; +using Wilcommerce.Auth.Events.User; +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Commands.User.Handlers +{ + /// + /// Implementation of + /// + public class EnableUserCommandHandler : Interfaces.IEnableUserCommandHandler + { + /// + /// Get the user manager instance + /// + public UserManager UserManager { get; } + + /// + /// Get the event bus instance + /// + public IEventBus EventBus { get; } + + /// + /// Construct the command handler + /// + /// The user manager + /// The event bus + public EnableUserCommandHandler(UserManager userManager, IEventBus eventBus) + { + UserManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); + EventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); + } + + /// + /// Enable the user + /// + /// The command to execute + /// + public async Task Handle(EnableUserCommand command) + { + try + { + var user = await UserManager.FindByIdAsync(command.UserId.ToString()); + user.Enable(); + + var result = await UserManager.UpdateAsync(user); + if (!result.Succeeded) + { + throw new ApplicationException("Error while enabling the user"); + } + + var @event = new UserEnabledEvent(user.Id); + EventBus.RaiseEvent(@event); + } + catch + { + throw; + } + } + } +} diff --git a/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/IChangeUserInfoCommandHandler.cs b/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/IChangeUserInfoCommandHandler.cs new file mode 100644 index 0000000..aa06cfb --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/IChangeUserInfoCommandHandler.cs @@ -0,0 +1,11 @@ +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Commands.User.Handlers.Interfaces +{ + /// + /// Handles the change of user's information + /// + public interface IChangeUserInfoCommandHandler : ICommandHandlerAsync + { + } +} diff --git a/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/ICreateNewAdministratorCommandHandler.cs b/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/ICreateNewAdministratorCommandHandler.cs new file mode 100644 index 0000000..0ea3de7 --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/ICreateNewAdministratorCommandHandler.cs @@ -0,0 +1,11 @@ +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Commands.User.Handlers.Interfaces +{ + /// + /// Handles the creation of a new administrator + /// + public interface ICreateNewAdministratorCommandHandler : ICommandHandlerAsync + { + } +} diff --git a/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/IDisableUserCommandHandler.cs b/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/IDisableUserCommandHandler.cs new file mode 100644 index 0000000..8457e6c --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/IDisableUserCommandHandler.cs @@ -0,0 +1,11 @@ +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Commands.User.Handlers.Interfaces +{ + /// + /// Handles the disabling of the user + /// + public interface IDisableUserCommandHandler : ICommandHandlerAsync + { + } +} diff --git a/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/IEnableUserCommandHandler.cs b/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/IEnableUserCommandHandler.cs new file mode 100644 index 0000000..f0751b6 --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/IEnableUserCommandHandler.cs @@ -0,0 +1,11 @@ +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Commands.User.Handlers.Interfaces +{ + /// + /// Handles the enabling of the user + /// + public interface IEnableUserCommandHandler : ICommandHandlerAsync + { + } +} diff --git a/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/IResetPasswordCommandHandler.cs b/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/IResetPasswordCommandHandler.cs new file mode 100644 index 0000000..655b74f --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/Handlers/Interfaces/IResetPasswordCommandHandler.cs @@ -0,0 +1,11 @@ +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Commands.User.Handlers.Interfaces +{ + /// + /// Handles the reset of user's password + /// + public interface IResetPasswordCommandHandler : ICommandHandlerAsync + { + } +} diff --git a/src/Wilcommerce.Auth/Commands/User/Handlers/ResetPasswordCommandHandler.cs b/src/Wilcommerce.Auth/Commands/User/Handlers/ResetPasswordCommandHandler.cs new file mode 100644 index 0000000..9e868d2 --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/Handlers/ResetPasswordCommandHandler.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Identity; +using System; +using System.Threading.Tasks; +using Wilcommerce.Auth.Events.User; +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Commands.User.Handlers +{ + /// + /// Implementation of + /// + public class ResetPasswordCommandHandler : Interfaces.IResetPasswordCommandHandler + { + /// + /// Get the user manager instance + /// + public UserManager UserManager { get; } + + /// + /// Get the event bus + /// + public IEventBus EventBus { get; } + + /// + /// Construct the command handler + /// + /// The user manager + /// The event bus + public ResetPasswordCommandHandler(UserManager userManager, IEventBus eventBus) + { + UserManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); + EventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); + } + + /// + /// Reset the user's password + /// + /// The command to execute + /// + public async Task Handle(ResetPasswordCommand command) + { + try + { + var user = await UserManager.FindByIdAsync(command.UserId.ToString()); + var result = await UserManager.ResetPasswordAsync(user, command.ResetToken, command.NewPassword); + + if (!result.Succeeded) + { + throw new ApplicationException("There was an error resetting the user password"); + } + + var @event = new UserPasswordResetEvent(command.UserId); + EventBus.RaiseEvent(@event); + } + catch + { + throw; + } + } + } +} diff --git a/src/Wilcommerce.Auth/Commands/User/ResetPasswordCommand.cs b/src/Wilcommerce.Auth/Commands/User/ResetPasswordCommand.cs new file mode 100644 index 0000000..b5d6e0f --- /dev/null +++ b/src/Wilcommerce.Auth/Commands/User/ResetPasswordCommand.cs @@ -0,0 +1,39 @@ +using System; +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Commands.User +{ + /// + /// Reset the user password + /// + public class ResetPasswordCommand : ICommand + { + /// + /// Get the user's id + /// + public Guid UserId { get; } + + /// + /// Get the reset token for the user + /// + public string ResetToken { get; } + + /// + /// Get the new user's password + /// + public string NewPassword { get; } + + /// + /// Construct the reset password command + /// + /// The user's id + /// The reset token + /// The new password + public ResetPasswordCommand(Guid userId, string resetToken, string newPassword) + { + UserId = userId; + ResetToken = resetToken; + NewPassword = newPassword; + } + } +} diff --git a/src/Wilcommerce.Auth/Commands/ValidatePasswordRecoveryCommand.cs b/src/Wilcommerce.Auth/Commands/ValidatePasswordRecoveryCommand.cs deleted file mode 100644 index addae1f..0000000 --- a/src/Wilcommerce.Auth/Commands/ValidatePasswordRecoveryCommand.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Wilcommerce.Core.Infrastructure; - -namespace Wilcommerce.Auth.Commands -{ - /// - /// Validate the password recovery request - /// - public class ValidatePasswordRecoveryCommand : ICommand - { - /// - /// Get the recovery token - /// - public string Token { get; } - - /// - /// Construct the command - /// - /// The recovery token - public ValidatePasswordRecoveryCommand(string token) - { - Token = token; - } - } -} diff --git a/src/Wilcommerce.Auth/Events/User/Handlers/UserEventHandler.cs b/src/Wilcommerce.Auth/Events/User/Handlers/UserEventHandler.cs index 38c7261..3420300 100644 --- a/src/Wilcommerce.Auth/Events/User/Handlers/UserEventHandler.cs +++ b/src/Wilcommerce.Auth/Events/User/Handlers/UserEventHandler.cs @@ -1,9 +1,4 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; -using System; -using System.Linq; -using Wilcommerce.Auth.Services.Interfaces; -using Wilcommerce.Core.Common.Domain.ReadModels; +using System; using Wilcommerce.Core.Infrastructure; namespace Wilcommerce.Auth.Events.User.Handlers @@ -13,8 +8,11 @@ namespace Wilcommerce.Auth.Events.User.Handlers /// public class UserEventHandler : IHandleEvent, - IHandleEvent, - IHandleEvent + IHandleEvent, + IHandleEvent, + IHandleEvent, + IHandleEvent, + IHandleEvent { /// /// Get the event store @@ -22,55 +20,83 @@ public class UserEventHandler : public IEventStore EventStore { get; } /// - /// Get the identity factory + /// Construct the event handler /// - public IIdentityFactory IdentityFactory { get; } + /// The event store instance + public UserEventHandler(IEventStore eventStore) + { + EventStore = eventStore ?? throw new ArgumentNullException(nameof(eventStore)); + } /// - /// Get the database of the common context + /// /// - public ICommonDatabase CommonDatabase { get; } + /// + public void Handle(UserSignedInEvent @event) + { + try + { + EventStore.Save(@event); + } + catch + { + throw; + } + } /// - /// Get the http context + /// /// - public HttpContext Context { get; } + /// + public void Handle(NewAdministratorCreatedEvent @event) + { + try + { + EventStore.Save(@event); + } + catch + { + throw; + } + } /// - /// Construct the event handler + /// /// - /// The event store instance - /// The identity factory instance - /// The common database instance - /// The http context accessor instance - public UserEventHandler(IEventStore eventStore, IIdentityFactory identityFactory, ICommonDatabase commonDatabase, IHttpContextAccessor httpContextAccessor) + /// + public void Handle(UserDisabledEvent @event) { - if (httpContextAccessor == null) + try + { + EventStore.Save(@event); + } + catch { - throw new ArgumentNullException(nameof(httpContextAccessor)); + throw; } - - EventStore = eventStore ?? throw new ArgumentNullException(nameof(eventStore)); - IdentityFactory = identityFactory ?? throw new ArgumentNullException(nameof(identityFactory)); - CommonDatabase = commonDatabase ?? throw new ArgumentNullException(nameof(commonDatabase)); - Context = httpContextAccessor.HttpContext; } - /// - public void Handle(UserSignedInEvent @event) + /// + /// + /// + /// + public void Handle(UserEnabledEvent @event) { try { EventStore.Save(@event); } - catch + catch { throw; } } - /// - public void Handle(PasswordRecoveryRequestedEvent @event) + /// + /// + /// + /// + public void Handle(UserInfoChangedEvent @event) { try { @@ -82,23 +108,15 @@ public void Handle(PasswordRecoveryRequestedEvent @event) } } - /// - public void Handle(PasswordRecoveryValidatedEvent @event) + /// + /// + /// + /// + public void Handle(UserPasswordResetEvent @event) { try { EventStore.Save(@event); - - var user = CommonDatabase.Users - .FirstOrDefault(u => u.Id == @event.AggregateId); - - if (user == null) - { - throw new Exception("User not found"); - } - - var principal = IdentityFactory.CreateIdentity(user); - Context.SignInAsync(AuthenticationDefaults.AuthenticationScheme, principal); } catch { diff --git a/src/Wilcommerce.Auth/Events/User/NewAdministratorCreatedEvent.cs b/src/Wilcommerce.Auth/Events/User/NewAdministratorCreatedEvent.cs new file mode 100644 index 0000000..9885379 --- /dev/null +++ b/src/Wilcommerce.Auth/Events/User/NewAdministratorCreatedEvent.cs @@ -0,0 +1,49 @@ +using System; +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Events.User +{ + /// + /// New administrator created + /// + public class NewAdministratorCreatedEvent : DomainEvent + { + /// + /// Get the administrator id + /// + public Guid AdministratorId { get; private set; } + + /// + /// Get the administrator name + /// + public string Name { get; private set; } + + /// + /// Get the administrator email + /// + public string Email { get; private set; } + + /// + /// Construct the event + /// + /// The administrator id + /// The administrator name + /// The administrator email + public NewAdministratorCreatedEvent(Guid administratorId, string name, string email) + : base(administratorId, typeof(Models.User)) + { + AdministratorId = administratorId; + Name = name; + Email = email; + } + + /// + /// Convert the event to string + /// + /// The converted string + public override string ToString() + { + return $"{FiredOn} - The administrator {Name}, {Email} [{AdministratorId}] was created"; + } + } +} diff --git a/src/Wilcommerce.Auth/Events/User/PasswordRecoveryRequestedEvent.cs b/src/Wilcommerce.Auth/Events/User/PasswordRecoveryRequestedEvent.cs deleted file mode 100644 index a6bb562..0000000 --- a/src/Wilcommerce.Auth/Events/User/PasswordRecoveryRequestedEvent.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using Wilcommerce.Core.Infrastructure; - -namespace Wilcommerce.Auth.Events.User -{ - /// - /// Password recovery validated - /// - public class PasswordRecoveryRequestedEvent : DomainEvent - { - /// - /// Get the username - /// - public string Username { get; } - - /// - /// Get the token id - /// - public Guid TokenId { get; } - - /// - /// Get the password recovery token - /// - public string Token { get; } - - /// - /// Get the token expiration date - /// - public DateTime ExpirationDate { get; } - - /// - /// Construct the event - /// - /// The user id - /// The username - /// The token id - /// The password recovery token - /// The token expiration date - public PasswordRecoveryRequestedEvent(Guid userId, string username, Guid tokenId, string token, DateTime expirationDate) - : base(userId, typeof(Core.Common.Domain.Models.User)) - { - Username = username; - TokenId = tokenId; - Token = token; - ExpirationDate = expirationDate; - } - } -} diff --git a/src/Wilcommerce.Auth/Events/User/PasswordRecoveryValidatedEvent.cs b/src/Wilcommerce.Auth/Events/User/PasswordRecoveryValidatedEvent.cs deleted file mode 100644 index ecc5815..0000000 --- a/src/Wilcommerce.Auth/Events/User/PasswordRecoveryValidatedEvent.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Wilcommerce.Core.Infrastructure; - -namespace Wilcommerce.Auth.Events.User -{ - /// - /// Password recovery validated - /// - public class PasswordRecoveryValidatedEvent : DomainEvent - { - /// - /// Get the password recovery token - /// - public string Token { get; } - - /// - /// Construct the event - /// - /// The user id - /// The password recovery token - public PasswordRecoveryValidatedEvent(Guid userId, string token) - : base(userId, typeof(Core.Common.Domain.Models.User)) - { - Token = token; - } - } -} diff --git a/src/Wilcommerce.Auth/Events/User/UserDisabledEvent.cs b/src/Wilcommerce.Auth/Events/User/UserDisabledEvent.cs new file mode 100644 index 0000000..6c14761 --- /dev/null +++ b/src/Wilcommerce.Auth/Events/User/UserDisabledEvent.cs @@ -0,0 +1,35 @@ +using System; +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Events.User +{ + /// + /// User disabled + /// + public class UserDisabledEvent : DomainEvent + { + /// + /// Get the user id + /// + public Guid UserId { get; private set; } + + /// + /// Construct the event + /// + /// The user id + public UserDisabledEvent(Guid userId) + : base(userId, typeof(Models.User)) + { + UserId = userId; + } + + /// + /// Convert the event to string + /// + /// The converted string + public override string ToString() + { + return $"{FiredOn} - User {UserId} disabled"; + } + } +} diff --git a/src/Wilcommerce.Auth/Events/User/UserEnabledEvent.cs b/src/Wilcommerce.Auth/Events/User/UserEnabledEvent.cs new file mode 100644 index 0000000..bad6c9b --- /dev/null +++ b/src/Wilcommerce.Auth/Events/User/UserEnabledEvent.cs @@ -0,0 +1,35 @@ +using System; +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Events.User +{ + /// + /// User enabled + /// + public class UserEnabledEvent : DomainEvent + { + /// + /// Get the user id + /// + public Guid UserId { get; private set; } + + /// + /// Construct the event + /// + /// The user id + public UserEnabledEvent(Guid userId) + : base(userId, typeof(Models.User)) + { + UserId = userId; + } + + /// + /// Convert the event to string + /// + /// The converted string + public override string ToString() + { + return $"{FiredOn} - User {UserId} enabled"; + } + } +} diff --git a/src/Wilcommerce.Auth/Events/User/UserInfoChangedEvent.cs b/src/Wilcommerce.Auth/Events/User/UserInfoChangedEvent.cs new file mode 100644 index 0000000..7cd4fe3 --- /dev/null +++ b/src/Wilcommerce.Auth/Events/User/UserInfoChangedEvent.cs @@ -0,0 +1,41 @@ +using System; +using Wilcommerce.Core.Common.Models; +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Events.User +{ + /// + /// User information changed + /// + public class UserInfoChangedEvent : DomainEvent + { + /// + /// Get the user id + /// + public Guid UserId { get; private set; } + + /// + /// Get the user name + /// + public string Name { get; private set; } + + /// + /// Get the user profile image + /// + public Image ProfileImage { get; private set; } + + /// + /// Construct the event + /// + /// The user id + /// The new user name + /// The new user profile image + public UserInfoChangedEvent(Guid userId, string name, Image profileImage) + : base(userId, typeof(Models.User)) + { + UserId = userId; + Name = name; + ProfileImage = profileImage; + } + } +} diff --git a/src/Wilcommerce.Auth/Events/User/UserPasswordResetEvent.cs b/src/Wilcommerce.Auth/Events/User/UserPasswordResetEvent.cs new file mode 100644 index 0000000..338c05e --- /dev/null +++ b/src/Wilcommerce.Auth/Events/User/UserPasswordResetEvent.cs @@ -0,0 +1,26 @@ +using System; +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Events.User +{ + /// + /// User password reset + /// + public class UserPasswordResetEvent : DomainEvent + { + /// + /// Get the user id + /// + public Guid UserId { get; private set; } + + /// + /// Construct the event + /// + /// + public UserPasswordResetEvent(Guid userId) + : base(userId, typeof(Models.User)) + { + UserId = userId; + } + } +} diff --git a/src/Wilcommerce.Auth/Events/User/UserSignedInEvent.cs b/src/Wilcommerce.Auth/Events/User/UserSignedInEvent.cs index ff0ae32..6382c70 100644 --- a/src/Wilcommerce.Auth/Events/User/UserSignedInEvent.cs +++ b/src/Wilcommerce.Auth/Events/User/UserSignedInEvent.cs @@ -19,7 +19,7 @@ public class UserSignedInEvent : DomainEvent /// The user id /// The username public UserSignedInEvent(Guid userId, string username) - : base(userId, typeof(Core.Common.Domain.Models.User)) + : base(userId, typeof(Models.User)) { Username = username; } diff --git a/src/Wilcommerce.Auth/Extensions/UserManagerExtensions.cs b/src/Wilcommerce.Auth/Extensions/UserManagerExtensions.cs new file mode 100644 index 0000000..38d06dc --- /dev/null +++ b/src/Wilcommerce.Auth/Extensions/UserManagerExtensions.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Identity; +using System; +using System.Security.Claims; +using Wilcommerce.Auth.Models; + +namespace Wilcommerce.Auth +{ + /// + /// Defines the extension methods for the UserManager class + /// + public static class UserManagerExtensions + { + /// + /// Get the user's full name or the user's name if not exists + /// + /// The UserManager instance + /// The claims principal instance + /// The user's full name + public static string GetUserFullName(this UserManager userManager, ClaimsPrincipal principal) + { + if (principal == null) + { + throw new ArgumentNullException(nameof(principal)); + } + + return principal.FindFirstValue(ClaimTypes.GivenName) ?? userManager.GetUserName(principal); + } + } +} diff --git a/src/Wilcommerce.Auth/Models/TokenTypes.cs b/src/Wilcommerce.Auth/Models/TokenTypes.cs deleted file mode 100644 index 35d3ce4..0000000 --- a/src/Wilcommerce.Auth/Models/TokenTypes.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; - -namespace Wilcommerce.Auth.Models -{ - /// - /// Defines the available token types - /// - public static class TokenTypes - { - /// - /// Password recovery token - /// - public static string PasswordRecovery => "PASSWORD_RECOVERY"; - - /// - /// Registration token - /// - public static string Registration => "REGISTRATION"; - - /// - /// Check whether the specified type is valid - /// - /// The token type to check - /// true if the type is valid, false otherwise - public static bool IsValidType(string type) - { - if (string.IsNullOrEmpty(type)) - { - throw new ArgumentNullException("type"); - } - - bool isValid = typeof(TokenTypes).GetProperties(BindingFlags.Static).Any(p => p.GetValue(null).ToString() == type); - return isValid; - } - } -} diff --git a/src/Wilcommerce.Auth/Models/User.cs b/src/Wilcommerce.Auth/Models/User.cs new file mode 100644 index 0000000..5174d0e --- /dev/null +++ b/src/Wilcommerce.Auth/Models/User.cs @@ -0,0 +1,162 @@ +using Microsoft.AspNetCore.Identity; +using System; +using Wilcommerce.Core.Common.Models; +using Wilcommerce.Core.Infrastructure; + +namespace Wilcommerce.Auth.Models +{ + /// + /// Represents the user + /// + public class User : IdentityUser, IAggregateRoot + { + /// + /// Get the user's id in Guid format + /// + public new Guid Id => Guid.Parse(base.Id); + + #region Constructor + /// + /// Construct the user + /// + protected User() + : base() + { + ProfileImage = new Image(); + } + #endregion + + #region Properties + /// + /// Get or set the user full name + /// + public string Name { get; protected set; } + + /// + /// Get or set the user profile image + /// + public Image ProfileImage { get; protected set; } + + /// + /// Get or set whether the user is active + /// + public bool IsActive { get; protected set; } + + /// + /// Get or set the date and time of when the user was disabled + /// + public DateTime? DisabledOn { get; set; } + #endregion + + #region Behaviors + /// + /// Enable the user + /// + public virtual void Enable() + { + IsActive = true; + if (DisabledOn != null) + { + DisabledOn = null; + } + } + + /// + /// Disable the user + /// + public virtual void Disable() + { + IsActive = false; + DisabledOn = DateTime.Now; + } + + /// + /// Change the user's name + /// + /// The new user's name + public virtual void ChangeName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("value cannot be empty", nameof(name)); + } + + Name = name; + } + + /// + /// Set the user's profile image + /// + /// The profile image to set + public virtual void SetProfileImage(Image profileImage) + { + ProfileImage = profileImage ?? throw new ArgumentNullException(nameof(profileImage)); + } + #endregion + + #region Factory Methods + /// + /// Creates a new administrator user + /// + /// The user full name + /// The user username + /// Whether the user is active + /// The created administrator user + public static User CreateAsAdministrator(string name, string email, bool active) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("value cannot be empty", nameof(name)); + } + + if (string.IsNullOrWhiteSpace(email)) + { + throw new ArgumentException("value cannot be empty", nameof(email)); + } + + var user = new User + { + UserName = email, + Email = email, + NormalizedEmail = email.ToUpper(), + NormalizedUserName = email.ToUpper(), + Name = name, + IsActive = active, + EmailConfirmed = true + }; + + return user; + } + + /// + /// Creates a new customer user + /// + /// The user full name + /// The user username + /// The created customer user + public static User CreateAsCustomer(string name, string email) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("value cannot be empty", nameof(name)); + } + + if (string.IsNullOrWhiteSpace(email)) + { + throw new ArgumentException("value cannot be empty", nameof(email)); + } + + var user = new User + { + UserName = email, + Email = email, + NormalizedEmail = email.ToUpper(), + NormalizedUserName = email.ToUpper(), + Name = name + }; + + return user; + } + #endregion + } +} diff --git a/src/Wilcommerce.Auth/Models/UserToken.cs b/src/Wilcommerce.Auth/Models/UserToken.cs deleted file mode 100644 index 535b33b..0000000 --- a/src/Wilcommerce.Auth/Models/UserToken.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using Wilcommerce.Core.Common.Domain.Models; -using Wilcommerce.Core.Infrastructure; - -namespace Wilcommerce.Auth.Models -{ - /// - /// Represents a token created for a specific user - /// - public class UserToken : IAggregateRoot - { - /// - /// Get the entity id - /// - public Guid Id { get; protected set; } - - #region Properties - /// - /// Get the related user id - /// - public Guid UserId { get; protected set; } - - /// - /// Get the token type - /// - public string TokenType { get; protected set; } - - /// - /// Get the token - /// - public string Token { get; protected set; } - - /// - /// Get the date and time of token creation - /// - public DateTime CreationDate { get; protected set; } - - /// - /// Get the date and time of token expiration - /// - public DateTime ExpirationDate { get; protected set; } - - /// - /// Get whether the token is expired - /// - public bool IsExpired { get; protected set; } - #endregion - - #region Public Methods - /// - /// Set the current token as expired - /// - public virtual void SetAsExpired() - { - if (IsExpired) - { - throw new InvalidOperationException($"Token already expired on {ExpirationDate.ToString()}"); - } - - IsExpired = true; - ExpirationDate = DateTime.Now; - } - #endregion - - #region Factory - /// - /// Create a new password recovery token - /// - /// The user - /// The token string - /// The token expiration date - /// The token created - public static UserToken PasswordRecovery(User user, string token, DateTime expirationDate) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrEmpty(token)) - { - throw new ArgumentNullException(nameof(token)); - } - - var now = DateTime.Now; - if (expirationDate < now) - { - throw new ArgumentException("Invalid expiration date", nameof(expirationDate)); - } - - var userToken = new UserToken - { - UserId = user.Id, - TokenType = TokenTypes.PasswordRecovery, - Token = token, - CreationDate = now, - ExpirationDate = expirationDate, - IsExpired = false - }; - - return userToken; - } - - /// - /// Create a new registration token - /// - /// The user - /// The token string - /// The token expiration date - /// The token created - public static UserToken Registration(User user, string token, DateTime expirationDate) - { - if (user == null) - { - throw new ArgumentNullException("user"); - } - - if (string.IsNullOrEmpty(token)) - { - throw new ArgumentNullException("token"); - } - - var now = DateTime.Now; - if (expirationDate < now) - { - throw new ArgumentException("Invalid expiration date", "expirationDate"); - } - - var userToken = new UserToken - { - UserId = user.Id, - TokenType = TokenTypes.Registration, - Token = token, - CreationDate = now, - ExpirationDate = expirationDate, - IsExpired = false - }; - - return userToken; - } - #endregion - } -} diff --git a/src/Wilcommerce.Auth/ReadModels/Extensions/UserExtensions.cs b/src/Wilcommerce.Auth/ReadModels/Extensions/UserExtensions.cs new file mode 100644 index 0000000..38a016c --- /dev/null +++ b/src/Wilcommerce.Auth/ReadModels/Extensions/UserExtensions.cs @@ -0,0 +1,36 @@ +using System.Linq; +using Wilcommerce.Auth.Models; + +namespace Wilcommerce.Auth.ReadModels +{ + /// + /// Defines the extension methods for the user read model + /// + public static class UserExtensions + { + /// + /// Retrieve all active users + /// + /// The instance to which attach this method + /// A list of users + public static IQueryable Actives(this IQueryable users) + { + return from u in users + where u.IsActive && u.DisabledOn == null + select u; + } + + /// + /// Retrieve the users with the specified username + /// + /// The instance to which attach this method + /// The user's username + /// A list of users + public static IQueryable WithUsername(this IQueryable users, string username) + { + return from u in users + where u.UserName == username + select u; + } + } +} diff --git a/src/Wilcommerce.Auth/ReadModels/Extensions/UserTokenExtensions.cs b/src/Wilcommerce.Auth/ReadModels/Extensions/UserTokenExtensions.cs deleted file mode 100644 index 998974c..0000000 --- a/src/Wilcommerce.Auth/ReadModels/Extensions/UserTokenExtensions.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Linq; -using Wilcommerce.Auth.Models; - -namespace Wilcommerce.Auth.ReadModels -{ - /// - /// Defines the extension methods for the read models - /// - public static class UserTokenExtensions - { - /// - /// Retrieve all the user tokens by the user id - /// - /// The instance to which attach this method - /// The user id - /// A list of user tokens - public static IQueryable ByUser(this IQueryable tokens, Guid userId) - { - return from t in tokens - where t.UserId == userId - select t; - } - - /// - /// Retrieve all the expired tokens - /// - /// The instance to which attach this method - /// A list of user tokens - public static IQueryable Expired(this IQueryable tokens) - { - return from t in tokens - where t.IsExpired || t.ExpirationDate <= DateTime.Now - select t; - } - - /// - /// Retrieve all the user tokens which are not expired - /// - /// The instance to which attach this method - /// A list of user tokens - public static IQueryable NotExpired(this IQueryable tokens) - { - return from t in tokens - where !t.IsExpired && t.ExpirationDate > DateTime.Now - select t; - } - - /// - /// Retrieve all the user tokens filtered by the type - /// - /// The instance to which attach this method - /// The token type - /// A list of user tokens - public static IQueryable ByTokenType(this IQueryable tokens, string type) - { - return from t in tokens - where t.TokenType == type - select t; - } - } -} diff --git a/src/Wilcommerce.Auth/ReadModels/IAuthDatabase.cs b/src/Wilcommerce.Auth/ReadModels/IAuthDatabase.cs index 803ae8f..8f6968b 100644 --- a/src/Wilcommerce.Auth/ReadModels/IAuthDatabase.cs +++ b/src/Wilcommerce.Auth/ReadModels/IAuthDatabase.cs @@ -9,8 +9,8 @@ namespace Wilcommerce.Auth.ReadModels public interface IAuthDatabase { /// - /// Get the tokens created by the platform + /// Get the users list /// - IQueryable Tokens { get; } + IQueryable Users { get; } } } diff --git a/src/Wilcommerce.Auth/Repository/IRepository.cs b/src/Wilcommerce.Auth/Repository/IRepository.cs deleted file mode 100644 index 95a527c..0000000 --- a/src/Wilcommerce.Auth/Repository/IRepository.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Wilcommerce.Auth.Repository -{ - /// - public interface IRepository : Core.Infrastructure.IRepository - { - } -} diff --git a/src/Wilcommerce.Auth/Services/AuthenticationService.cs b/src/Wilcommerce.Auth/Services/AuthenticationService.cs index 06489cf..b9c4fb6 100644 --- a/src/Wilcommerce.Auth/Services/AuthenticationService.cs +++ b/src/Wilcommerce.Auth/Services/AuthenticationService.cs @@ -1,120 +1,76 @@ using System; using System.Linq; using System.Threading.Tasks; -using Wilcommerce.Core.Common.Domain.Models; -using Wilcommerce.Core.Common.Domain.ReadModels; using Microsoft.AspNetCore.Identity; using Wilcommerce.Auth.Services.Interfaces; -using Wilcommerce.Auth.Commands.Handlers.Interfaces; -using Wilcommerce.Auth.Commands; using Wilcommerce.Core.Infrastructure; using Wilcommerce.Auth.Events.User; -using Microsoft.AspNetCore.Http; +using Wilcommerce.Auth.ReadModels; +using Wilcommerce.Auth.Models; using Microsoft.AspNetCore.Authentication; +using System.Security.Claims; namespace Wilcommerce.Auth.Services { /// - /// Implementation of + /// Implementation of /// public class AuthenticationService : Interfaces.IAuthenticationService { - /// - /// Get the http context - /// - public HttpContext Context { get; } - /// /// Get the common context database /// - public ICommonDatabase CommonDatabase { get; } - - /// - /// Get the password hasher service - /// - public IPasswordHasher PasswordHasher { get; } - - /// - /// Get the token generator service - /// - public ITokenGenerator TokenGenerator { get; } - - /// - /// Get the identity factory - /// - public IIdentityFactory IdentityFactory { get; } + public IAuthDatabase AuthDatabase { get; } /// - /// Get the password recovery handler - /// - public IRecoverPasswordCommandHandler RecoverPasswordHandler { get; } - - /// - /// Get the password recovery validation handler + /// Get the event bus /// - public IValidatePasswordRecoveryCommandHandler ValidatePasswordRecoveryHandler { get; } + public IEventBus EventBus { get; } /// - /// Get the event bus + /// Get the signin manager instance /// - public IEventBus EventBus { get; } + public SignInManager SignInManager { get; } /// /// Construct the authentication service /// - /// The http context accessor instance - /// The common database instance - /// The password hasher instance - /// The token generator instance - /// The password recover handler instance - /// The password recovery validation handler instance + /// The common database instance /// The event bus instance - /// The identity factory instance - public AuthenticationService(IHttpContextAccessor httpContextAccessor, ICommonDatabase commonDatabase, IPasswordHasher passwordHasher, ITokenGenerator tokenGenerator, IRecoverPasswordCommandHandler recoverPasswordHandler, IValidatePasswordRecoveryCommandHandler validatePasswordRecoveryHandler, IEventBus eventBus, IIdentityFactory identityFactory) + /// + public AuthenticationService(IAuthDatabase authDatabase, IEventBus eventBus, SignInManager signInManager) { - if (httpContextAccessor == null) - { - throw new ArgumentNullException(nameof(httpContextAccessor)); - } - - Context = httpContextAccessor.HttpContext; - CommonDatabase = commonDatabase ?? throw new ArgumentNullException(nameof(commonDatabase)); - PasswordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher)); - TokenGenerator = tokenGenerator ?? throw new ArgumentNullException(nameof(tokenGenerator)); - RecoverPasswordHandler = recoverPasswordHandler ?? throw new ArgumentNullException(nameof(recoverPasswordHandler)); - ValidatePasswordRecoveryHandler = validatePasswordRecoveryHandler ?? throw new ArgumentNullException(nameof(validatePasswordRecoveryHandler)); + AuthDatabase = authDatabase ?? throw new ArgumentNullException(nameof(authDatabase)); EventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); - IdentityFactory = identityFactory ?? throw new ArgumentNullException(nameof(identityFactory)); + SignInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager)); } - /// - public Task SignIn(string email, string password, bool isPersistent) + /// + /// Implementation of + /// + /// + /// + /// + /// + public virtual async Task SignIn(string email, string password, bool isPersistent) { try { - var user = CommonDatabase.Users - .Where(u => u.IsActive && u.DisabledOn == null) - .FirstOrDefault(u => u.Email == email); + var user = AuthDatabase.Users + .Actives() + .WithUsername(email) + .Single(); - if (user == null) + var signin = await SignInManager.PasswordSignInAsync(user, password, isPersistent, false); + if (signin.Succeeded) { - throw new InvalidOperationException($"User {email} not found"); - } + var claimsPrincipal = await AddCustomClaimsForUser(user); + await RefreshAuthenticationWithClaimsPrincipal(claimsPrincipal, isPersistent); - if (!IsPasswordValid(user, password)) - { - throw new InvalidOperationException("Bad credentials"); + var @event = new UserSignedInEvent(user.Id, user.Email); + EventBus.RaiseEvent(@event); } - var principal = IdentityFactory.CreateIdentity(user); - var signin = Context.SignInAsync( - AuthenticationDefaults.AuthenticationScheme, - principal, - new AuthenticationProperties { IsPersistent = isPersistent }); - - var @event = new UserSignedInEvent(user.Id, user.Email); - EventBus.RaiseEvent(@event); - return signin; } catch @@ -123,12 +79,15 @@ public Task SignIn(string email, string password, bool isPersistent) } } - /// - public Task SignOut() + /// + /// Implementation of + /// + /// + public virtual Task SignOut() { try { - return Context.SignOutAsync(AuthenticationDefaults.AuthenticationScheme); + return SignInManager.SignOutAsync(); } catch { @@ -136,61 +95,36 @@ public Task SignOut() } } - /// - public Task RecoverPassword(string email) + #region Protected methods + /// + /// Add custom claims for the specified user + /// + /// The current user + /// The claims principal for the authenticated user + protected virtual async Task AddCustomClaimsForUser(User user) { - try - { - var user = CommonDatabase.Users - .Where(u => u.IsActive && u.DisabledOn == null) - .FirstOrDefault(u => u.Email == email); + var claimsPrincipal = await SignInManager.CreateUserPrincipalAsync(user); + var claimsIdentity = claimsPrincipal.Identity as ClaimsIdentity; + claimsIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())); + claimsIdentity.AddClaim(new Claim(ClaimTypes.Email, user.Email)); + claimsIdentity.AddClaim(new Claim(ClaimTypes.GivenName, user.Name)); - if (user == null) - { - throw new InvalidOperationException($"User {email} not found"); - } - - var command = new RecoverPasswordCommand(user, TokenGenerator.GenerateForUser(user)); - return RecoverPasswordHandler.Handle(command); - } - catch - { - throw; - } - } - - /// - public Task ValidatePasswordRecovery(string token) - { - try - { - var command = new ValidatePasswordRecoveryCommand(token); - return ValidatePasswordRecoveryHandler.Handle(command); - } - catch - { - throw; - } + return claimsPrincipal; } - #region Protected methods /// - /// Check whether the password is valid + /// Refresh the authentication using the specified claims principal /// - /// The user instance - /// The password to check - /// true if the password is valid, false otherwise - protected virtual bool IsPasswordValid(User user, string password) + /// The claims principal instance to perform authentication + /// Whether the authentication is persistent + /// + protected virtual async Task RefreshAuthenticationWithClaimsPrincipal(ClaimsPrincipal claimsPrincipal, bool isPersistent) { - try - { - var result = PasswordHasher.VerifyHashedPassword(user, user.Password, password); - return result != PasswordVerificationResult.Failed; - } - catch - { - throw; - } + await SignInManager.Context.SignOutAsync(); + await SignInManager.Context.SignInAsync( + IdentityConstants.ApplicationScheme, + claimsPrincipal, + new AuthenticationProperties { IsPersistent = isPersistent }); } #endregion } diff --git a/src/Wilcommerce.Auth/Services/IdentityFactory.cs b/src/Wilcommerce.Auth/Services/IdentityFactory.cs deleted file mode 100644 index 3f88d6e..0000000 --- a/src/Wilcommerce.Auth/Services/IdentityFactory.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Security.Claims; -using Wilcommerce.Auth.Services.Interfaces; -using Wilcommerce.Core.Common.Domain.Models; - -namespace Wilcommerce.Auth.Services -{ - /// - /// Implementation of - /// - public class IdentityFactory : IIdentityFactory - { - /// - public virtual ClaimsPrincipal CreateIdentity(User user) - { - try - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var identity = new ClaimsIdentity(); - identity.AddClaim(new Claim(ClaimTypes.Name, user.Email)); - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())); - identity.AddClaim(new Claim(ClaimTypes.Email, user.Email)); - identity.AddClaim(new Claim(ClaimTypes.Role, GetUserRoleAsString(user))); - identity.AddClaim(new Claim(ClaimTypes.GivenName, user.Name)); - - return new ClaimsPrincipal(identity); - } - catch - { - throw; - } - } - - /// - /// Get the string representing the user's role - /// - /// The user instance - /// The user's role as a string - protected virtual string GetUserRoleAsString(User user) - { - var role = user.Role; - switch (role) - { - case User.Roles.CUSTOMER: - return AuthenticationDefaults.CustomerRole; - case User.Roles.ADMINISTRATOR: - return AuthenticationDefaults.AdministratorRole; - default: - return null; - } - } - } -} diff --git a/src/Wilcommerce.Auth/Services/Interfaces/IAuthenticationService.cs b/src/Wilcommerce.Auth/Services/Interfaces/IAuthenticationService.cs index ed8dc27..92a3520 100644 --- a/src/Wilcommerce.Auth/Services/Interfaces/IAuthenticationService.cs +++ b/src/Wilcommerce.Auth/Services/Interfaces/IAuthenticationService.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using System.Threading.Tasks; namespace Wilcommerce.Auth.Services.Interfaces { @@ -14,26 +15,12 @@ public interface IAuthenticationService /// The password /// Whether the authentication is persistent /// - Task SignIn(string username, string password, bool isPersistent); + Task SignIn(string username, string password, bool isPersistent); /// /// Sign out the authenticated user /// /// Task SignOut(); - - /// - /// Perform the password recovery request - /// - /// - /// - Task RecoverPassword(string email); - - /// - /// Validate the password recovery request by the specified token - /// - /// - /// - Task ValidatePasswordRecovery(string token); } } diff --git a/src/Wilcommerce.Auth/Services/Interfaces/IIdentityFactory.cs b/src/Wilcommerce.Auth/Services/Interfaces/IIdentityFactory.cs deleted file mode 100644 index 6e89690..0000000 --- a/src/Wilcommerce.Auth/Services/Interfaces/IIdentityFactory.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Security.Claims; -using Wilcommerce.Core.Common.Domain.Models; - -namespace Wilcommerce.Auth.Services.Interfaces -{ - /// - /// Represents the factory to create the indentity - /// - public interface IIdentityFactory - { - /// - /// Create the claims principal by the user - /// - /// The user for which creates the identity - /// The claims principal - ClaimsPrincipal CreateIdentity(User user); - } -} diff --git a/src/Wilcommerce.Auth/Services/Interfaces/IRoleFactory.cs b/src/Wilcommerce.Auth/Services/Interfaces/IRoleFactory.cs new file mode 100644 index 0000000..a686220 --- /dev/null +++ b/src/Wilcommerce.Auth/Services/Interfaces/IRoleFactory.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Identity; +using System.Threading.Tasks; + +namespace Wilcommerce.Auth.Services.Interfaces +{ + /// + /// Represents the factory to create the available roles + /// + public interface IRoleFactory + { + /// + /// Create an administrator role if not exists and returns it + /// + /// The administrator role + Task Administrator(); + + /// + /// Create a customer role if not exists and returns it + /// + /// The customer role + Task Customer(); + } +} diff --git a/src/Wilcommerce.Auth/Services/Interfaces/ITokenGenerator.cs b/src/Wilcommerce.Auth/Services/Interfaces/ITokenGenerator.cs index bed1ddf..f155c76 100644 --- a/src/Wilcommerce.Auth/Services/Interfaces/ITokenGenerator.cs +++ b/src/Wilcommerce.Auth/Services/Interfaces/ITokenGenerator.cs @@ -1,4 +1,5 @@ -using Wilcommerce.Core.Common.Domain.Models; +using System.Threading.Tasks; +using Wilcommerce.Auth.Models; namespace Wilcommerce.Auth.Services.Interfaces { @@ -8,10 +9,17 @@ namespace Wilcommerce.Auth.Services.Interfaces public interface ITokenGenerator { /// - /// Generate a token string for the specified user + /// Generate an email confirmation token string for the specified user /// /// The current user /// The token string - string GenerateForUser(User user); + Task GenerateEmailConfirmationTokenForUser(User user); + + /// + /// Generate a password recovery token string for the specified user + /// + /// The current user + /// The token string + Task GeneratePasswordRecoveryTokenForUser(User user); } } diff --git a/src/Wilcommerce.Auth/Services/RoleFactory.cs b/src/Wilcommerce.Auth/Services/RoleFactory.cs new file mode 100644 index 0000000..08248c3 --- /dev/null +++ b/src/Wilcommerce.Auth/Services/RoleFactory.cs @@ -0,0 +1,60 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Wilcommerce.Auth.Services.Interfaces; + +namespace Wilcommerce.Auth.Services +{ + /// + /// Implementation of + /// + public class RoleFactory : IRoleFactory + { + private readonly RoleManager _roleManager; + + /// + /// Construct the role factory + /// + /// The role manager instance + public RoleFactory(RoleManager roleManager) + { + _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); + } + + /// + /// Implementation of + /// + /// + public virtual async Task Administrator() + { + return await CreateRoleIfNotExists(AuthenticationDefaults.CustomerRole); + } + + /// + /// Implementation of + /// + /// + public virtual async Task Customer() + { + return await CreateRoleIfNotExists(AuthenticationDefaults.CustomerRole); + } + + #region Protected Methods + /// + /// Create the role with the specified name if not exists and returns it + /// + /// The role name + /// The role instance + protected virtual async Task CreateRoleIfNotExists(string roleName) + { + var role = await _roleManager.FindByNameAsync(roleName); + if (role == null) + { + return new IdentityRole(roleName); + } + + return role; + } + #endregion + } +} diff --git a/src/Wilcommerce.Auth/Services/TokenGenerator.cs b/src/Wilcommerce.Auth/Services/TokenGenerator.cs index f7d73f9..a070072 100644 --- a/src/Wilcommerce.Auth/Services/TokenGenerator.cs +++ b/src/Wilcommerce.Auth/Services/TokenGenerator.cs @@ -1,7 +1,8 @@ using System; -using System.Text; using Wilcommerce.Auth.Services.Interfaces; -using Wilcommerce.Core.Common.Domain.Models; +using Wilcommerce.Auth.Models; +using Microsoft.AspNetCore.Identity; +using System.Threading.Tasks; namespace Wilcommerce.Auth.Services { @@ -10,13 +11,54 @@ namespace Wilcommerce.Auth.Services /// public class TokenGenerator : ITokenGenerator { - /// - public string GenerateForUser(User user) + private readonly UserManager _userManager; + + /// + /// Construct the token generator class + /// + /// The identity user manager + public TokenGenerator(UserManager userManager) + { + _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); + } + + /// + /// Implementation of + /// + /// + /// + public virtual async Task GenerateEmailConfirmationTokenForUser(User user) + { + try + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + return await _userManager.GenerateEmailConfirmationTokenAsync(user); + } + catch + { + throw; + } + } + + /// + /// Implementation of + /// + /// + /// + public virtual async Task GeneratePasswordRecoveryTokenForUser(User user) { try { - var tokenBytes = Encoding.UTF8.GetBytes($"{user.Email}{Guid.NewGuid().ToString("N")}"); - return Convert.ToBase64String(tokenBytes); + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + return await _userManager.GeneratePasswordResetTokenAsync(user); } catch { diff --git a/src/Wilcommerce.Auth/Wilcommerce.Auth.csproj b/src/Wilcommerce.Auth/Wilcommerce.Auth.csproj index c482d9c..9674da2 100644 --- a/src/Wilcommerce.Auth/Wilcommerce.Auth.csproj +++ b/src/Wilcommerce.Auth/Wilcommerce.Auth.csproj @@ -15,7 +15,7 @@ false false false - 1.0.0-rc4 + 1.0.0-rc5 Wilcommerce Authentication and Authorization library @@ -29,15 +29,12 @@ - - - - - + - + + - +