diff --git a/Src/DDD.Infra.CrossCutting.Identity/Services/AuthService.cs b/Src/DDD.Infra.CrossCutting.Identity/Services/AuthService.cs new file mode 100644 index 0000000..42495a9 --- /dev/null +++ b/Src/DDD.Infra.CrossCutting.Identity/Services/AuthService.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using DDD.Domain.Interfaces; +using DDD.Infra.CrossCutting.Identity.Data; +using DDD.Infra.CrossCutting.Identity.Models; +using DDD.Infra.CrossCutting.Identity.Models.AccountViewModels; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; + +namespace DDD.Infra.CrossCutting.Identity.Services +{ + public class AuthService : IAuthService + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly RoleManager _roleManager; + private readonly AuthDbContext _dbContext; + private readonly IJwtFactory _jwtFactory; + private readonly IUser _user; + + public AuthService(UserManager userManager, + SignInManager signInManager, + RoleManager roleManager, + AuthDbContext dbContext, + IJwtFactory jwtFactory, + IUser user) + { + _userManager = userManager; + _signInManager = signInManager; + _roleManager = roleManager; + _dbContext = dbContext; + _jwtFactory = jwtFactory; + _user = user; + } + public async Task<(TokenViewModel, string)> LoginAsync(string email, string password) + { + var signInResult = await _signInManager.PasswordSignInAsync(email, password, false, true); + + if (!signInResult.Succeeded) + { + return (null, signInResult.ToString()); + } + + var user = await _userManager.FindByEmailAsync(email); + + return (await GenerateToken(user), null); + } + + public async Task<(bool, IdentityResult)> RegisterAsync(string email, string password) + { + var user = new ApplicationUser { UserName = email, Email = email }; + var userCreated = await _userManager.CreateAsync(user,password); + if (!userCreated.Succeeded) + { + return (false, userCreated); + } + + var roleAssigned = await _userManager.AddToRoleAsync(user, "Admin"); + if (!userCreated.Succeeded) + { + return (false, roleAssigned); + } + + var userClaims = new List + { + new("Customers_Write", "Write"), + new("Customers_Remove", "Remove"), + }; + await _userManager.AddClaimsAsync(user, userClaims); + + return (true, null); + } + + public async Task<(TokenViewModel, string, string)> RefreshTokenAsync(string accessToken, string refreshToken) + { + var refreshTokenCurrent = await _dbContext.RefreshTokens.SingleOrDefaultAsync + (x => x.Token == refreshToken && !x.Used && !x.Invalidated); + + if (refreshTokenCurrent.ExpiryDate < DateTime.UtcNow) + { + refreshTokenCurrent.Invalidated = true; + await _dbContext.SaveChangesAsync(); + return (null, "RefreshToken", "Refresh token invalid"); + } + + // Get User + var appUser = await _userManager.FindByIdAsync(refreshTokenCurrent.UserId); + if (appUser is null) + { + return new (null, "User", "User does not exist"); + } + + refreshTokenCurrent.Used = true; + await _dbContext.SaveChangesAsync(); + + return new(await GenerateToken(appUser), null, null); + } + + public dynamic GetCurrentUser() => + new + { + IsAuthenticated = _user.IsAuthenticated(), + ClaimsIdentity = _user.GetClaimsIdentity().Select(x => new { x.Type, x.Value }), + }; + + private async Task GenerateToken(ApplicationUser appUser) + { + var claimsIdentity = new ClaimsIdentity(await GetClaims(appUser)); + + var jwtToken = await _jwtFactory.GenerateJwtToken(claimsIdentity); + + var refreshToken = new RefreshToken + { + Token = Guid.NewGuid().ToString("N"), + UserId = appUser.Id, + CreationDate = DateTime.UtcNow, + ExpiryDate = DateTime.UtcNow.AddMinutes(90), + JwtId = jwtToken.JwtId + }; + await _dbContext.RefreshTokens.AddAsync(refreshToken); + await _dbContext.SaveChangesAsync(); + + return new TokenViewModel + { + AccessToken = jwtToken.AccessToken, + RefreshToken = refreshToken.Token, + }; + } + + private async Task> GetClaims(ApplicationUser user) + { + var claims = new List + { + new(JwtRegisteredClaimNames.Email, user.Email), + new(ClaimTypes.NameIdentifier, user.Id), + }; + + claims.AddRange(await _userManager.GetClaimsAsync(user)); + + var userRoles = await _userManager.GetRolesAsync(user); + claims.AddRange(userRoles.Select(role => new Claim(ClaimsIdentity.DefaultRoleClaimType, role))); + + foreach (var userRole in userRoles) + { + var role = await _roleManager.FindByNameAsync(userRole); + var roleClaims = await _roleManager.GetClaimsAsync(role); + claims.AddRange(roleClaims); + } + + return claims; + } + } +} diff --git a/Src/DDD.Infra.CrossCutting.Identity/Services/IAuthService.cs b/Src/DDD.Infra.CrossCutting.Identity/Services/IAuthService.cs new file mode 100644 index 0000000..bf2a619 --- /dev/null +++ b/Src/DDD.Infra.CrossCutting.Identity/Services/IAuthService.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using DDD.Infra.CrossCutting.Identity.Models.AccountViewModels; +using Microsoft.AspNetCore.Identity; + +namespace DDD.Infra.CrossCutting.Identity.Services +{ + public interface IAuthService + { + Task<(TokenViewModel, string)> LoginAsync(string email, string password); + Task<(bool, IdentityResult)> RegisterAsync(string email, string password); + Task<(TokenViewModel, string, string)> RefreshTokenAsync(string accessToken, string refreshToken); + dynamic GetCurrentUser(); + } +} diff --git a/Src/DDD.Services.Api/Controllers/AccountController.cs b/Src/DDD.Services.Api/Controllers/AccountController.cs index 5bceea5..96a157b 100644 --- a/Src/DDD.Services.Api/Controllers/AccountController.cs +++ b/Src/DDD.Services.Api/Controllers/AccountController.cs @@ -1,19 +1,10 @@ -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; using DDD.Domain.Core.Bus; using DDD.Domain.Core.Notifications; -using DDD.Domain.Interfaces; -using DDD.Infra.CrossCutting.Identity.Data; -using DDD.Infra.CrossCutting.Identity.Models; using DDD.Infra.CrossCutting.Identity.Models.AccountViewModels; using DDD.Infra.CrossCutting.Identity.Services; using MediatR; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -22,31 +13,16 @@ namespace DDD.Services.Api.Controllers [Authorize] public class AccountController : ApiController { - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - private readonly RoleManager _roleManager; - private readonly AuthDbContext _dbContext; - private readonly IUser _user; - private readonly IJwtFactory _jwtFactory; + private readonly IAuthService _authService; private readonly ILogger _logger; public AccountController( - UserManager userManager, - SignInManager signInManager, - RoleManager roleManager, - AuthDbContext dbContext, - IUser user, - IJwtFactory jwtFactory, + IAuthService authService, ILoggerFactory loggerFactory, INotificationHandler notifications, IMediatorHandler mediator) : base(notifications, mediator) { - _userManager = userManager; - _signInManager = signInManager; - _roleManager = roleManager; - _dbContext = dbContext; - _user = user; - _jwtFactory = jwtFactory; + _authService = authService; _logger = loggerFactory.CreateLogger(); } @@ -61,20 +37,17 @@ public async Task Login([FromBody] LoginViewModel model) return Response(); } - // Sign In - var signInResult = await _signInManager.PasswordSignInAsync(model.Email, model.Password, false, true); - if (!signInResult.Succeeded) + var (token, error) = await _authService.LoginAsync(model.Email, model.Password); + + if (token is null) { - NotifyError(signInResult.ToString(), "Login failure"); + NotifyError(error, "Login failure"); return Response(); } - // Get User - var appUser = await _userManager.FindByEmailAsync(model.Email); - //var appUser = _userManager.Users.SingleOrDefault(r => r.Email == model.Email); - _logger.LogInformation(1, "User logged in."); - return Response(await GenerateToken(appUser)); + + return Response(token); } [HttpPost] @@ -88,34 +61,14 @@ public async Task Register(RegisterViewModel model) return Response(); } - // Add User - var appUser = new ApplicationUser { UserName = model.Email, Email = model.Email }; - var identityResult = await _userManager.CreateAsync(appUser, model.Password); - if (!identityResult.Succeeded) + var (isRegistrationCompleted, error) = await _authService.RegisterAsync(model.Email, model.Password); + + if (!isRegistrationCompleted) { - AddIdentityErrors(identityResult); + AddIdentityErrors(error); return Response(); } - // Add UserRoles - identityResult = await _userManager.AddToRoleAsync(appUser, "Admin"); - if (!identityResult.Succeeded) - { - AddIdentityErrors(identityResult); - return Response(); - } - - // Add UserClaims - var userClaims = new List - { - new Claim("Customers_Write", "Write"), - new Claim("Customers_Remove", "Remove"), - }; - await _userManager.AddClaimsAsync(appUser, userClaims); - - // SignIn - //await _signInManager.SignInAsync(user, false); - _logger.LogInformation(3, "User created a new account with password."); return Response(); } @@ -131,97 +84,19 @@ public async Task Refresh(TokenViewModel model) return Response(); } - // Get current RefreshToken - var refreshTokenCurrent = _dbContext.RefreshTokens.SingleOrDefault - (x => x.Token == model.RefreshToken && !x.Used && !x.Invalidated); - if (refreshTokenCurrent is null) - { - NotifyError("RefreshToken", "Refresh token does not exist"); - return Response(); - } - if (refreshTokenCurrent.ExpiryDate < DateTime.UtcNow) - { - // Update current RefreshToken - refreshTokenCurrent.Invalidated = true; - await _dbContext.SaveChangesAsync(); - NotifyError("RefreshToken", "Refresh token invalid"); - return Response(); - } + var (token, errorCode, errorMessage) = await _authService.RefreshTokenAsync(model.AccessToken, model.RefreshToken); - // Get User - var appUser = await _userManager.FindByIdAsync(refreshTokenCurrent.UserId); - if (appUser is null) + if (token is null) { - NotifyError("User", "User does not exist"); + NotifyError(errorCode, errorMessage); return Response(); } - - // Remove current RefreshToken - //_dbContext.Remove(refreshTokenCurrent); - //await _dbContext.SaveChangesAsync(); - - // Update current RefreshToken - refreshTokenCurrent.Used = true; - await _dbContext.SaveChangesAsync(); - - return Response(await GenerateToken(appUser)); + + return Response(token); } [HttpGet] [Route("current")] - public IActionResult GetCurrent() - { - return Response(new - { - IsAuthenticated = _user.IsAuthenticated(), - ClaimsIdentity = _user.GetClaimsIdentity().Select(x => new { x.Type, x.Value }), - }); - } - - private async Task GenerateToken(ApplicationUser appUser) - { - // Init ClaimsIdentity - var claimsIdentity = new ClaimsIdentity(); - claimsIdentity.AddClaim(new Claim(JwtRegisteredClaimNames.Email, appUser.Email)); - claimsIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, appUser.Id)); - - // Get UserClaims - var userClaims = await _userManager.GetClaimsAsync(appUser); - claimsIdentity.AddClaims(userClaims); - - // Get UserRoles - var userRoles = await _userManager.GetRolesAsync(appUser); - claimsIdentity.AddClaims(userRoles.Select(role => new Claim(ClaimsIdentity.DefaultRoleClaimType, role))); - // ClaimsIdentity.DefaultRoleClaimType & ClaimTypes.Role is the same - - // Get RoleClaims - foreach (var userRole in userRoles) - { - var role = await _roleManager.FindByNameAsync(userRole); - var roleClaims = await _roleManager.GetClaimsAsync(role); - claimsIdentity.AddClaims(roleClaims); - } - - // Generate access token - var jwtToken = await _jwtFactory.GenerateJwtToken(claimsIdentity); - - // Add refresh token - var refreshToken = new RefreshToken - { - Token = Guid.NewGuid().ToString("N"), - UserId = appUser.Id, - CreationDate = DateTime.UtcNow, - ExpiryDate = DateTime.UtcNow.AddMinutes(90), - JwtId = jwtToken.JwtId - }; - await _dbContext.RefreshTokens.AddAsync(refreshToken); - await _dbContext.SaveChangesAsync(); - - return new TokenViewModel - { - AccessToken = jwtToken.AccessToken, - RefreshToken = refreshToken.Token, - }; - } + public IActionResult GetCurrent() => Response(_authService.GetCurrentUser()); } } diff --git a/Src/DDD.Services.Api/StartupExtensions/AuthExtension.cs b/Src/DDD.Services.Api/StartupExtensions/AuthExtension.cs index 72e22d9..5f39890 100644 --- a/Src/DDD.Services.Api/StartupExtensions/AuthExtension.cs +++ b/Src/DDD.Services.Api/StartupExtensions/AuthExtension.cs @@ -3,6 +3,7 @@ using DDD.Infra.CrossCutting.Identity.Authorization; using DDD.Infra.CrossCutting.Identity.Data; using DDD.Infra.CrossCutting.Identity.Models; +using DDD.Infra.CrossCutting.Identity.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; @@ -79,6 +80,8 @@ public static IServiceCollection AddCustomizedAuth(this IServiceCollection servi options.AddPolicy("CanRemoveCustomerData", policy2); }); + services.AddTransient(); + return services; }