From 82fded5297a030982b641ac8e8d518c473526f04 Mon Sep 17 00:00:00 2001 From: 83585-Vanderlan Silva Date: Tue, 31 Oct 2023 18:20:03 -0300 Subject: [PATCH] Finish mediator configuration --- src/Orion.Api/Bootstrapper.cs | 2 +- .../AuthenticationConfiguration.cs | 3 +- .../ApplicationAssembly.cs | 2 +- .../LoginWithCredentialsRequestHandler.cs | 11 +---- .../LoginWithCredentialsResponse.cs | 4 +- .../LoginWithRefreshTokenRequestHandler.cs | 11 +---- .../Messages/MessagesKeys.cs | 11 +++-- .../Resources/OrionResources.en-US.resx | 3 ++ .../Resources/OrionResources.pt-BR.resx | 3 ++ .../Authentication/CurrentUser.cs | 9 ++-- .../Authentication/ICurrentUser.cs | 3 ++ .../Entities/Enuns/UserProfile.cs | 5 ++- .../Extensions/EnumExtensions.cs | 20 +++++++++ .../Services/Interfaces/IUserService.cs | 5 +-- src/Orion.Domain.Core/Services/UserService.cs | 26 ++++++++---- .../Api/Controllers/AuthControllerTest.cs | 20 ++++++--- .../Domain/Services/UserServiceTest.cs | 42 +++---------------- 17 files changed, 92 insertions(+), 88 deletions(-) create mode 100644 src/Orion.Domain.Core/Extensions/EnumExtensions.cs diff --git a/src/Orion.Api/Bootstrapper.cs b/src/Orion.Api/Bootstrapper.cs index 92bc2c4..00fc597 100644 --- a/src/Orion.Api/Bootstrapper.cs +++ b/src/Orion.Api/Bootstrapper.cs @@ -80,6 +80,6 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur services.AddHttpContextAccessor(); services.AddScoped(); - services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(ApplicationAssembly).Assembly)); + services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(IApplicationAssembly).Assembly)); } } diff --git a/src/Orion.Api/Configuration/AuthenticationConfiguration.cs b/src/Orion.Api/Configuration/AuthenticationConfiguration.cs index 780a933..6dc85da 100644 --- a/src/Orion.Api/Configuration/AuthenticationConfiguration.cs +++ b/src/Orion.Api/Configuration/AuthenticationConfiguration.cs @@ -4,6 +4,7 @@ using System.Security.Claims; using System.Text; using Orion.Application.Core.Commands.LoginWithCredentials; +using Orion.Domain.Core.Extensions; namespace Orion.Api.Configuration; @@ -44,7 +45,7 @@ public static (string Token, DateTime ValidTo)CreateToken(LoginWithCredentialsRe new Claim(ClaimTypes.Email, loginWithCredentialsResponse.Email), new Claim(ClaimTypes.GivenName, loginWithCredentialsResponse.Name), new Claim(ClaimTypes.Sid, loginWithCredentialsResponse.PublicId), - new Claim(ClaimTypes.Role, loginWithCredentialsResponse.ProfileDescription), + new Claim(ClaimTypes.Role, loginWithCredentialsResponse.Profile.Description()), }; var signinKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SymmetricSecurityKey)); diff --git a/src/Orion.Application.Core/ApplicationAssembly.cs b/src/Orion.Application.Core/ApplicationAssembly.cs index 93e3f6b..7a4a234 100644 --- a/src/Orion.Application.Core/ApplicationAssembly.cs +++ b/src/Orion.Application.Core/ApplicationAssembly.cs @@ -1,3 +1,3 @@ namespace Orion.Application.Core; -public sealed class ApplicationAssembly { } +public interface IApplicationAssembly { } diff --git a/src/Orion.Application.Core/Commands/LoginWithCredentials/LoginWithCredentialsRequestHandler.cs b/src/Orion.Application.Core/Commands/LoginWithCredentials/LoginWithCredentialsRequestHandler.cs index cfc3c67..1857843 100644 --- a/src/Orion.Application.Core/Commands/LoginWithCredentials/LoginWithCredentialsRequestHandler.cs +++ b/src/Orion.Application.Core/Commands/LoginWithCredentials/LoginWithCredentialsRequestHandler.cs @@ -1,6 +1,4 @@ using MediatR; -using Orion.Domain.Core.Entities; -using Orion.Domain.Core.Extensions; using Orion.Domain.Core.Services.Interfaces; namespace Orion.Application.Core.Commands.LoginWithCredentials; @@ -16,14 +14,7 @@ public LoginWithCredentialsRequestHandler(IUserService userService) public async Task Handle(LoginWithCredentialsRequest request, CancellationToken cancellationToken) { - var user = await _userService.LoginAsync(request.Email, request.Password); - - var refreshToken = await _userService.AddRefreshTokenAsync( - new RefreshToken - { - Email = user.Email, - Refreshtoken = Guid.NewGuid().ToString().ToSha512() - }); + var (user, refreshToken) = await _userService.SignInWithCredentialsAsync(request.Email, request.Password); return new LoginWithCredentialsResponse { diff --git a/src/Orion.Application.Core/Commands/LoginWithCredentials/LoginWithCredentialsResponse.cs b/src/Orion.Application.Core/Commands/LoginWithCredentials/LoginWithCredentialsResponse.cs index a4b14e8..2a3219e 100644 --- a/src/Orion.Application.Core/Commands/LoginWithCredentials/LoginWithCredentialsResponse.cs +++ b/src/Orion.Application.Core/Commands/LoginWithCredentials/LoginWithCredentialsResponse.cs @@ -1,5 +1,4 @@ -using Orion.Domain.Core.Authentication; -using Orion.Domain.Core.Entities.Enuns; +using Orion.Domain.Core.Entities.Enuns; namespace Orion.Application.Core.Commands.LoginWithCredentials; @@ -9,6 +8,5 @@ public class LoginWithCredentialsResponse public string Name { get; set; } public string Email { get; set; } public UserProfile Profile { get; set; } - public string ProfileDescription => Profile == UserProfile.Admin ? AuthorizationConfiguration.Roles.Admin : AuthorizationConfiguration.Roles.Customer; public string RefreshToken { get; set; } } \ No newline at end of file diff --git a/src/Orion.Application.Core/Commands/LoginWithRefreshToken/LoginWithRefreshTokenRequestHandler.cs b/src/Orion.Application.Core/Commands/LoginWithRefreshToken/LoginWithRefreshTokenRequestHandler.cs index cadd065..77f1dba 100644 --- a/src/Orion.Application.Core/Commands/LoginWithRefreshToken/LoginWithRefreshTokenRequestHandler.cs +++ b/src/Orion.Application.Core/Commands/LoginWithRefreshToken/LoginWithRefreshTokenRequestHandler.cs @@ -1,6 +1,4 @@ using MediatR; -using Orion.Domain.Core.Entities; -using Orion.Domain.Core.Extensions; using Orion.Domain.Core.Services.Interfaces; namespace Orion.Application.Core.Commands.LoginWithRefreshToken; @@ -16,14 +14,7 @@ public LoginWithRefreshTokenRequestHandler(IUserService userService) public async Task Handle(LoginWithRefreshTokenRequest request, CancellationToken cancellationToken) { - var user = await _userService.SignInWithRehreshTokenAsync(request.RefreshToken, request.Token); - - var refreshToken = await _userService.AddRefreshTokenAsync( - new RefreshToken - { - Email = user.Email, - Refreshtoken = Guid.NewGuid().ToString().ToSha512() - }); + var (user, refreshToken) = await _userService.SignInWithRefreshTokenAsync(request.RefreshToken, request.Token); return new LoginWithRefreshTokenResponse { diff --git a/src/Orion.Croscutting.Resources/Messages/MessagesKeys.cs b/src/Orion.Croscutting.Resources/Messages/MessagesKeys.cs index e2ee44c..f40b6cd 100644 --- a/src/Orion.Croscutting.Resources/Messages/MessagesKeys.cs +++ b/src/Orion.Croscutting.Resources/Messages/MessagesKeys.cs @@ -1,7 +1,7 @@ namespace Orion.Croscutting.Resources.Messages; /// -/// Update all Resource Files to mantain the globalization support +/// Update all resource files to maintain globalization support /// public static class MessagesKeys { @@ -13,13 +13,16 @@ public static class ExceptionsTitles public static class UserMessages { + //Auth + public const string InvalidCredentials = "User.InvalidCredentials"; + public const string InvalidRefreshToken = "User.InvalidRefreshToken"; + + //User public const string NullEntity = "User.NullEntity"; public const string EmptyName = "User.EmptyName"; public const string EmptyPasword = "User.EmptyPassword"; public const string EmptyEmail = "User.EmptyEmail"; - public const string EmptyId = "User.EmptyId"; //todo: add to resource + public const string EmptyId = "User.EmptyId"; public const string EmailExists = "User.EmailExists"; - public const string InvalidCredentials = "User.InvalidCredentials"; - public const string InvalidRefreshToken = "User.InvalidRefreshToken"; } } diff --git a/src/Orion.Croscutting.Resources/Resources/OrionResources.en-US.resx b/src/Orion.Croscutting.Resources/Resources/OrionResources.en-US.resx index 0ad2173..e385aea 100644 --- a/src/Orion.Croscutting.Resources/Resources/OrionResources.en-US.resx +++ b/src/Orion.Croscutting.Resources/Resources/OrionResources.en-US.resx @@ -144,4 +144,7 @@ Is necessary to inform the E-mail + + Is necessary to inform the Id + \ No newline at end of file diff --git a/src/Orion.Croscutting.Resources/Resources/OrionResources.pt-BR.resx b/src/Orion.Croscutting.Resources/Resources/OrionResources.pt-BR.resx index 62d6815..f7ee555 100644 --- a/src/Orion.Croscutting.Resources/Resources/OrionResources.pt-BR.resx +++ b/src/Orion.Croscutting.Resources/Resources/OrionResources.pt-BR.resx @@ -144,4 +144,7 @@ É necessário informar o E-mail + + É necessário informar o Id + \ No newline at end of file diff --git a/src/Orion.Domain.Core/Authentication/CurrentUser.cs b/src/Orion.Domain.Core/Authentication/CurrentUser.cs index 2864b9d..d5bafef 100644 --- a/src/Orion.Domain.Core/Authentication/CurrentUser.cs +++ b/src/Orion.Domain.Core/Authentication/CurrentUser.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Http; using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; @@ -17,10 +16,10 @@ public CurrentUser(IHttpContextAccessor accessor) _claims = _accessor.HttpContext.User.Claims.ToList(); } - public string Name => IsAuthenticated() ? _claims.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value : string.Empty; - public string Id => IsAuthenticated() ? _claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid)?.Value: string.Empty; - public string Email => IsAuthenticated() ? _claims.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value: string.Empty; - public string Profile => IsAuthenticated() ? _claims.FirstOrDefault(x => x.Type == ClaimTypes.Role)?.Value: string.Empty; + public string Name => IsAuthenticated() ? _claims.Find(x => x.Type == ClaimTypes.GivenName)?.Value : string.Empty; + public string Id => IsAuthenticated() ? _claims.Find(x => x.Type == ClaimTypes.Sid)?.Value: string.Empty; + public string Email => IsAuthenticated() ? _claims.Find(x => x.Type == ClaimTypes.Email)?.Value: string.Empty; + public string Profile => IsAuthenticated() ? _claims.Find(x => x.Type == ClaimTypes.Role)?.Value: string.Empty; public bool IsAuthenticated() { diff --git a/src/Orion.Domain.Core/Authentication/ICurrentUser.cs b/src/Orion.Domain.Core/Authentication/ICurrentUser.cs index d8624e6..908c1f3 100644 --- a/src/Orion.Domain.Core/Authentication/ICurrentUser.cs +++ b/src/Orion.Domain.Core/Authentication/ICurrentUser.cs @@ -3,5 +3,8 @@ public interface ICurrentUser { string Name { get; } + public string Id { get; } + public string Email { get; } + public string Profile { get; } bool IsAuthenticated(); } diff --git a/src/Orion.Domain.Core/Entities/Enuns/UserProfile.cs b/src/Orion.Domain.Core/Entities/Enuns/UserProfile.cs index e3787bc..67a7baf 100644 --- a/src/Orion.Domain.Core/Entities/Enuns/UserProfile.cs +++ b/src/Orion.Domain.Core/Entities/Enuns/UserProfile.cs @@ -1,12 +1,13 @@ using System.ComponentModel; +using static Orion.Domain.Core.Authentication.AuthorizationConfiguration; namespace Orion.Domain.Core.Entities.Enuns; public enum UserProfile { - [Description("Admin")] + [Description(Roles.Admin)] Admin = 1, - [Description("Customer")] + [Description(Roles.Customer)] Customer = 2 } diff --git a/src/Orion.Domain.Core/Extensions/EnumExtensions.cs b/src/Orion.Domain.Core/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..0b95d41 --- /dev/null +++ b/src/Orion.Domain.Core/Extensions/EnumExtensions.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; +using System.Linq; + +namespace Orion.Domain.Core.Extensions +{ + public static class EnumExtensions + { + public static string Description(this T source) + { + var fieldInfo = source.GetType().GetField(source.ToString()); + + var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); + + if (attributes.Any()) + return attributes.First().Description; + + return string.Empty; + } + } +} diff --git a/src/Orion.Domain.Core/Services/Interfaces/IUserService.cs b/src/Orion.Domain.Core/Services/Interfaces/IUserService.cs index d4b91e4..659f155 100644 --- a/src/Orion.Domain.Core/Services/Interfaces/IUserService.cs +++ b/src/Orion.Domain.Core/Services/Interfaces/IUserService.cs @@ -8,8 +8,7 @@ namespace Orion.Domain.Core.Services.Interfaces; public interface IUserService : IBaseService { - Task LoginAsync(string email, string password); - Task AddRefreshTokenAsync(RefreshToken refreshToken); - Task SignInWithRehreshTokenAsync(string refreshToken, string expiredToken); + Task<(User User, RefreshToken RefreshToken)> SignInWithCredentialsAsync(string email, string password); + Task<(User User, RefreshToken RefreshToken)> SignInWithRefreshTokenAsync(string refreshToken, string expiredToken); Task> ListPaginateAsync(UserFilter filter); } diff --git a/src/Orion.Domain.Core/Services/UserService.cs b/src/Orion.Domain.Core/Services/UserService.cs index 351143c..93a03f8 100644 --- a/src/Orion.Domain.Core/Services/UserService.cs +++ b/src/Orion.Domain.Core/Services/UserService.cs @@ -50,11 +50,17 @@ public async Task FindByIdAsync(string publicId) return await _unitOfWork.UserRepository.GetByIdAsync(publicId); } - public async Task LoginAsync(string email, string password) + public async Task<(User User, RefreshToken RefreshToken)> SignInWithCredentialsAsync(string email, string password) { var user = await _unitOfWork.UserRepository.LoginAsync(email, password.ToSha512()); - return user ?? throw new UnauthorizedUserException(_messages[UserMessages.InvalidCredentials], _messages[ExceptionsTitles.AuthenticationError]); + if (user is not null) + { + var refreshToken = await AddRefreshTokenAsync(user.Email); + return (user, refreshToken); + } + + throw new UnauthorizedUserException(_messages[UserMessages.InvalidCredentials], _messages[ExceptionsTitles.AuthenticationError]); } public async Task UpdateAsync(User user) @@ -71,20 +77,26 @@ public async Task UpdateAsync(User user) await _unitOfWork.CommitAsync(); } - public async Task AddRefreshTokenAsync(RefreshToken refreshToken) + private async Task AddRefreshTokenAsync(string userEmail) { - var existantRefreshToken = (await _unitOfWork.RefreshTokenRepository.SearchByAsync(x => x.Email == refreshToken.Email)).FirstOrDefault(); + var existantRefreshToken = (await _unitOfWork.RefreshTokenRepository.SearchByAsync(x => x.Email == userEmail)).FirstOrDefault(); if (existantRefreshToken is not null) return existantRefreshToken; + var refreshToken = new RefreshToken + { + Email = userEmail, + Refreshtoken = Guid.NewGuid().ToString().ToSha512() + }; + var addedRefreshToken = await _unitOfWork.RefreshTokenRepository.AddAsync(refreshToken); await _unitOfWork.CommitAsync(); return addedRefreshToken; } - public async Task SignInWithRehreshTokenAsync(string refreshToken, string expiredToken) + public async Task<(User User, RefreshToken RefreshToken)> SignInWithRefreshTokenAsync(string refreshToken, string expiredToken) { if (string.IsNullOrEmpty(refreshToken)) { @@ -105,9 +117,9 @@ public async Task SignInWithRehreshTokenAsync(string refreshToken, string if (user is not null) { await _unitOfWork.RefreshTokenRepository.DeleteAsync(userRefreshToken.PublicId); - await _unitOfWork.CommitAsync(); + var newRefreshToken = await AddRefreshTokenAsync(user.Email); - return user; + return (user, newRefreshToken); } } diff --git a/tests/Orion.Test/Api/Controllers/AuthControllerTest.cs b/tests/Orion.Test/Api/Controllers/AuthControllerTest.cs index e8cc445..730d356 100644 --- a/tests/Orion.Test/Api/Controllers/AuthControllerTest.cs +++ b/tests/Orion.Test/Api/Controllers/AuthControllerTest.cs @@ -8,6 +8,7 @@ using Orion.Application.Core.Commands.LoginWithCredentials; using Orion.Application.Core.Commands.LoginWithRefreshToken; using Orion.Domain.Core.Entities; +using Orion.Domain.Core.Entities.Enuns; using Orion.Test.Api.Controllers.BaseController; using Orion.Test.Faker; using System; @@ -78,9 +79,16 @@ public async Task Login_WithInvalidCredentials_RetunsUnauthorized() public async Task RefreshToken_WithValidRefreshToken_ReturnsNewToken() { //arrange & act - var (token, _) = AuthenticationConfiguration.CreateToken(new LoginWithCredentialsResponse { Email = _validUser.Email, Name = _validUser.Name, PublicId = _validUser.PublicId }, _configuration); + var (token, _) = AuthenticationConfiguration.CreateToken(new LoginWithCredentialsResponse + { + Email = _validUser.Email, + Name = _validUser.Name, + PublicId = _validUser.PublicId, + Profile = UserProfile.Admin + }, + _configuration); - var result = await _authController.RefreshToken(new LoginWithRefreshTokenRequest { RefreshToken = _validRefreshToken.Refreshtoken, Token = token}); + var result = await _authController.RefreshToken(new LoginWithRefreshTokenRequest { RefreshToken = _validRefreshToken.Refreshtoken, Token = token }); var contentResult = (OkObjectResult)result; var userApiToken = (UserApiTokenModel)contentResult.Value; @@ -113,14 +121,15 @@ private void SetupMediatorMock() { var mediatorMock = new Mock(); - mediatorMock.Setup(x => x.Send(It.Is(x => x.Password == _validUser.Password && x.Email == _validUser.Email), + mediatorMock.Setup(x => x.Send(It.Is(x => x.Password == _validUser.Password && x.Email == _validUser.Email), It.IsAny())) .ReturnsAsync(new LoginWithCredentialsResponse { Email = _validUser.Email, Name = _validUser.Name, Profile = _validUser.Profile, - PublicId = _validUser.PublicId + PublicId = _validUser.PublicId, + RefreshToken = _validRefreshToken.Refreshtoken }); mediatorMock.Setup(x => x.Send(It.Is(x => x.RefreshToken == _validRefreshToken.Refreshtoken), @@ -130,7 +139,8 @@ private void SetupMediatorMock() Email = _validUser.Email, Name = _validUser.Name, Profile = _validUser.Profile, - PublicId = _validUser.PublicId + PublicId = _validUser.PublicId, + RefreshToken = _validRefreshToken.Refreshtoken }); var inMemorySettings = new Dictionary { diff --git a/tests/Orion.Test/Domain/Services/UserServiceTest.cs b/tests/Orion.Test/Domain/Services/UserServiceTest.cs index dcddb08..dd578b5 100644 --- a/tests/Orion.Test/Domain/Services/UserServiceTest.cs +++ b/tests/Orion.Test/Domain/Services/UserServiceTest.cs @@ -178,7 +178,7 @@ public async Task LoginAsync_WithValidCredentials_LoginAsSuccess() Assert.NotNull(userFound); //act - var userLoged = await userService.LoginAsync(userFound.Email, userPassword); + var (userLoged, refreshToken) = await userService.SignInWithCredentialsAsync(userFound.Email, userPassword); //assert Assert.NotNull(userLoged); @@ -190,7 +190,7 @@ public async Task LoginAsync_WithValidCredentials_LoginAsSuccess() } [Fact] - public async Task LoginAsync_WithInvalidCredentials_ThrowsUnauthorizedUserException() + public async Task SignInWithCredentialsAsync_WithInvalidCredentials_ThrowsUnauthorizedUserException() { //arrange using var scope = ServiceProvider.CreateScope(); @@ -200,45 +200,17 @@ public async Task LoginAsync_WithInvalidCredentials_ThrowsUnauthorizedUserExcept var userFound = await userService.FindByIdAsync(userAdded.PublicId); //act & assert - await Assert.ThrowsAsync(() => userService.LoginAsync(userFound.Email, "wrong pass")); + await Assert.ThrowsAsync(() => userService.SignInWithCredentialsAsync(userFound.Email, "wrong pass")); Assert.NotNull(userFound); await userService.DeleteAsync(userFound.PublicId); } - [Fact] - public async Task RefreshTokenAddAync_WithValidEmail_AddARefreskToken() - { - //arrange - using var scope = ServiceProvider.CreateScope(); - var userService = scope.ServiceProvider.GetService(); - - var userAdded = await userService.AddAsync(UserFaker.Get()); - var userFound = await userService.FindByIdAsync(userAdded.PublicId); - Assert.NotNull(userFound); - - var refreshToken = Guid.NewGuid().ToString(); - - var (token, _) = AuthenticationConfiguration.CreateToken(new LoginWithCredentialsResponse { Email = userAdded.Email, Name = userAdded.Name, PublicId = userAdded.PublicId }, GetCofiguration()); - - var refreshTokenAdded = await userService.AddRefreshTokenAsync(new RefreshToken { Email = userAdded.Email, Refreshtoken = refreshToken }); - - //act - var userByRefreshToken = await userService.SignInWithRehreshTokenAsync(refreshTokenAdded.Refreshtoken, token); - - //assert - Assert.NotNull(userByRefreshToken); - Assert.Equal(userByRefreshToken.Email, userAdded.Email); - Assert.Equal(userByRefreshToken.Password, userAdded.Password); - Assert.Equal(userByRefreshToken.Name, userAdded.Name); - - await userService.DeleteAsync(userFound.PublicId); - } [Theory] [InlineData(null, null)] [InlineData("Invalid refresh token", "invalid old token")] - public async Task GetUserByRefreshTokenAsync_WithInvalidId_ThrowsUnauthorizedUserException(string refreshToken, string token) + public async Task SignInWithRefreshTokenAsync_WithInvalidToken_ThrowsUnauthorizedUserException(string refreshToken, string token) { //arrange using var scope = ServiceProvider.CreateScope(); @@ -249,11 +221,9 @@ public async Task GetUserByRefreshTokenAsync_WithInvalidId_ThrowsUnauthorizedUse var userFound = await userService.FindByIdAsync(userAdded.PublicId); Assert.NotNull(userFound); - - await userService.AddRefreshTokenAsync(new RefreshToken { Email = userAdded.Email, Refreshtoken = Guid.NewGuid().ToString() }); - + //act - var exeption = await Assert.ThrowsAsync(() => userService.SignInWithRehreshTokenAsync(refreshToken, token)); + var exeption = await Assert.ThrowsAsync(() => userService.SignInWithRefreshTokenAsync(refreshToken, token)); //assert Assert.Equal(exeption.Message, messages[UserMessages.InvalidRefreshToken]);