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.
This commit is contained in:
Joe Milazzo 2023-05-28 09:07:05 -05:00 committed by GitHub
parent 3eeb131985
commit 5a95911483
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 88 additions and 6 deletions

View File

@ -0,0 +1,10 @@
namespace API.Constants;
public static class EasyCacheProfiles
{
/// <summary>
/// Not in use
/// </summary>
public const string RevokedJwt = "revokedJWT";
public const string Favicon = "favicon";
}

View File

@ -18,6 +18,7 @@ using API.Middleware.RateLimit;
using API.Services; using API.Services;
using API.SignalR; using API.SignalR;
using AutoMapper; using AutoMapper;
using EasyCaching.Core;
using Hangfire; using Hangfire;
using Kavita.Common; using Kavita.Common;
using Kavita.Common.EnvironmentInfo; using Kavita.Common.EnvironmentInfo;
@ -44,6 +45,7 @@ public class AccountController : BaseApiController
private readonly IAccountService _accountService; private readonly IAccountService _accountService;
private readonly IEmailService _emailService; private readonly IEmailService _emailService;
private readonly IEventHub _eventHub; private readonly IEventHub _eventHub;
private readonly IEasyCachingProviderFactory _cacheFactory;
/// <inheritdoc /> /// <inheritdoc />
public AccountController(UserManager<AppUser> userManager, public AccountController(UserManager<AppUser> userManager,
@ -51,7 +53,8 @@ public class AccountController : BaseApiController
ITokenService tokenService, IUnitOfWork unitOfWork, ITokenService tokenService, IUnitOfWork unitOfWork,
ILogger<AccountController> logger, ILogger<AccountController> logger,
IMapper mapper, IAccountService accountService, IMapper mapper, IAccountService accountService,
IEmailService emailService, IEventHub eventHub) IEmailService emailService, IEventHub eventHub,
IEasyCachingProviderFactory cacheFactory)
{ {
_userManager = userManager; _userManager = userManager;
_signInManager = signInManager; _signInManager = signInManager;
@ -62,6 +65,7 @@ public class AccountController : BaseApiController
_accountService = accountService; _accountService = accountService;
_emailService = emailService; _emailService = emailService;
_eventHub = eventHub; _eventHub = eventHub;
_cacheFactory = cacheFactory;
} }
/// <summary> /// <summary>
@ -187,8 +191,9 @@ public class AccountController : BaseApiController
var result = await _signInManager var result = await _signInManager
.CheckPasswordSignInAsync(user, loginDto.Password, true); .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."); return Unauthorized("You've been locked out from too many authorization attempts. Please wait 10 minutes.");
} }

View File

@ -1,4 +1,5 @@
using System.IO.Abstractions; using System.IO.Abstractions;
using API.Constants;
using API.Data; using API.Data;
using API.Helpers; using API.Helpers;
using API.Services; using API.Services;
@ -68,7 +69,7 @@ public static class ApplicationServiceExtensions
services.AddEasyCaching(options => services.AddEasyCaching(options =>
{ {
options.UseInMemory("favicon"); options.UseInMemory(EasyCacheProfiles.Favicon);
}); });
} }

View File

@ -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;
/// <summary>
/// Responsible for maintaining an in-memory. Not in use
/// </summary>
public class JwtRevocationMiddleware
{
private readonly RequestDelegate _next;
private readonly IEasyCachingProviderFactory _cacheFactory;
private readonly ILogger<JwtRevocationMiddleware> _logger;
public JwtRevocationMiddleware(RequestDelegate next, IEasyCachingProviderFactory cacheFactory, ILogger<JwtRevocationMiddleware> 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<bool> IsTokenRevoked(string token)
{
// Check if the token exists in the revocation list stored in the cache
var isRevoked = await _cacheFactory.GetCachingProvider(EasyCacheProfiles.RevokedJwt)
.GetAsync<string>(token);
return isRevoked.HasValue;
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.Constants;
using API.Entities.Enums; using API.Entities.Enums;
using API.Extensions; using API.Extensions;
using EasyCaching.Core; using EasyCaching.Core;
@ -212,7 +213,7 @@ public class ImageService : IImageService
var baseUrl = uri.Scheme + "://" + uri.Host; var baseUrl = uri.Scheme + "://" + uri.Host;
var provider = _cacheFactory.GetCachingProvider("favicon"); var provider = _cacheFactory.GetCachingProvider(EasyCacheProfiles.Favicon);
var res = await provider.GetAsync<string>(baseUrl); var res = await provider.GetAsync<string>(baseUrl);
if (res.HasValue) if (res.HasValue)
{ {

View File

@ -23,6 +23,7 @@ public interface ITokenService
Task<string> CreateToken(AppUser user); Task<string> CreateToken(AppUser user);
Task<TokenRequestDto?> ValidateRefreshToken(TokenRequestDto request); Task<TokenRequestDto?> ValidateRefreshToken(TokenRequestDto request);
Task<string> CreateRefreshToken(AppUser user); Task<string> CreateRefreshToken(AppUser user);
Task<string> GetJwtFromUser(AppUser user);
} }
@ -59,7 +60,7 @@ public class TokenService : ITokenService
var tokenDescriptor = new SecurityTokenDescriptor() var tokenDescriptor = new SecurityTokenDescriptor()
{ {
Subject = new ClaimsIdentity(claims), Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddDays(14), Expires = DateTime.UtcNow.AddDays(2),
SigningCredentials = credentials SigningCredentials = credentials
}; };
@ -124,4 +125,11 @@ public class TokenService : ITokenService
return null; return null;
} }
} }
public async Task<string> GetJwtFromUser(AppUser user)
{
var userClaims = await _userManager.GetClaimsAsync(user);
var jwtClaim = userClaims.FirstOrDefault(claim => claim.Type == "jwt");
return jwtClaim?.Value;
}
} }

View File

@ -7,7 +7,7 @@
"name": "GPL-3.0", "name": "GPL-3.0",
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE" "url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
}, },
"version": "0.7.2.21" "version": "0.7.2.22"
}, },
"servers": [ "servers": [
{ {