mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-08-11 09:13:42 -04:00
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:
parent
3eeb131985
commit
5a95911483
10
API/Constants/CacheProfiles.cs
Normal file
10
API/Constants/CacheProfiles.cs
Normal 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";
|
||||||
|
}
|
@ -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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
57
API/Middleware/JWTRevocationMiddleware.cs
Normal file
57
API/Middleware/JWTRevocationMiddleware.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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": [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user