From bee381702b3b45d6808b4d30431e981a3cd12d33 Mon Sep 17 00:00:00 2001 From: ChiaraBm Date: Fri, 16 Jan 2026 09:19:15 +0100 Subject: [PATCH] Added session caching for user validation to reduce db calls and introduced configurable session options. --- Moonlight.Api/Configuration/SessionOptions.cs | 6 +++ Moonlight.Api/Services/UserAuthService.cs | 50 +++++++++++++++---- Moonlight.Api/Startup/Startup.Base.cs | 3 ++ 3 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 Moonlight.Api/Configuration/SessionOptions.cs diff --git a/Moonlight.Api/Configuration/SessionOptions.cs b/Moonlight.Api/Configuration/SessionOptions.cs new file mode 100644 index 00000000..9767009b --- /dev/null +++ b/Moonlight.Api/Configuration/SessionOptions.cs @@ -0,0 +1,6 @@ +namespace Moonlight.Api.Configuration; + +public class SessionOptions +{ + public int ValidationCacheMinutes { get; set; } = 3; +} \ No newline at end of file diff --git a/Moonlight.Api/Services/UserAuthService.cs b/Moonlight.Api/Services/UserAuthService.cs index 2c6bb267..06567234 100644 --- a/Moonlight.Api/Services/UserAuthService.cs +++ b/Moonlight.Api/Services/UserAuthService.cs @@ -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 UserRepository; + private readonly IMemoryCache Cache; private readonly ILogger Logger; + private readonly IOptions Options; private const string UserIdClaim = "UserId"; private const string IssuedAtClaim = "IssuedAt"; - public UserAuthService(DatabaseRepository userRepository, ILogger logger) + public UserAuthService( + DatabaseRepository userRepository, + ILogger logger, + IMemoryCache cache, IOptions options) { UserRepository = userRepository; Logger = logger; + Cache = cache; + Options = options; } public async Task SyncAsync(ClaimsPrincipal? principal) @@ -75,13 +85,31 @@ public class UserAuthService if (!int.TryParse(userIdString, out var userId)) return false; - var user = await UserRepository - .Query() - .AsNoTracking() - .FirstOrDefaultAsync(user => user.Id == userId); + var cacheKey = $"Moonlight.{nameof(UserAuthService)}.{nameof(ValidateAsync)}-{userId}"; - if (user == null) - return false; + if (!Cache.TryGetValue(cacheKey, out var user)) + { + user = await UserRepository + .Query() + .AsNoTracking() + .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); @@ -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); } \ No newline at end of file diff --git a/Moonlight.Api/Startup/Startup.Base.cs b/Moonlight.Api/Startup/Startup.Base.cs index f1456520..b97b9dad 100644 --- a/Moonlight.Api/Startup/Startup.Base.cs +++ b/Moonlight.Api/Startup/Startup.Base.cs @@ -29,6 +29,9 @@ public partial class Startup builder.Services.AddSingleton(); builder.Services.AddSingleton(); + + builder.Services.AddMemoryCache(); + builder.Services.AddOptions().BindConfiguration("Moonlight:Session"); } private static void UseBase(WebApplication application)