From 5a95911483f810d72ee6830149d62edc1ab96a8a Mon Sep 17 00:00:00 2001 From: Joe Milazzo Date: Sun, 28 May 2023 09:07:05 -0500 Subject: [PATCH] Last Release before Release Testing (#2017) * Attempting to invalidate JWT on login (when locked out), but can't figure a way to get a JWT, since we don't store them. Just committing as I'm going to remove the middleware, this is not worth the performance and complexity. * Removed some security stuff that didn't line up. * Dropping Token Expiration down to 2 days to test during release testing. --- API/Constants/CacheProfiles.cs | 10 ++++ API/Controllers/AccountController.cs | 9 ++- .../ApplicationServiceExtensions.cs | 3 +- API/Middleware/JWTRevocationMiddleware.cs | 57 +++++++++++++++++++ API/Services/ImageService.cs | 3 +- API/Services/TokenService.cs | 10 +++- openapi.json | 2 +- 7 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 API/Constants/CacheProfiles.cs create mode 100644 API/Middleware/JWTRevocationMiddleware.cs diff --git a/API/Constants/CacheProfiles.cs b/API/Constants/CacheProfiles.cs new file mode 100644 index 000000000..80e6f39d3 --- /dev/null +++ b/API/Constants/CacheProfiles.cs @@ -0,0 +1,10 @@ +namespace API.Constants; + +public static class EasyCacheProfiles +{ + /// + /// Not in use + /// + public const string RevokedJwt = "revokedJWT"; + public const string Favicon = "favicon"; +} diff --git a/API/Controllers/AccountController.cs b/API/Controllers/AccountController.cs index ec2b1156d..73f0a8bd6 100644 --- a/API/Controllers/AccountController.cs +++ b/API/Controllers/AccountController.cs @@ -18,6 +18,7 @@ using API.Middleware.RateLimit; using API.Services; using API.SignalR; using AutoMapper; +using EasyCaching.Core; using Hangfire; using Kavita.Common; using Kavita.Common.EnvironmentInfo; @@ -44,6 +45,7 @@ public class AccountController : BaseApiController private readonly IAccountService _accountService; private readonly IEmailService _emailService; private readonly IEventHub _eventHub; + private readonly IEasyCachingProviderFactory _cacheFactory; /// public AccountController(UserManager userManager, @@ -51,7 +53,8 @@ public class AccountController : BaseApiController ITokenService tokenService, IUnitOfWork unitOfWork, ILogger logger, IMapper mapper, IAccountService accountService, - IEmailService emailService, IEventHub eventHub) + IEmailService emailService, IEventHub eventHub, + IEasyCachingProviderFactory cacheFactory) { _userManager = userManager; _signInManager = signInManager; @@ -62,6 +65,7 @@ public class AccountController : BaseApiController _accountService = accountService; _emailService = emailService; _eventHub = eventHub; + _cacheFactory = cacheFactory; } /// @@ -187,8 +191,9 @@ public class AccountController : BaseApiController var result = await _signInManager .CheckPasswordSignInAsync(user, loginDto.Password, true); - if (result.IsLockedOut) + if (result.IsLockedOut) // result.IsLockedOut { + await _userManager.UpdateSecurityStampAsync(user); return Unauthorized("You've been locked out from too many authorization attempts. Please wait 10 minutes."); } diff --git a/API/Extensions/ApplicationServiceExtensions.cs b/API/Extensions/ApplicationServiceExtensions.cs index ef3a65710..6c8e67026 100644 --- a/API/Extensions/ApplicationServiceExtensions.cs +++ b/API/Extensions/ApplicationServiceExtensions.cs @@ -1,4 +1,5 @@ using System.IO.Abstractions; +using API.Constants; using API.Data; using API.Helpers; using API.Services; @@ -68,7 +69,7 @@ public static class ApplicationServiceExtensions services.AddEasyCaching(options => { - options.UseInMemory("favicon"); + options.UseInMemory(EasyCacheProfiles.Favicon); }); } diff --git a/API/Middleware/JWTRevocationMiddleware.cs b/API/Middleware/JWTRevocationMiddleware.cs new file mode 100644 index 000000000..2bcba3425 --- /dev/null +++ b/API/Middleware/JWTRevocationMiddleware.cs @@ -0,0 +1,57 @@ +using System.Threading.Tasks; +using API.Constants; +using EasyCaching.Core; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace API.Middleware; + +/// +/// Responsible for maintaining an in-memory. Not in use +/// +public class JwtRevocationMiddleware +{ + private readonly RequestDelegate _next; + private readonly IEasyCachingProviderFactory _cacheFactory; + private readonly ILogger _logger; + + public JwtRevocationMiddleware(RequestDelegate next, IEasyCachingProviderFactory cacheFactory, ILogger logger) + { + _next = next; + _cacheFactory = cacheFactory; + _logger = logger; + } + + public async Task InvokeAsync(HttpContext context) + { + if (context.User.Identity is {IsAuthenticated: false}) + { + await _next(context); + return; + } + + // Get the JWT from the request headers or wherever you store it + var token = context.Request.Headers["Authorization"].ToString()?.Replace("Bearer ", string.Empty); + + // Check if the token is revoked + if (await IsTokenRevoked(token)) + { + _logger.LogWarning("Revoked token detected: {Token}", token); + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + return; + } + + await _next(context); + } + + private async Task IsTokenRevoked(string token) + { + // Check if the token exists in the revocation list stored in the cache + var isRevoked = await _cacheFactory.GetCachingProvider(EasyCacheProfiles.RevokedJwt) + .GetAsync(token); + + + return isRevoked.HasValue; + } +} diff --git a/API/Services/ImageService.cs b/API/Services/ImageService.cs index 656b427a6..85ec4a041 100644 --- a/API/Services/ImageService.cs +++ b/API/Services/ImageService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using API.Constants; using API.Entities.Enums; using API.Extensions; using EasyCaching.Core; @@ -212,7 +213,7 @@ public class ImageService : IImageService var baseUrl = uri.Scheme + "://" + uri.Host; - var provider = _cacheFactory.GetCachingProvider("favicon"); + var provider = _cacheFactory.GetCachingProvider(EasyCacheProfiles.Favicon); var res = await provider.GetAsync(baseUrl); if (res.HasValue) { diff --git a/API/Services/TokenService.cs b/API/Services/TokenService.cs index 443cea5b9..ef04757f1 100644 --- a/API/Services/TokenService.cs +++ b/API/Services/TokenService.cs @@ -23,6 +23,7 @@ public interface ITokenService Task CreateToken(AppUser user); Task ValidateRefreshToken(TokenRequestDto request); Task CreateRefreshToken(AppUser user); + Task GetJwtFromUser(AppUser user); } @@ -59,7 +60,7 @@ public class TokenService : ITokenService var tokenDescriptor = new SecurityTokenDescriptor() { Subject = new ClaimsIdentity(claims), - Expires = DateTime.UtcNow.AddDays(14), + Expires = DateTime.UtcNow.AddDays(2), SigningCredentials = credentials }; @@ -124,4 +125,11 @@ public class TokenService : ITokenService return null; } } + + public async Task GetJwtFromUser(AppUser user) + { + var userClaims = await _userManager.GetClaimsAsync(user); + var jwtClaim = userClaims.FirstOrDefault(claim => claim.Type == "jwt"); + return jwtClaim?.Value; + } } diff --git a/openapi.json b/openapi.json index ae7ef1ae4..30a6a846f 100644 --- a/openapi.json +++ b/openapi.json @@ -7,7 +7,7 @@ "name": "GPL-3.0", "url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE" }, - "version": "0.7.2.21" + "version": "0.7.2.22" }, "servers": [ {