From d534601ff86510d6e729840bfab7c5ef77f329c4 Mon Sep 17 00:00:00 2001 From: Nikolai Arkharov Date: Mon, 20 Sep 2021 02:34:41 +0300 Subject: [PATCH 1/8] add commands, implement basic register and login functionality --- .../Controllers/AccountController.cs | 38 ++++++++++++ server/StudySharp.API/Startup.cs | 7 +++ .../Commands/LoginCommand.cs | 59 +++++++++++++++++++ .../Commands/LogoutCommand.cs | 9 +++ .../Commands/RegisterNewUserCommand.cs | 53 +++++++++++++++++ .../JwtService/LoginResult.cs | 10 ++++ .../StudySharp.ApplicationServices/Startup.cs | 2 +- .../Constants/ErrorConstants.cs | 1 + .../ApplicationUser.cs | 8 +++ server/StudySharp.DomainServices/Startup.cs | 2 +- .../StudySharpDbContext.cs | 2 +- 11 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 server/StudySharp.API/Controllers/AccountController.cs create mode 100644 server/StudySharp.ApplicationServices/Commands/LoginCommand.cs create mode 100644 server/StudySharp.ApplicationServices/Commands/LogoutCommand.cs create mode 100644 server/StudySharp.ApplicationServices/Commands/RegisterNewUserCommand.cs create mode 100644 server/StudySharp.ApplicationServices/JwtService/LoginResult.cs create mode 100644 server/StudySharp.DomainServices/ApplicationUser.cs diff --git a/server/StudySharp.API/Controllers/AccountController.cs b/server/StudySharp.API/Controllers/AccountController.cs new file mode 100644 index 0000000..9e4a26b --- /dev/null +++ b/server/StudySharp.API/Controllers/AccountController.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using StudySharp.ApplicationServices.Commands; +using StudySharp.Domain.General; + +namespace StudySharp.API.Controllers +{ + [ApiController] + [Route("[controller]")] + public class AccountController : ControllerBase + { + private readonly IMediator _mediator; + + public AccountController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpPost("register")] + public async Task Register([FromBody] RegisterNewUserCommand registerNewUserCommand) + { + return await _mediator.Send(registerNewUserCommand); + } + + [HttpPost("login")] + public async Task> Login([FromBody] LoginCommand loginCommand) + { + return await _mediator.Send>(loginCommand); + } + + [HttpPost("logout")] + public async Task Logout([FromBody] LogoutCommand logoutCommand) + { + return await _mediator.Send(logoutCommand); + } + } +} diff --git a/server/StudySharp.API/Startup.cs b/server/StudySharp.API/Startup.cs index 1446dea..a5fbf59 100644 --- a/server/StudySharp.API/Startup.cs +++ b/server/StudySharp.API/Startup.cs @@ -31,6 +31,11 @@ public void ConfigureServices(IServiceCollection services) { c.SwaggerDoc("v1", new OpenApiInfo { Title = "StudySharp.API", Version = "v1" }); }); + + services.AddCors(options => + { + options.AddPolicy("AllowAll", builder => { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); }); + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -46,6 +51,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app .UseMiddleware() .UseRouting() + .UseCors("AllowAll") + .UseAuthentication() .UseAuthorization() .UseEndpoints(endpoints => { diff --git a/server/StudySharp.ApplicationServices/Commands/LoginCommand.cs b/server/StudySharp.ApplicationServices/Commands/LoginCommand.cs new file mode 100644 index 0000000..23857ca --- /dev/null +++ b/server/StudySharp.ApplicationServices/Commands/LoginCommand.cs @@ -0,0 +1,59 @@ +using System; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Identity; +using StudySharp.ApplicationServices.JwtService; +using StudySharp.Domain.Constants; +using StudySharp.Domain.General; +using StudySharp.DomainServices; + +namespace StudySharp.ApplicationServices.Commands +{ + public sealed class LoginCommand : IRequest> + { + public string Email { get; set; } + public string Password { get; set; } + } + + public sealed class LoginCommandHandler : IRequestHandler> + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IJwtAuthManager _jwtAuthManager; + + public LoginCommandHandler(UserManager userManager, SignInManager signInManager, IJwtAuthManager jwtAuthManager) + { + _userManager = userManager; + _signInManager = signInManager; + _jwtAuthManager = jwtAuthManager; + } + + public async Task> Handle(LoginCommand request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByNameAsync(request.Email); + if (user == null) + { + return OperationResult.Fail(ErrorConstants.InvalidCredentials); + } + + var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false); + + if (!result.Succeeded) + { + return OperationResult.Fail(ErrorConstants.InvalidCredentials); + } + + var jwtResult = _jwtAuthManager.GenerateTokens(user.UserName, new Claim[] { new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName) }, DateTime.UtcNow); + + return OperationResult.Ok(new LoginResult + { + UserName = user.UserName, + Role = "Test", + AccessToken = jwtResult.AccessToken, + RefreshToken = jwtResult.RefreshToken.TokenString, + }); + } + } +} diff --git a/server/StudySharp.ApplicationServices/Commands/LogoutCommand.cs b/server/StudySharp.ApplicationServices/Commands/LogoutCommand.cs new file mode 100644 index 0000000..cb32fb6 --- /dev/null +++ b/server/StudySharp.ApplicationServices/Commands/LogoutCommand.cs @@ -0,0 +1,9 @@ +using MediatR; +using StudySharp.Domain.General; + +namespace StudySharp.ApplicationServices.Commands +{ + public sealed class LogoutCommand : IRequest + { + } +} diff --git a/server/StudySharp.ApplicationServices/Commands/RegisterNewUserCommand.cs b/server/StudySharp.ApplicationServices/Commands/RegisterNewUserCommand.cs new file mode 100644 index 0000000..0034e96 --- /dev/null +++ b/server/StudySharp.ApplicationServices/Commands/RegisterNewUserCommand.cs @@ -0,0 +1,53 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Identity; +using StudySharp.Domain.Constants; +using StudySharp.Domain.General; +using StudySharp.DomainServices; + +namespace StudySharp.ApplicationServices.Commands +{ + public sealed class RegisterNewUserCommand : IRequest + { + public string Email { get; set; } + public string Password { get; set; } + public string ConfirmPassword { get; set; } + } + + public sealed class RegisterNewUserCommandHandler : IRequestHandler + { + private readonly UserManager _userManager; + + public RegisterNewUserCommandHandler(UserManager userManager) + { + _userManager = userManager; + } + + public async Task Handle(RegisterNewUserCommand request, CancellationToken cancellationToken) + { + // compare passwords? + var user = await _userManager.FindByNameAsync(request.Email); + if (user != null) + { + return OperationResult.Fail(string.Format(ErrorConstants.EntityAlreadyExists, "User", "email", request.Email)); + } + + var newUser = new ApplicationUser + { + UserName = request.Email, + Email = request.Email, + }; + var result = await _userManager.CreateAsync(newUser, request.Password); + + if (!result.Succeeded) + { + return OperationResult.Fail(result.Errors.Select(_ => _.Description).ToList()); + } + + // redirect to confirm email and then login? + return OperationResult.Ok(); + } + } +} diff --git a/server/StudySharp.ApplicationServices/JwtService/LoginResult.cs b/server/StudySharp.ApplicationServices/JwtService/LoginResult.cs new file mode 100644 index 0000000..ed13699 --- /dev/null +++ b/server/StudySharp.ApplicationServices/JwtService/LoginResult.cs @@ -0,0 +1,10 @@ +namespace StudySharp.ApplicationServices.Commands +{ + public sealed class LoginResult + { + public string UserName { get; set; } + public string Role { get; set; } + public string AccessToken { get; set; } + public string RefreshToken { get; set; } + } +} \ No newline at end of file diff --git a/server/StudySharp.ApplicationServices/Startup.cs b/server/StudySharp.ApplicationServices/Startup.cs index a5c8692..24eff59 100644 --- a/server/StudySharp.ApplicationServices/Startup.cs +++ b/server/StudySharp.ApplicationServices/Startup.cs @@ -27,7 +27,7 @@ public static IServiceCollection AddApplicationServices( }).AddJwtBearer(x => { x.RequireHttpsMetadata = false; - x.SaveToken = true; + x.SaveToken = false; x.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, diff --git a/server/StudySharp.Domain/Constants/ErrorConstants.cs b/server/StudySharp.Domain/Constants/ErrorConstants.cs index 189298d..6af4da6 100644 --- a/server/StudySharp.Domain/Constants/ErrorConstants.cs +++ b/server/StudySharp.Domain/Constants/ErrorConstants.cs @@ -4,5 +4,6 @@ public static class ErrorConstants { public const string EntityNotFound = "{0} with {1} '{2}' not found!"; public const string EntityAlreadyExists = "{0} with {1} '{2}' already exists!"; + public const string InvalidCredentials = "Invalid username or password!"; } } diff --git a/server/StudySharp.DomainServices/ApplicationUser.cs b/server/StudySharp.DomainServices/ApplicationUser.cs new file mode 100644 index 0000000..918cb13 --- /dev/null +++ b/server/StudySharp.DomainServices/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace StudySharp.DomainServices +{ + public sealed class ApplicationUser : IdentityUser + { + } +} diff --git a/server/StudySharp.DomainServices/Startup.cs b/server/StudySharp.DomainServices/Startup.cs index 3864118..58445b7 100644 --- a/server/StudySharp.DomainServices/Startup.cs +++ b/server/StudySharp.DomainServices/Startup.cs @@ -20,7 +20,7 @@ public static IServiceCollection AddDomainServices( configuration.GetConnectionString(ConnectionStrings.Default), x => x.MigrationsAssembly(typeof(Startup).Assembly.GetName().FullName)); }, ServiceLifetime.Transient) - .AddIdentity() + .AddIdentity>() .AddEntityFrameworkStores() .AddDefaultTokenProviders(); diff --git a/server/StudySharp.DomainServices/StudySharpDbContext.cs b/server/StudySharp.DomainServices/StudySharpDbContext.cs index d323628..6d539b0 100644 --- a/server/StudySharp.DomainServices/StudySharpDbContext.cs +++ b/server/StudySharp.DomainServices/StudySharpDbContext.cs @@ -10,7 +10,7 @@ namespace StudySharp.DomainServices { - public class StudySharpDbContext : IdentityDbContext + public class StudySharpDbContext : IdentityDbContext, int> { public DbSet Teachers { get; set; } public DbSet Courses { get; set; } From 957729939f931c10365e3655aeec5b4d684dc63d Mon Sep 17 00:00:00 2001 From: Nikolai Arkharov Date: Tue, 21 Sep 2021 00:09:07 +0300 Subject: [PATCH 2/8] refresh token implementation --- .../Controllers/AccountController.cs | 47 +++++++++++++- .../StudySharp.API/Requests/LoginRequest.cs | 8 +++ .../Requests/RefreshTokenRequest.cs | 7 +++ .../Requests/RegisterNewUserRequest.cs | 9 +++ .../StudySharp.API/Responses/LoginResponse.cs | 10 +++ .../Commands/LoginCommand.cs | 2 +- .../Commands/RefsreshTokenCommand.cs | 62 +++++++++++++++++++ .../Commands/RegisterNewUserCommand.cs | 2 +- .../JwtService/LoginResult.cs | 4 +- 9 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 server/StudySharp.API/Requests/LoginRequest.cs create mode 100644 server/StudySharp.API/Requests/RefreshTokenRequest.cs create mode 100644 server/StudySharp.API/Requests/RegisterNewUserRequest.cs create mode 100644 server/StudySharp.API/Responses/LoginResponse.cs create mode 100644 server/StudySharp.ApplicationServices/Commands/RefsreshTokenCommand.cs diff --git a/server/StudySharp.API/Controllers/AccountController.cs b/server/StudySharp.API/Controllers/AccountController.cs index 9e4a26b..0f1ea03 100644 --- a/server/StudySharp.API/Controllers/AccountController.cs +++ b/server/StudySharp.API/Controllers/AccountController.cs @@ -1,7 +1,10 @@ using System.Threading.Tasks; using MediatR; using Microsoft.AspNetCore.Mvc; +using StudySharp.API.Requests; +using StudySharp.API.Responses; using StudySharp.ApplicationServices.Commands; +using StudySharp.ApplicationServices.JwtService; using StudySharp.Domain.General; namespace StudySharp.API.Controllers @@ -18,15 +21,45 @@ public AccountController(IMediator mediator) } [HttpPost("register")] - public async Task Register([FromBody] RegisterNewUserCommand registerNewUserCommand) + public async Task Register([FromBody] RegisterNewUserRequest registerNewUserRequest) { + // AutoMapper is required ASAP!!! + var registerNewUserCommand = new RegisterNewUserCommand + { + Email = registerNewUserRequest.Email, + Password = registerNewUserRequest.Password, + ConfirmPassword = registerNewUserRequest.ConfirmPassword, + }; + return await _mediator.Send(registerNewUserCommand); } [HttpPost("login")] - public async Task> Login([FromBody] LoginCommand loginCommand) + public async Task> Login([FromBody] LoginRequest loginRequest) { - return await _mediator.Send>(loginCommand); + // AutoMapper is required ASAP!!! + var loginCommand = new LoginCommand + { + Email = loginRequest.Email, + Password = loginRequest.Password, + }; + + var operationResult = await _mediator.Send>(loginCommand); + + if (!operationResult.IsSucceeded) + { + return OperationResult.Fail(operationResult.Errors); + } + + var response = new LoginResponse + { + UserName = operationResult.Result.UserName, + AccessToken = operationResult.Result.AccessToken, + RefreshToken = operationResult.Result.RefreshToken, + Role = operationResult.Result.Role, + }; + + return OperationResult.Ok(response); } [HttpPost("logout")] @@ -34,5 +67,13 @@ public async Task Logout([FromBody] LogoutCommand logoutCommand { return await _mediator.Send(logoutCommand); } + + [HttpPost("refresh-token")] + public async Task> RefreshToken([FromBody] RefreshTokenRequest refreshTokenRequest) + { + var accessToken = HttpContext.Request.Headers["Authorization"].ToString().Split(" ")[1]; + var userName = User.Identity.Name; + return await _mediator.Send>(new RefreshTokenCommand { AccessToken = accessToken, RefreshToken = refreshTokenRequest.RefreshToken, UserName = userName }); + } } } diff --git a/server/StudySharp.API/Requests/LoginRequest.cs b/server/StudySharp.API/Requests/LoginRequest.cs new file mode 100644 index 0000000..78280d0 --- /dev/null +++ b/server/StudySharp.API/Requests/LoginRequest.cs @@ -0,0 +1,8 @@ +namespace StudySharp.API.Requests +{ + public sealed class LoginRequest + { + public string Email { get; set; } + public string Password { get; set; } + } +} diff --git a/server/StudySharp.API/Requests/RefreshTokenRequest.cs b/server/StudySharp.API/Requests/RefreshTokenRequest.cs new file mode 100644 index 0000000..e6358c3 --- /dev/null +++ b/server/StudySharp.API/Requests/RefreshTokenRequest.cs @@ -0,0 +1,7 @@ +namespace StudySharp.API.Requests +{ + public sealed class RefreshTokenRequest + { + public string RefreshToken { get; set; } + } +} diff --git a/server/StudySharp.API/Requests/RegisterNewUserRequest.cs b/server/StudySharp.API/Requests/RegisterNewUserRequest.cs new file mode 100644 index 0000000..6237c30 --- /dev/null +++ b/server/StudySharp.API/Requests/RegisterNewUserRequest.cs @@ -0,0 +1,9 @@ +namespace StudySharp.API.Requests +{ + public sealed class RegisterNewUserRequest + { + public string Email { get; set; } + public string Password { get; set; } + public string ConfirmPassword { get; set; } + } +} diff --git a/server/StudySharp.API/Responses/LoginResponse.cs b/server/StudySharp.API/Responses/LoginResponse.cs new file mode 100644 index 0000000..e4411df --- /dev/null +++ b/server/StudySharp.API/Responses/LoginResponse.cs @@ -0,0 +1,10 @@ +namespace StudySharp.API.Responses +{ + public sealed class LoginResponse + { + public string UserName { get; set; } + public string Role { get; set; } + public string AccessToken { get; set; } + public string RefreshToken { get; set; } + } +} diff --git a/server/StudySharp.ApplicationServices/Commands/LoginCommand.cs b/server/StudySharp.ApplicationServices/Commands/LoginCommand.cs index 23857ca..558b776 100644 --- a/server/StudySharp.ApplicationServices/Commands/LoginCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/LoginCommand.cs @@ -45,7 +45,7 @@ public async Task> Handle(LoginCommand request, Can return OperationResult.Fail(ErrorConstants.InvalidCredentials); } - var jwtResult = _jwtAuthManager.GenerateTokens(user.UserName, new Claim[] { new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName) }, DateTime.UtcNow); + var jwtResult = _jwtAuthManager.GenerateTokens(user.UserName, new Claim[] { new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName) }, DateTime.Now); return OperationResult.Ok(new LoginResult { diff --git a/server/StudySharp.ApplicationServices/Commands/RefsreshTokenCommand.cs b/server/StudySharp.ApplicationServices/Commands/RefsreshTokenCommand.cs new file mode 100644 index 0000000..5aa50a3 --- /dev/null +++ b/server/StudySharp.ApplicationServices/Commands/RefsreshTokenCommand.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Identity; +using Microsoft.IdentityModel.Tokens; +using StudySharp.ApplicationServices.JwtService; +using StudySharp.Domain.Constants; +using StudySharp.Domain.General; +using StudySharp.DomainServices; + +namespace StudySharp.ApplicationServices.Commands +{ + public sealed class RefreshTokenCommand : IRequest> + { + public string UserName { get; set; } + public string AccessToken { get; set; } + public string RefreshToken { get; set; } + } + + public sealed class RefsreshTokenCommandHandler : IRequestHandler> + { + private readonly UserManager _userManager; + private readonly IJwtAuthManager _jwtAuthManager; + + public RefsreshTokenCommandHandler(IJwtAuthManager jwtAuthManager, UserManager userManager) + { + _jwtAuthManager = jwtAuthManager; + _userManager = userManager; + } + + public async Task> Handle(RefreshTokenCommand request, CancellationToken cancellationToken) + { + try + { + var user = await _userManager.FindByNameAsync(request.UserName); + if (user == null) + { + return OperationResult.Fail(string.Format(ErrorConstants.EntityNotFound, "User", "Email", request.UserName)); + } + + if (string.IsNullOrEmpty(request.RefreshToken)) + { + return OperationResult.Fail(ErrorConstants.InvalidCredentials); + } + + var jwtResult = _jwtAuthManager.Refresh(request.RefreshToken, request.AccessToken, DateTime.Now); + return OperationResult.Ok(new LoginResult + { + UserName = user.UserName, + Role = "Test", + AccessToken = jwtResult.AccessToken, + RefreshToken = jwtResult.RefreshToken.TokenString, + }); + } + catch (SecurityTokenException exception) + { + return OperationResult.Fail(exception.Message); // return 401 so that the client side can redirect the user to login page + } + } + } +} diff --git a/server/StudySharp.ApplicationServices/Commands/RegisterNewUserCommand.cs b/server/StudySharp.ApplicationServices/Commands/RegisterNewUserCommand.cs index 0034e96..99b04c0 100644 --- a/server/StudySharp.ApplicationServices/Commands/RegisterNewUserCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/RegisterNewUserCommand.cs @@ -31,7 +31,7 @@ public async Task Handle(RegisterNewUserCommand request, Cancel var user = await _userManager.FindByNameAsync(request.Email); if (user != null) { - return OperationResult.Fail(string.Format(ErrorConstants.EntityAlreadyExists, "User", "email", request.Email)); + return OperationResult.Fail(string.Format(ErrorConstants.EntityAlreadyExists, "User", nameof(request.Email), request.Email)); } var newUser = new ApplicationUser diff --git a/server/StudySharp.ApplicationServices/JwtService/LoginResult.cs b/server/StudySharp.ApplicationServices/JwtService/LoginResult.cs index ed13699..2f0c0f9 100644 --- a/server/StudySharp.ApplicationServices/JwtService/LoginResult.cs +++ b/server/StudySharp.ApplicationServices/JwtService/LoginResult.cs @@ -1,4 +1,4 @@ -namespace StudySharp.ApplicationServices.Commands +namespace StudySharp.ApplicationServices.JwtService { public sealed class LoginResult { @@ -7,4 +7,4 @@ public sealed class LoginResult public string AccessToken { get; set; } public string RefreshToken { get; set; } } -} \ No newline at end of file +} From eb70edd6cd89caa9d9c3562915017803c4dd8cd1 Mon Sep 17 00:00:00 2001 From: Nikolai Arkharov Date: Thu, 23 Sep 2021 21:37:25 +0300 Subject: [PATCH 3/8] add automapper --- .../Controllers/AccountController.cs | 62 ++++++++++--------- .../Controllers/WeatherForecastController.cs | 2 +- .../MapperProfiles/AuthProfile.cs | 18 ++++++ .../Requests/{ => Auth}/LoginRequest.cs | 2 +- .../{ => Auth}/RefreshTokenRequest.cs | 2 +- .../{ => Auth}/RegisterNewUserRequest.cs | 2 +- .../Responses/{ => Auth}/LoginResponse.cs | 4 +- .../Responses/Auth/RefreshTokenResponse.cs | 6 ++ server/StudySharp.API/Startup.cs | 4 +- server/StudySharp.API/StudySharp.API.csproj | 2 + .../Commands/{ => Auth}/LoginCommand.cs | 13 ++-- .../Commands/{ => Auth}/LogoutCommand.cs | 2 +- .../{ => Auth}/RefsreshTokenCommand.cs | 27 ++++---- .../{ => Auth}/RegisterNewUserCommand.cs | 2 +- .../JwtRefreshTokenCache.cs | 13 ++-- .../JwtService.cs} | 8 +-- .../JwtTokenConfig.cs | 2 +- .../ResultModels}/LoginResult.cs | 4 +- .../ResultModels/RefreshTokenResult.cs | 6 ++ .../StudySharp.ApplicationServices/Startup.cs | 4 +- 20 files changed, 115 insertions(+), 70 deletions(-) create mode 100644 server/StudySharp.API/MapperProfiles/AuthProfile.cs rename server/StudySharp.API/Requests/{ => Auth}/LoginRequest.cs (76%) rename server/StudySharp.API/Requests/{ => Auth}/RefreshTokenRequest.cs (72%) rename server/StudySharp.API/Requests/{ => Auth}/RegisterNewUserRequest.cs (82%) rename server/StudySharp.API/Responses/{ => Auth}/LoginResponse.cs (72%) create mode 100644 server/StudySharp.API/Responses/Auth/RefreshTokenResponse.cs rename server/StudySharp.ApplicationServices/Commands/{ => Auth}/LoginCommand.cs (77%) rename server/StudySharp.ApplicationServices/Commands/{ => Auth}/LogoutCommand.cs (70%) rename server/StudySharp.ApplicationServices/Commands/{ => Auth}/RefsreshTokenCommand.cs (52%) rename server/StudySharp.ApplicationServices/Commands/{ => Auth}/RegisterNewUserCommand.cs (96%) rename server/StudySharp.ApplicationServices/{JwtService => JwtAuthService}/JwtRefreshTokenCache.cs (68%) rename server/StudySharp.ApplicationServices/{JwtService/JwtAuthManager.cs => JwtAuthService/JwtService.cs} (97%) rename server/StudySharp.ApplicationServices/{JwtService => JwtAuthService}/JwtTokenConfig.cs (90%) rename server/StudySharp.ApplicationServices/{JwtService => JwtAuthService/ResultModels}/LoginResult.cs (66%) create mode 100644 server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/RefreshTokenResult.cs diff --git a/server/StudySharp.API/Controllers/AccountController.cs b/server/StudySharp.API/Controllers/AccountController.cs index 0f1ea03..fd96020 100644 --- a/server/StudySharp.API/Controllers/AccountController.cs +++ b/server/StudySharp.API/Controllers/AccountController.cs @@ -1,48 +1,44 @@ using System.Threading.Tasks; +using AutoMapper; using MediatR; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using StudySharp.API.Requests; -using StudySharp.API.Responses; -using StudySharp.ApplicationServices.Commands; -using StudySharp.ApplicationServices.JwtService; +using StudySharp.API.Requests.Auth; +using StudySharp.API.Responses.Auth; +using StudySharp.ApplicationServices.Commands.Auth; +using StudySharp.ApplicationServices.JwtAuthService.ResultModels; using StudySharp.Domain.General; namespace StudySharp.API.Controllers { + [Authorize] [ApiController] - [Route("[controller]")] + [Route("api/[controller]")] public class AccountController : ControllerBase { private readonly IMediator _mediator; + private readonly IMapper _mapper; - public AccountController(IMediator mediator) + public AccountController(IMediator mediator, IMapper mapper) { _mediator = mediator; + _mapper = mapper; } + [AllowAnonymous] [HttpPost("register")] public async Task Register([FromBody] RegisterNewUserRequest registerNewUserRequest) { - // AutoMapper is required ASAP!!! - var registerNewUserCommand = new RegisterNewUserCommand - { - Email = registerNewUserRequest.Email, - Password = registerNewUserRequest.Password, - ConfirmPassword = registerNewUserRequest.ConfirmPassword, - }; + var registerNewUserCommand = _mapper.Map(registerNewUserRequest); return await _mediator.Send(registerNewUserCommand); } + [AllowAnonymous] [HttpPost("login")] public async Task> Login([FromBody] LoginRequest loginRequest) { - // AutoMapper is required ASAP!!! - var loginCommand = new LoginCommand - { - Email = loginRequest.Email, - Password = loginRequest.Password, - }; + var loginCommand = _mapper.Map(loginRequest); var operationResult = await _mediator.Send>(loginCommand); @@ -51,13 +47,7 @@ public async Task> Login([FromBody] LoginRequest return OperationResult.Fail(operationResult.Errors); } - var response = new LoginResponse - { - UserName = operationResult.Result.UserName, - AccessToken = operationResult.Result.AccessToken, - RefreshToken = operationResult.Result.RefreshToken, - Role = operationResult.Result.Role, - }; + var response = _mapper.Map(operationResult.Result); return OperationResult.Ok(response); } @@ -69,11 +59,27 @@ public async Task Logout([FromBody] LogoutCommand logoutCommand } [HttpPost("refresh-token")] - public async Task> RefreshToken([FromBody] RefreshTokenRequest refreshTokenRequest) + public async Task> RefreshToken([FromBody] RefreshTokenRequest refreshTokenRequest) { var accessToken = HttpContext.Request.Headers["Authorization"].ToString().Split(" ")[1]; var userName = User.Identity.Name; - return await _mediator.Send>(new RefreshTokenCommand { AccessToken = accessToken, RefreshToken = refreshTokenRequest.RefreshToken, UserName = userName }); + var refreshTokenCommand = new RefreshTokenCommand + { + AccessToken = accessToken, + RefreshToken = refreshTokenRequest.RefreshToken, + UserName = userName, + }; + + var operationResult = await _mediator.Send>(refreshTokenCommand); + + if (!operationResult.IsSucceeded) + { + return OperationResult.Fail(operationResult.Errors); + } + + var response = _mapper.Map(operationResult.Result); + + return OperationResult.Ok(response); } } } diff --git a/server/StudySharp.API/Controllers/WeatherForecastController.cs b/server/StudySharp.API/Controllers/WeatherForecastController.cs index 60a78c1..fffff95 100644 --- a/server/StudySharp.API/Controllers/WeatherForecastController.cs +++ b/server/StudySharp.API/Controllers/WeatherForecastController.cs @@ -12,7 +12,7 @@ namespace StudySharp.API.Controllers { [ApiController] - [Route("[controller]")] + [Route("api/[controller]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] diff --git a/server/StudySharp.API/MapperProfiles/AuthProfile.cs b/server/StudySharp.API/MapperProfiles/AuthProfile.cs new file mode 100644 index 0000000..8a8114a --- /dev/null +++ b/server/StudySharp.API/MapperProfiles/AuthProfile.cs @@ -0,0 +1,18 @@ +using AutoMapper; +using StudySharp.API.Requests.Auth; +using StudySharp.API.Responses.Auth; +using StudySharp.ApplicationServices.Commands.Auth; +using StudySharp.ApplicationServices.JwtAuthService.ResultModels; + +namespace StudySharp.API.MapperProfiles +{ + public class AuthProfile : Profile + { + public AuthProfile() + { + CreateMap(); + CreateMap(); + CreateMap(); + } + } +} diff --git a/server/StudySharp.API/Requests/LoginRequest.cs b/server/StudySharp.API/Requests/Auth/LoginRequest.cs similarity index 76% rename from server/StudySharp.API/Requests/LoginRequest.cs rename to server/StudySharp.API/Requests/Auth/LoginRequest.cs index 78280d0..9d57d71 100644 --- a/server/StudySharp.API/Requests/LoginRequest.cs +++ b/server/StudySharp.API/Requests/Auth/LoginRequest.cs @@ -1,4 +1,4 @@ -namespace StudySharp.API.Requests +namespace StudySharp.API.Requests.Auth { public sealed class LoginRequest { diff --git a/server/StudySharp.API/Requests/RefreshTokenRequest.cs b/server/StudySharp.API/Requests/Auth/RefreshTokenRequest.cs similarity index 72% rename from server/StudySharp.API/Requests/RefreshTokenRequest.cs rename to server/StudySharp.API/Requests/Auth/RefreshTokenRequest.cs index e6358c3..e60cae4 100644 --- a/server/StudySharp.API/Requests/RefreshTokenRequest.cs +++ b/server/StudySharp.API/Requests/Auth/RefreshTokenRequest.cs @@ -1,4 +1,4 @@ -namespace StudySharp.API.Requests +namespace StudySharp.API.Requests.Auth { public sealed class RefreshTokenRequest { diff --git a/server/StudySharp.API/Requests/RegisterNewUserRequest.cs b/server/StudySharp.API/Requests/Auth/RegisterNewUserRequest.cs similarity index 82% rename from server/StudySharp.API/Requests/RegisterNewUserRequest.cs rename to server/StudySharp.API/Requests/Auth/RegisterNewUserRequest.cs index 6237c30..326297b 100644 --- a/server/StudySharp.API/Requests/RegisterNewUserRequest.cs +++ b/server/StudySharp.API/Requests/Auth/RegisterNewUserRequest.cs @@ -1,4 +1,4 @@ -namespace StudySharp.API.Requests +namespace StudySharp.API.Requests.Auth { public sealed class RegisterNewUserRequest { diff --git a/server/StudySharp.API/Responses/LoginResponse.cs b/server/StudySharp.API/Responses/Auth/LoginResponse.cs similarity index 72% rename from server/StudySharp.API/Responses/LoginResponse.cs rename to server/StudySharp.API/Responses/Auth/LoginResponse.cs index e4411df..7651d97 100644 --- a/server/StudySharp.API/Responses/LoginResponse.cs +++ b/server/StudySharp.API/Responses/Auth/LoginResponse.cs @@ -1,6 +1,6 @@ -namespace StudySharp.API.Responses +namespace StudySharp.API.Responses.Auth { - public sealed class LoginResponse + public class LoginResponse { public string UserName { get; set; } public string Role { get; set; } diff --git a/server/StudySharp.API/Responses/Auth/RefreshTokenResponse.cs b/server/StudySharp.API/Responses/Auth/RefreshTokenResponse.cs new file mode 100644 index 0000000..2756d6c --- /dev/null +++ b/server/StudySharp.API/Responses/Auth/RefreshTokenResponse.cs @@ -0,0 +1,6 @@ +namespace StudySharp.API.Responses.Auth +{ + public class RefreshTokenResponse : LoginResponse + { + } +} diff --git a/server/StudySharp.API/Startup.cs b/server/StudySharp.API/Startup.cs index a5fbf59..2c8f9eb 100644 --- a/server/StudySharp.API/Startup.cs +++ b/server/StudySharp.API/Startup.cs @@ -1,3 +1,4 @@ +using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -25,7 +26,8 @@ public void ConfigureServices(IServiceCollection services) services .AddDomainServices(Configuration) .AddApplicationServices(Configuration) - .AddEmailService(Configuration, "EmailConfig"); + .AddEmailService(Configuration, "EmailConfig") + .AddAutoMapper(Assembly.GetExecutingAssembly()); services.AddControllers(); services.AddSwaggerGen(c => { diff --git a/server/StudySharp.API/StudySharp.API.csproj b/server/StudySharp.API/StudySharp.API.csproj index 4648377..216e243 100644 --- a/server/StudySharp.API/StudySharp.API.csproj +++ b/server/StudySharp.API/StudySharp.API.csproj @@ -7,6 +7,8 @@ + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/server/StudySharp.ApplicationServices/Commands/LoginCommand.cs b/server/StudySharp.ApplicationServices/Commands/Auth/LoginCommand.cs similarity index 77% rename from server/StudySharp.ApplicationServices/Commands/LoginCommand.cs rename to server/StudySharp.ApplicationServices/Commands/Auth/LoginCommand.cs index 558b776..91fdc48 100644 --- a/server/StudySharp.ApplicationServices/Commands/LoginCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/Auth/LoginCommand.cs @@ -4,12 +4,13 @@ using System.Threading.Tasks; using MediatR; using Microsoft.AspNetCore.Identity; -using StudySharp.ApplicationServices.JwtService; +using StudySharp.ApplicationServices.JwtAuthService; +using StudySharp.ApplicationServices.JwtAuthService.ResultModels; using StudySharp.Domain.Constants; using StudySharp.Domain.General; using StudySharp.DomainServices; -namespace StudySharp.ApplicationServices.Commands +namespace StudySharp.ApplicationServices.Commands.Auth { public sealed class LoginCommand : IRequest> { @@ -21,13 +22,13 @@ public sealed class LoginCommandHandler : IRequestHandler _userManager; private readonly SignInManager _signInManager; - private readonly IJwtAuthManager _jwtAuthManager; + private readonly IJwtService _jwtService; - public LoginCommandHandler(UserManager userManager, SignInManager signInManager, IJwtAuthManager jwtAuthManager) + public LoginCommandHandler(UserManager userManager, SignInManager signInManager, IJwtService jwtAuthManager) { _userManager = userManager; _signInManager = signInManager; - _jwtAuthManager = jwtAuthManager; + _jwtService = jwtAuthManager; } public async Task> Handle(LoginCommand request, CancellationToken cancellationToken) @@ -45,7 +46,7 @@ public async Task> Handle(LoginCommand request, Can return OperationResult.Fail(ErrorConstants.InvalidCredentials); } - var jwtResult = _jwtAuthManager.GenerateTokens(user.UserName, new Claim[] { new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName) }, DateTime.Now); + var jwtResult = _jwtService.GenerateTokens(user.UserName, new Claim[] { new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName) }, DateTime.Now); return OperationResult.Ok(new LoginResult { diff --git a/server/StudySharp.ApplicationServices/Commands/LogoutCommand.cs b/server/StudySharp.ApplicationServices/Commands/Auth/LogoutCommand.cs similarity index 70% rename from server/StudySharp.ApplicationServices/Commands/LogoutCommand.cs rename to server/StudySharp.ApplicationServices/Commands/Auth/LogoutCommand.cs index cb32fb6..88aad6a 100644 --- a/server/StudySharp.ApplicationServices/Commands/LogoutCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/Auth/LogoutCommand.cs @@ -1,7 +1,7 @@ using MediatR; using StudySharp.Domain.General; -namespace StudySharp.ApplicationServices.Commands +namespace StudySharp.ApplicationServices.Commands.Auth { public sealed class LogoutCommand : IRequest { diff --git a/server/StudySharp.ApplicationServices/Commands/RefsreshTokenCommand.cs b/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs similarity index 52% rename from server/StudySharp.ApplicationServices/Commands/RefsreshTokenCommand.cs rename to server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs index 5aa50a3..8b05d38 100644 --- a/server/StudySharp.ApplicationServices/Commands/RefsreshTokenCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs @@ -4,48 +4,49 @@ using MediatR; using Microsoft.AspNetCore.Identity; using Microsoft.IdentityModel.Tokens; -using StudySharp.ApplicationServices.JwtService; +using StudySharp.ApplicationServices.JwtAuthService; +using StudySharp.ApplicationServices.JwtAuthService.ResultModels; using StudySharp.Domain.Constants; using StudySharp.Domain.General; using StudySharp.DomainServices; -namespace StudySharp.ApplicationServices.Commands +namespace StudySharp.ApplicationServices.Commands.Auth { - public sealed class RefreshTokenCommand : IRequest> + public sealed class RefreshTokenCommand : IRequest> { public string UserName { get; set; } public string AccessToken { get; set; } public string RefreshToken { get; set; } } - public sealed class RefsreshTokenCommandHandler : IRequestHandler> + public sealed class RefsreshTokenCommandHandler : IRequestHandler> { private readonly UserManager _userManager; - private readonly IJwtAuthManager _jwtAuthManager; + private readonly IJwtService _jwtService; - public RefsreshTokenCommandHandler(IJwtAuthManager jwtAuthManager, UserManager userManager) + public RefsreshTokenCommandHandler(IJwtService jwtAuthManager, UserManager userManager) { - _jwtAuthManager = jwtAuthManager; + _jwtService = jwtAuthManager; _userManager = userManager; } - public async Task> Handle(RefreshTokenCommand request, CancellationToken cancellationToken) + public async Task> Handle(RefreshTokenCommand request, CancellationToken cancellationToken) { try { var user = await _userManager.FindByNameAsync(request.UserName); if (user == null) { - return OperationResult.Fail(string.Format(ErrorConstants.EntityNotFound, "User", "Email", request.UserName)); + return OperationResult.Fail(string.Format(ErrorConstants.EntityNotFound, "User", "Email", request.UserName)); } if (string.IsNullOrEmpty(request.RefreshToken)) { - return OperationResult.Fail(ErrorConstants.InvalidCredentials); + return OperationResult.Fail(ErrorConstants.InvalidCredentials); } - var jwtResult = _jwtAuthManager.Refresh(request.RefreshToken, request.AccessToken, DateTime.Now); - return OperationResult.Ok(new LoginResult + var jwtResult = _jwtService.Refresh(request.RefreshToken, request.AccessToken, DateTime.Now); + return OperationResult.Ok(new RefreshTokenResult { UserName = user.UserName, Role = "Test", @@ -55,7 +56,7 @@ public async Task> Handle(RefreshTokenCommand reque } catch (SecurityTokenException exception) { - return OperationResult.Fail(exception.Message); // return 401 so that the client side can redirect the user to login page + return OperationResult.Fail(exception.Message); // return 401 so that the client side can redirect the user to login page } } } diff --git a/server/StudySharp.ApplicationServices/Commands/RegisterNewUserCommand.cs b/server/StudySharp.ApplicationServices/Commands/Auth/RegisterNewUserCommand.cs similarity index 96% rename from server/StudySharp.ApplicationServices/Commands/RegisterNewUserCommand.cs rename to server/StudySharp.ApplicationServices/Commands/Auth/RegisterNewUserCommand.cs index 99b04c0..e90904e 100644 --- a/server/StudySharp.ApplicationServices/Commands/RegisterNewUserCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/Auth/RegisterNewUserCommand.cs @@ -7,7 +7,7 @@ using StudySharp.Domain.General; using StudySharp.DomainServices; -namespace StudySharp.ApplicationServices.Commands +namespace StudySharp.ApplicationServices.Commands.Auth { public sealed class RegisterNewUserCommand : IRequest { diff --git a/server/StudySharp.ApplicationServices/JwtService/JwtRefreshTokenCache.cs b/server/StudySharp.ApplicationServices/JwtAuthService/JwtRefreshTokenCache.cs similarity index 68% rename from server/StudySharp.ApplicationServices/JwtService/JwtRefreshTokenCache.cs rename to server/StudySharp.ApplicationServices/JwtAuthService/JwtRefreshTokenCache.cs index 6f06c97..792edde 100644 --- a/server/StudySharp.ApplicationServices/JwtService/JwtRefreshTokenCache.cs +++ b/server/StudySharp.ApplicationServices/JwtAuthService/JwtRefreshTokenCache.cs @@ -3,28 +3,30 @@ using System.Threading.Tasks; using Microsoft.Extensions.Hosting; -namespace StudySharp.ApplicationServices.JwtService +namespace StudySharp.ApplicationServices.JwtAuthService { public class JwtRefreshTokenCache : IHostedService, IDisposable { - private readonly IJwtAuthManager _jwtAuthManager; + private readonly IJwtService _jwtService; private Timer _timer; - public JwtRefreshTokenCache(IJwtAuthManager jwtAuthManager) + public JwtRefreshTokenCache(IJwtService jwtAuthManager) { - _jwtAuthManager = jwtAuthManager; + _jwtService = jwtAuthManager; } public Task StartAsync(CancellationToken stoppingToken) { // remove expired refresh tokens from cache every minute +#pragma warning disable CS8622 _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(1)); +#pragma warning restore CS8622 return Task.CompletedTask; } private void DoWork(object state) { - _jwtAuthManager.RemoveExpiredRefreshTokens(DateTime.Now); + _jwtService.RemoveExpiredRefreshTokens(DateTime.Now); } public Task StopAsync(CancellationToken stoppingToken) @@ -36,6 +38,7 @@ public Task StopAsync(CancellationToken stoppingToken) public void Dispose() { _timer?.Dispose(); + GC.SuppressFinalize(this); } } } diff --git a/server/StudySharp.ApplicationServices/JwtService/JwtAuthManager.cs b/server/StudySharp.ApplicationServices/JwtAuthService/JwtService.cs similarity index 97% rename from server/StudySharp.ApplicationServices/JwtService/JwtAuthManager.cs rename to server/StudySharp.ApplicationServices/JwtAuthService/JwtService.cs index fa01c87..10a054e 100644 --- a/server/StudySharp.ApplicationServices/JwtService/JwtAuthManager.cs +++ b/server/StudySharp.ApplicationServices/JwtAuthService/JwtService.cs @@ -9,9 +9,9 @@ using System.Text.Json.Serialization; using Microsoft.IdentityModel.Tokens; -namespace StudySharp.ApplicationServices.JwtService +namespace StudySharp.ApplicationServices.JwtAuthService { - public interface IJwtAuthManager + public interface IJwtService { IImmutableDictionary UsersRefreshTokensReadOnlyDictionary { get; } JwtAuthResult GenerateTokens(string username, Claim[] claims, DateTime now); @@ -21,14 +21,14 @@ public interface IJwtAuthManager (ClaimsPrincipal principal, JwtSecurityToken?) DecodeJwtToken(string token); } - public class JwtAuthManager : IJwtAuthManager + public class JwtService : IJwtService { public IImmutableDictionary UsersRefreshTokensReadOnlyDictionary => _usersRefreshTokens.ToImmutableDictionary(); private readonly ConcurrentDictionary _usersRefreshTokens; // can store in a database or a distributed cache private readonly JwtTokenConfig _jwtTokenConfig; private readonly byte[] _secret; - public JwtAuthManager(JwtTokenConfig jwtTokenConfig) + public JwtService(JwtTokenConfig jwtTokenConfig) { _jwtTokenConfig = jwtTokenConfig; _usersRefreshTokens = new ConcurrentDictionary(); diff --git a/server/StudySharp.ApplicationServices/JwtService/JwtTokenConfig.cs b/server/StudySharp.ApplicationServices/JwtAuthService/JwtTokenConfig.cs similarity index 90% rename from server/StudySharp.ApplicationServices/JwtService/JwtTokenConfig.cs rename to server/StudySharp.ApplicationServices/JwtAuthService/JwtTokenConfig.cs index 6b66f59..e9ed420 100644 --- a/server/StudySharp.ApplicationServices/JwtService/JwtTokenConfig.cs +++ b/server/StudySharp.ApplicationServices/JwtAuthService/JwtTokenConfig.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace StudySharp.ApplicationServices.JwtService +namespace StudySharp.ApplicationServices.JwtAuthService { public sealed class JwtTokenConfig { diff --git a/server/StudySharp.ApplicationServices/JwtService/LoginResult.cs b/server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/LoginResult.cs similarity index 66% rename from server/StudySharp.ApplicationServices/JwtService/LoginResult.cs rename to server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/LoginResult.cs index 2f0c0f9..c2cb94d 100644 --- a/server/StudySharp.ApplicationServices/JwtService/LoginResult.cs +++ b/server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/LoginResult.cs @@ -1,6 +1,6 @@ -namespace StudySharp.ApplicationServices.JwtService +namespace StudySharp.ApplicationServices.JwtAuthService.ResultModels { - public sealed class LoginResult + public class LoginResult { public string UserName { get; set; } public string Role { get; set; } diff --git a/server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/RefreshTokenResult.cs b/server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/RefreshTokenResult.cs new file mode 100644 index 0000000..2acb52c --- /dev/null +++ b/server/StudySharp.ApplicationServices/JwtAuthService/ResultModels/RefreshTokenResult.cs @@ -0,0 +1,6 @@ +namespace StudySharp.ApplicationServices.JwtAuthService.ResultModels +{ + public sealed class RefreshTokenResult : LoginResult + { + } +} diff --git a/server/StudySharp.ApplicationServices/Startup.cs b/server/StudySharp.ApplicationServices/Startup.cs index 24eff59..b50c085 100644 --- a/server/StudySharp.ApplicationServices/Startup.cs +++ b/server/StudySharp.ApplicationServices/Startup.cs @@ -8,7 +8,7 @@ using Microsoft.IdentityModel.Tokens; using StudySharp.ApplicationServices.EmailService; using StudySharp.ApplicationServices.Infrastructure.EmailService; -using StudySharp.ApplicationServices.JwtService; +using StudySharp.ApplicationServices.JwtAuthService; namespace StudySharp.ApplicationServices { @@ -40,7 +40,7 @@ public static IServiceCollection AddApplicationServices( ClockSkew = TimeSpan.FromMinutes(1), }; }); - services.AddSingleton(); + services.AddSingleton(); services.AddHostedService(); services.AddMediatR(Assembly.GetExecutingAssembly()); From cfea0b9864956ee55e2e5490636fc3682e833c6d Mon Sep 17 00:00:00 2001 From: Nikolai Arkharov Date: Thu, 23 Sep 2021 21:41:10 +0300 Subject: [PATCH 4/8] add missing profile config --- server/StudySharp.API/MapperProfiles/AuthProfile.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/server/StudySharp.API/MapperProfiles/AuthProfile.cs b/server/StudySharp.API/MapperProfiles/AuthProfile.cs index 8a8114a..7a0be88 100644 --- a/server/StudySharp.API/MapperProfiles/AuthProfile.cs +++ b/server/StudySharp.API/MapperProfiles/AuthProfile.cs @@ -13,6 +13,7 @@ public AuthProfile() CreateMap(); CreateMap(); CreateMap(); + CreateMap(); } } } From 3c7c954d8bb9e8c9a50a81f29bb85eacc405dbb5 Mon Sep 17 00:00:00 2001 From: Nikolai Arkharov Date: Thu, 23 Sep 2021 22:52:11 +0300 Subject: [PATCH 5/8] add logout functionality --- .../Controllers/AccountController.cs | 8 +++-- .../Controllers/WeatherForecastController.cs | 2 ++ .../Commands/Auth/LogoutCommand.cs | 34 ++++++++++++++++++- .../StudySharp.ApplicationServices/Startup.cs | 4 +-- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/server/StudySharp.API/Controllers/AccountController.cs b/server/StudySharp.API/Controllers/AccountController.cs index fd96020..046f9b2 100644 --- a/server/StudySharp.API/Controllers/AccountController.cs +++ b/server/StudySharp.API/Controllers/AccountController.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using AutoMapper; using MediatR; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using StudySharp.API.Requests.Auth; @@ -53,15 +54,16 @@ public async Task> Login([FromBody] LoginRequest } [HttpPost("logout")] - public async Task Logout([FromBody] LogoutCommand logoutCommand) + public async Task Logout() { - return await _mediator.Send(logoutCommand); + var userName = User.Identity.Name; + return await _mediator.Send(new LogoutCommand { UserName = userName }); } [HttpPost("refresh-token")] public async Task> RefreshToken([FromBody] RefreshTokenRequest refreshTokenRequest) { - var accessToken = HttpContext.Request.Headers["Authorization"].ToString().Split(" ")[1]; + var accessToken = await HttpContext.GetTokenAsync("Bearer", "access_token"); var userName = User.Identity.Name; var refreshTokenCommand = new RefreshTokenCommand { diff --git a/server/StudySharp.API/Controllers/WeatherForecastController.cs b/server/StudySharp.API/Controllers/WeatherForecastController.cs index fffff95..69e322e 100644 --- a/server/StudySharp.API/Controllers/WeatherForecastController.cs +++ b/server/StudySharp.API/Controllers/WeatherForecastController.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using MediatR; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using StudySharp.ApplicationServices.Commands; @@ -11,6 +12,7 @@ namespace StudySharp.API.Controllers { + [Authorize] [ApiController] [Route("api/[controller]")] public class WeatherForecastController : ControllerBase diff --git a/server/StudySharp.ApplicationServices/Commands/Auth/LogoutCommand.cs b/server/StudySharp.ApplicationServices/Commands/Auth/LogoutCommand.cs index 88aad6a..c548367 100644 --- a/server/StudySharp.ApplicationServices/Commands/Auth/LogoutCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/Auth/LogoutCommand.cs @@ -1,9 +1,41 @@ -using MediatR; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.AspNetCore.Identity; +using StudySharp.ApplicationServices.JwtAuthService; +using StudySharp.Domain.Constants; using StudySharp.Domain.General; +using StudySharp.DomainServices; namespace StudySharp.ApplicationServices.Commands.Auth { public sealed class LogoutCommand : IRequest { + public string UserName { get; set; } + } + + public sealed class LogoutCommandHandler : IRequestHandler + { + private readonly UserManager _userManager; + private readonly IJwtService _jwtService; + + public LogoutCommandHandler(UserManager userManager, IJwtService jwtAuthManager) + { + _userManager = userManager; + _jwtService = jwtAuthManager; + } + + public async Task Handle(LogoutCommand request, CancellationToken cancellationToken) + { + var user = await _userManager.FindByNameAsync(request.UserName); + if (user == null) + { + return OperationResult.Fail(string.Format(ErrorConstants.EntityNotFound, "User", "Email", request.UserName)); + } + + _jwtService.RemoveRefreshTokenByUserName(user.UserName); + + return OperationResult.Ok(); + } } } diff --git a/server/StudySharp.ApplicationServices/Startup.cs b/server/StudySharp.ApplicationServices/Startup.cs index b50c085..da87c58 100644 --- a/server/StudySharp.ApplicationServices/Startup.cs +++ b/server/StudySharp.ApplicationServices/Startup.cs @@ -27,7 +27,7 @@ public static IServiceCollection AddApplicationServices( }).AddJwtBearer(x => { x.RequireHttpsMetadata = false; - x.SaveToken = false; + x.SaveToken = true; x.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, @@ -37,7 +37,7 @@ public static IServiceCollection AddApplicationServices( ValidAudience = jwtTokenConfig.Audience, ValidateAudience = true, ValidateLifetime = true, - ClockSkew = TimeSpan.FromMinutes(1), + ClockSkew = TimeSpan.Zero, }; }); services.AddSingleton(); From 7267dfbe93af1b27aa0c6405a351f85f7622b118 Mon Sep 17 00:00:00 2001 From: Nikolai Arkharov Date: Thu, 23 Sep 2021 23:05:45 +0300 Subject: [PATCH 6/8] add error messages --- .../Commands/Auth/RefsreshTokenCommand.cs | 2 +- .../Commands/Auth/RegisterNewUserCommand.cs | 8 ++++++-- server/StudySharp.Domain/Constants/ErrorConstants.cs | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs b/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs index 8b05d38..d8edd42 100644 --- a/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs @@ -42,7 +42,7 @@ public async Task> Handle(RefreshTokenComman if (string.IsNullOrEmpty(request.RefreshToken)) { - return OperationResult.Fail(ErrorConstants.InvalidCredentials); + return OperationResult.Fail(ErrorConstants.InvalidToken); } var jwtResult = _jwtService.Refresh(request.RefreshToken, request.AccessToken, DateTime.Now); diff --git a/server/StudySharp.ApplicationServices/Commands/Auth/RegisterNewUserCommand.cs b/server/StudySharp.ApplicationServices/Commands/Auth/RegisterNewUserCommand.cs index e90904e..b8f645e 100644 --- a/server/StudySharp.ApplicationServices/Commands/Auth/RegisterNewUserCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/Auth/RegisterNewUserCommand.cs @@ -27,18 +27,23 @@ public RegisterNewUserCommandHandler(UserManager userManager) public async Task Handle(RegisterNewUserCommand request, CancellationToken cancellationToken) { - // compare passwords? var user = await _userManager.FindByNameAsync(request.Email); if (user != null) { return OperationResult.Fail(string.Format(ErrorConstants.EntityAlreadyExists, "User", nameof(request.Email), request.Email)); } + if (!request.Password.Trim().Equals(request.ConfirmPassword.Trim())) + { + return OperationResult.Fail(ErrorConstants.PasswordsDoNotMatch); + } + var newUser = new ApplicationUser { UserName = request.Email, Email = request.Email, }; + var result = await _userManager.CreateAsync(newUser, request.Password); if (!result.Succeeded) @@ -46,7 +51,6 @@ public async Task Handle(RegisterNewUserCommand request, Cancel return OperationResult.Fail(result.Errors.Select(_ => _.Description).ToList()); } - // redirect to confirm email and then login? return OperationResult.Ok(); } } diff --git a/server/StudySharp.Domain/Constants/ErrorConstants.cs b/server/StudySharp.Domain/Constants/ErrorConstants.cs index 6af4da6..7b22dbb 100644 --- a/server/StudySharp.Domain/Constants/ErrorConstants.cs +++ b/server/StudySharp.Domain/Constants/ErrorConstants.cs @@ -5,5 +5,7 @@ public static class ErrorConstants public const string EntityNotFound = "{0} with {1} '{2}' not found!"; public const string EntityAlreadyExists = "{0} with {1} '{2}' already exists!"; public const string InvalidCredentials = "Invalid username or password!"; + public const string PasswordsDoNotMatch = "Passwords do not match!"; + public const string InvalidToken = "Invalid token!"; } } From d79b35ccc2bfea919e5361b4c658d312e6324726 Mon Sep 17 00:00:00 2001 From: Nikolai Arkharov Date: Thu, 23 Sep 2021 23:08:51 +0300 Subject: [PATCH 7/8] remove comment in RefreshTokenCommand --- .../Commands/Auth/RefsreshTokenCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs b/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs index d8edd42..0bc457f 100644 --- a/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs @@ -56,7 +56,7 @@ public async Task> Handle(RefreshTokenComman } catch (SecurityTokenException exception) { - return OperationResult.Fail(exception.Message); // return 401 so that the client side can redirect the user to login page + return OperationResult.Fail(exception.Message); } } } From f95a1ee54aa3e81bc1fa1e3d945ec71dabb5981e Mon Sep 17 00:00:00 2001 From: Dmytro Kudrenko Date: Thu, 23 Sep 2021 23:49:03 +0300 Subject: [PATCH 8/8] Fix redurant --- .../StudySharp.API/Controllers/AccountController.cs | 12 ++++++------ .../Controllers/WeatherForecastController.cs | 4 ++-- .../Commands/Auth/LoginCommand.cs | 10 ++++------ .../Commands/Auth/RefsreshTokenCommand.cs | 2 +- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/server/StudySharp.API/Controllers/AccountController.cs b/server/StudySharp.API/Controllers/AccountController.cs index 046f9b2..acf539d 100644 --- a/server/StudySharp.API/Controllers/AccountController.cs +++ b/server/StudySharp.API/Controllers/AccountController.cs @@ -32,7 +32,7 @@ public async Task Register([FromBody] RegisterNewUserRequest re { var registerNewUserCommand = _mapper.Map(registerNewUserRequest); - return await _mediator.Send(registerNewUserCommand); + return await _mediator.Send(registerNewUserCommand); } [AllowAnonymous] @@ -41,7 +41,7 @@ public async Task> Login([FromBody] LoginRequest { var loginCommand = _mapper.Map(loginRequest); - var operationResult = await _mediator.Send>(loginCommand); + var operationResult = await _mediator.Send(loginCommand); if (!operationResult.IsSucceeded) { @@ -50,14 +50,14 @@ public async Task> Login([FromBody] LoginRequest var response = _mapper.Map(operationResult.Result); - return OperationResult.Ok(response); + return OperationResult.Ok(response); } [HttpPost("logout")] public async Task Logout() { var userName = User.Identity.Name; - return await _mediator.Send(new LogoutCommand { UserName = userName }); + return await _mediator.Send(new LogoutCommand { UserName = userName }); } [HttpPost("refresh-token")] @@ -72,7 +72,7 @@ public async Task> RefreshToken([FromBody] UserName = userName, }; - var operationResult = await _mediator.Send>(refreshTokenCommand); + var operationResult = await _mediator.Send(refreshTokenCommand); if (!operationResult.IsSucceeded) { @@ -81,7 +81,7 @@ public async Task> RefreshToken([FromBody] var response = _mapper.Map(operationResult.Result); - return OperationResult.Ok(response); + return OperationResult.Ok(response); } } } diff --git a/server/StudySharp.API/Controllers/WeatherForecastController.cs b/server/StudySharp.API/Controllers/WeatherForecastController.cs index 69e322e..41183fb 100644 --- a/server/StudySharp.API/Controllers/WeatherForecastController.cs +++ b/server/StudySharp.API/Controllers/WeatherForecastController.cs @@ -36,13 +36,13 @@ public WeatherForecastController(ILogger logger, IMed [HttpGet] public async Task> Get([FromQuery] GetTagByIdQuery getTagByIdQuery) { - return await _mediator.Send>(getTagByIdQuery); + return await _mediator.Send(getTagByIdQuery); } [HttpPost] public async Task Add([FromBody] AddTagCommand addTagCommand) { - return await _mediator.Send(addTagCommand); + return await _mediator.Send(addTagCommand); } [HttpPut] diff --git a/server/StudySharp.ApplicationServices/Commands/Auth/LoginCommand.cs b/server/StudySharp.ApplicationServices/Commands/Auth/LoginCommand.cs index 91fdc48..07f4085 100644 --- a/server/StudySharp.ApplicationServices/Commands/Auth/LoginCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/Auth/LoginCommand.cs @@ -21,13 +21,11 @@ public sealed class LoginCommand : IRequest> public sealed class LoginCommandHandler : IRequestHandler> { private readonly UserManager _userManager; - private readonly SignInManager _signInManager; private readonly IJwtService _jwtService; - public LoginCommandHandler(UserManager userManager, SignInManager signInManager, IJwtService jwtAuthManager) + public LoginCommandHandler(UserManager userManager, IJwtService jwtAuthManager) { _userManager = userManager; - _signInManager = signInManager; _jwtService = jwtAuthManager; } @@ -39,14 +37,14 @@ public async Task> Handle(LoginCommand request, Can return OperationResult.Fail(ErrorConstants.InvalidCredentials); } - var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false); + var isSucceeded = await _userManager.CheckPasswordAsync(user, request.Password); - if (!result.Succeeded) + if (!isSucceeded) { return OperationResult.Fail(ErrorConstants.InvalidCredentials); } - var jwtResult = _jwtService.GenerateTokens(user.UserName, new Claim[] { new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName) }, DateTime.Now); + var jwtResult = _jwtService.GenerateTokens(user.UserName, new[] { new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName) }, DateTime.Now); return OperationResult.Ok(new LoginResult { diff --git a/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs b/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs index 0bc457f..98203ea 100644 --- a/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs +++ b/server/StudySharp.ApplicationServices/Commands/Auth/RefsreshTokenCommand.cs @@ -46,7 +46,7 @@ public async Task> Handle(RefreshTokenComman } var jwtResult = _jwtService.Refresh(request.RefreshToken, request.AccessToken, DateTime.Now); - return OperationResult.Ok(new RefreshTokenResult + return OperationResult.Ok(new RefreshTokenResult { UserName = user.UserName, Role = "Test",