Added session caching for user validation to reduce db calls and introduced configurable session options.
This commit is contained in:
6
Moonlight.Api/Configuration/SessionOptions.cs
Normal file
6
Moonlight.Api/Configuration/SessionOptions.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Moonlight.Api.Configuration;
|
||||
|
||||
public class SessionOptions
|
||||
{
|
||||
public int ValidationCacheMinutes { get; set; } = 3;
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moonlight.Api.Configuration;
|
||||
using Moonlight.Api.Database;
|
||||
using Moonlight.Api.Database.Entities;
|
||||
|
||||
@@ -9,15 +12,22 @@ namespace Moonlight.Api.Services;
|
||||
public class UserAuthService
|
||||
{
|
||||
private readonly DatabaseRepository<User> UserRepository;
|
||||
private readonly IMemoryCache Cache;
|
||||
private readonly ILogger<UserAuthService> Logger;
|
||||
private readonly IOptions<SessionOptions> Options;
|
||||
|
||||
private const string UserIdClaim = "UserId";
|
||||
private const string IssuedAtClaim = "IssuedAt";
|
||||
|
||||
public UserAuthService(DatabaseRepository<User> userRepository, ILogger<UserAuthService> logger)
|
||||
public UserAuthService(
|
||||
DatabaseRepository<User> userRepository,
|
||||
ILogger<UserAuthService> logger,
|
||||
IMemoryCache cache, IOptions<SessionOptions> options)
|
||||
{
|
||||
UserRepository = userRepository;
|
||||
Logger = logger;
|
||||
Cache = cache;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public async Task<bool> SyncAsync(ClaimsPrincipal? principal)
|
||||
@@ -75,14 +85,32 @@ public class UserAuthService
|
||||
if (!int.TryParse(userIdString, out var userId))
|
||||
return false;
|
||||
|
||||
var user = await UserRepository
|
||||
var cacheKey = $"Moonlight.{nameof(UserAuthService)}.{nameof(ValidateAsync)}-{userId}";
|
||||
|
||||
if (!Cache.TryGetValue<UserSession>(cacheKey, out var user))
|
||||
{
|
||||
user = await UserRepository
|
||||
.Query()
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(user => user.Id == userId);
|
||||
.Where(u => u.Id == userId)
|
||||
.Select(u => new UserSession(u.InvalidateTimestamp))
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (user == null)
|
||||
return false;
|
||||
|
||||
Cache.Set(
|
||||
cacheKey,
|
||||
user,
|
||||
TimeSpan.FromMinutes(Options.Value.ValidationCacheMinutes)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (user == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
var issuedAtString = principal.FindFirstValue(IssuedAtClaim);
|
||||
|
||||
if (!long.TryParse(issuedAtString, out var issuedAtUnix))
|
||||
@@ -90,10 +118,14 @@ public class UserAuthService
|
||||
|
||||
var issuedAt = DateTimeOffset.FromUnixTimeSeconds(issuedAtUnix).ToUniversalTime();
|
||||
|
||||
// If the issued at timestamp is greater than the token validation timestamp
|
||||
// everything is fine. If not it means that the token should be invalidated
|
||||
// If the issued at timestamp is greater than the token validation timestamp,
|
||||
// everything is fine. If not, it means that the token should be invalidated
|
||||
// as it is too old
|
||||
|
||||
return issuedAt > user.InvalidateTimestamp;
|
||||
}
|
||||
|
||||
// A small model which contains data queried per session validation after the defined cache time.
|
||||
// Used for projection
|
||||
private record UserSession(DateTimeOffset InvalidateTimestamp);
|
||||
}
|
||||
@@ -29,6 +29,9 @@ public partial class Startup
|
||||
builder.Services.AddSingleton<DiagnoseService>();
|
||||
|
||||
builder.Services.AddSingleton<IDiagnoseProvider, UpdateDiagnoseProvider>();
|
||||
|
||||
builder.Services.AddMemoryCache();
|
||||
builder.Services.AddOptions<SessionOptions>().BindConfiguration("Moonlight:Session");
|
||||
}
|
||||
|
||||
private static void UseBase(WebApplication application)
|
||||
|
||||
Reference in New Issue
Block a user