diff --git a/Sources/Octockup.Server/Handlers/CreateTokenRequestHandler.cs b/Sources/Octockup.Server/Handlers/CreateTokenRequestHandler.cs new file mode 100644 index 0000000..86d720e --- /dev/null +++ b/Sources/Octockup.Server/Handlers/CreateTokenRequestHandler.cs @@ -0,0 +1,49 @@ +using MediatR; +using AutoMapper; +using System.Security.Claims; +using Octockup.Server.Models; +using Octockup.Server.Database; +using Octockup.Server.Models.Dto; +using EasyExtensions.AspNetCore.Authorization.Builders; +using EasyExtensions.AspNetCore.Authorization.Services; + +namespace Octockup.Server.Handlers +{ + + public class CreateTokenRequestHandler(ITokenProvider _tokenProvider, AppDbContext _dbContext, + IConfiguration _configuration, IMapper _mapper) : IRequestHandler + { + public async Task Handle(CreateTokenRequest request, CancellationToken cancellationToken) + { + string accessToken = _tokenProvider.CreateToken(x => GetUserClaims(x, request.User)); + int refreshLifetime = _configuration.GetValue("JwtSettings:RefreshLifetimeHours"); + if (refreshLifetime <= 0) + { + throw new InvalidOperationException("JwtSettings:RefreshLifetimeHours must be greater than 0"); + } + TimeSpan refreshLifetimeSpan = TimeSpan.FromHours(refreshLifetime); + Session session = new() + { + User = request.User, + UserId = request.User.Id, + RefreshToken = _tokenProvider.CreateToken(refreshLifetimeSpan, x => GetUserClaims(x, request.User)), + }; + _dbContext.Sessions.Add(session); + await _dbContext.SaveChangesAsync(cancellationToken); + UserDto userDto = _mapper.Map(request.User); + return new AuthResponse(accessToken, session.RefreshToken, userDto); + } + + private static ClaimBuilder GetUserClaims(ClaimBuilder builder, User user) + { + ArgumentNullException.ThrowIfNull(user, nameof(user)); + ArgumentException.ThrowIfNullOrWhiteSpace(user.Username, nameof(user.Username)); + ArgumentException.ThrowIfNullOrWhiteSpace(user.Email, nameof(user.Email)); + + return builder.Add(ClaimTypes.Name, user.Username) + .Add(ClaimTypes.Role, user.Role.ToString()) + .Add(ClaimTypes.Email, user.Email) + .Add(ClaimTypes.Sid, user.Id.ToString()); + } + } +} diff --git a/Sources/Octockup.Server/Handlers/LoginRequestHandler.cs b/Sources/Octockup.Server/Handlers/LoginRequestHandler.cs index ed173d6..3b88a80 100644 --- a/Sources/Octockup.Server/Handlers/LoginRequestHandler.cs +++ b/Sources/Octockup.Server/Handlers/LoginRequestHandler.cs @@ -1,53 +1,26 @@ using MediatR; -using AutoMapper; +using System.Net; using Octockup.Server.Models; -using System.Security.Claims; using Octockup.Server.Database; -using Octockup.Server.Models.Dto; using EasyExtensions.EntityFrameworkCore.Exceptions; -using EasyExtensions.AspNetCore.Authorization.Services; -using EasyExtensions.AspNetCore.Authorization.Builders; namespace Octockup.Server.Handlers { - public class LoginRequestHandler(ILogger _logger, ITokenProvider _tokenProvider, - AppDbContext _dbContext, IConfiguration _configuration, IMapper _mapper) : IRequestHandler + public class LoginRequestHandler(ILogger _logger, AppDbContext _dbContext, + IMediator _mediator) : IRequestHandler { public async Task Handle(LoginRequest request, CancellationToken cancellationToken) { var foundUser = _dbContext.Users.FirstOrDefault(x => x.Username.Equals(request.Username, StringComparison.CurrentCultureIgnoreCase)) - ?? throw new WebApiException(System.Net.HttpStatusCode.NotFound, nameof(User), "User not found"); + ?? throw new WebApiException(HttpStatusCode.NotFound, nameof(User), "User not found"); if (!foundUser.PasswordHash.Equals(request.PasswordHash)) { _logger.LogWarning("Login attempt for '{Username}' failed", request.Username); - throw new WebApiException(System.Net.HttpStatusCode.Unauthorized, nameof(User), "Invalid password"); + throw new WebApiException(HttpStatusCode.Unauthorized, nameof(User), "Invalid password"); } _logger.LogInformation("User '{Username}' logged in", request.Username); - string accessToken = _tokenProvider.CreateToken(x => GetUserClaims(x, foundUser)); - int refreshLifetime = _configuration.GetValue("JwtSettings:RefreshLifetimeHours"); - if (refreshLifetime <= 0) - { - throw new InvalidOperationException("JwtSettings:RefreshLifetimeHours must be greater than 0"); - } - TimeSpan refreshLifetimeSpan = TimeSpan.FromHours(refreshLifetime); - Session session = new() - { - User = foundUser, - UserId = foundUser.Id, - RefreshToken = _tokenProvider.CreateToken(refreshLifetimeSpan, x => GetUserClaims(x, foundUser)), - }; - _dbContext.Sessions.Add(session); - await _dbContext.SaveChangesAsync(cancellationToken); - UserDto userDto = _mapper.Map(foundUser); - return new(accessToken, session.RefreshToken, userDto); - } - - private static ClaimBuilder GetUserClaims(ClaimBuilder builder, User user) - { - return builder.Add(ClaimTypes.Name, user.Username) - .Add(ClaimTypes.Role, user.Role.ToString()) - .Add(ClaimTypes.Email, user.Email) - .Add(ClaimTypes.Sid, user.Id.ToString()); + CreateTokenRequest createTokenRequest = new(foundUser); + return await _mediator.Send(createTokenRequest, cancellationToken); } } } diff --git a/Sources/Octockup.Server/Handlers/RefreshRequestHandler.cs b/Sources/Octockup.Server/Handlers/RefreshRequestHandler.cs index 0ba5bf0..9f81881 100644 --- a/Sources/Octockup.Server/Handlers/RefreshRequestHandler.cs +++ b/Sources/Octockup.Server/Handlers/RefreshRequestHandler.cs @@ -1,37 +1,28 @@ using MediatR; +using AutoMapper; using Octockup.Server.Models; using Octockup.Server.Database; +using EasyExtensions.EntityFrameworkCore.Exceptions; +using EasyExtensions.AspNetCore.Authorization.Services; namespace Octockup.Server.Handlers { - public class RefreshRequestHandler : IRequestHandler + public class RefreshRequestHandler(ITokenProvider _tokenProvider, IMediator _mediator, + AppDbContext _dbContext) : IRequestHandler { - public Task Handle(RefreshRequest request, CancellationToken cancellationToken) + public async Task Handle(RefreshRequest request, CancellationToken cancellationToken) { bool isValid = _tokenProvider.ValidateToken(request.RefreshToken); if (!isValid) { - - return Unauthorized(); - } - var foundToken = _dbContext.Sessions.FirstOrDefault(x => x.RefreshToken == request.RefreshToken); - if (foundToken is null) - { - return NotFound(); + throw new WebApiException(System.Net.HttpStatusCode.Unauthorized, nameof(Session), "Invalid refresh token"); } + var foundToken = _dbContext.Sessions.FirstOrDefault(x => x.RefreshToken == request.RefreshToken) + ?? throw new WebApiException(System.Net.HttpStatusCode.NotFound, nameof(Session), "Session not found"); _dbContext.Sessions.Remove(foundToken); - await _dbContext.SaveChangesAsync(); - // JwtSettingsRefreshLifetimeHours - int hours = 720; - string token = _tokenProvider.CreateToken(TimeSpan.FromHours(hours)); - Session session = new() - { - UserId = foundToken.UserId, - RefreshToken = _tokenProvider.CreateToken(x => x.Add(ClaimTypes.Name, "refresh")) - }; - _dbContext.Sessions.Add(session); - await _dbContext.SaveChangesAsync(); - return Ok(new AuthResponse(token, session.RefreshToken)); + await _dbContext.SaveChangesAsync(cancellationToken); + CreateTokenRequest createTokenRequest = new(foundToken.User); + return await _mediator.Send(createTokenRequest, cancellationToken); } } } diff --git a/Sources/Octockup.Server/Models/CreateTokenRequest.cs b/Sources/Octockup.Server/Models/CreateTokenRequest.cs new file mode 100644 index 0000000..074df7a --- /dev/null +++ b/Sources/Octockup.Server/Models/CreateTokenRequest.cs @@ -0,0 +1,7 @@ +using MediatR; +using Octockup.Server.Database; + +namespace Octockup.Server.Models +{ + public record CreateTokenRequest(User User) : IRequest; +}