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 System.Security.Claims;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Moonlight.Api.Configuration;
|
||||||
using Moonlight.Api.Database;
|
using Moonlight.Api.Database;
|
||||||
using Moonlight.Api.Database.Entities;
|
using Moonlight.Api.Database.Entities;
|
||||||
|
|
||||||
@@ -9,15 +12,22 @@ namespace Moonlight.Api.Services;
|
|||||||
public class UserAuthService
|
public class UserAuthService
|
||||||
{
|
{
|
||||||
private readonly DatabaseRepository<User> UserRepository;
|
private readonly DatabaseRepository<User> UserRepository;
|
||||||
|
private readonly IMemoryCache Cache;
|
||||||
private readonly ILogger<UserAuthService> Logger;
|
private readonly ILogger<UserAuthService> Logger;
|
||||||
|
private readonly IOptions<SessionOptions> Options;
|
||||||
|
|
||||||
private const string UserIdClaim = "UserId";
|
private const string UserIdClaim = "UserId";
|
||||||
private const string IssuedAtClaim = "IssuedAt";
|
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;
|
UserRepository = userRepository;
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
|
Cache = cache;
|
||||||
|
Options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> SyncAsync(ClaimsPrincipal? principal)
|
public async Task<bool> SyncAsync(ClaimsPrincipal? principal)
|
||||||
@@ -75,14 +85,32 @@ public class UserAuthService
|
|||||||
if (!int.TryParse(userIdString, out var userId))
|
if (!int.TryParse(userIdString, out var userId))
|
||||||
return false;
|
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()
|
.Query()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(user => user.Id == userId);
|
.Where(u => u.Id == userId)
|
||||||
|
.Select(u => new UserSession(u.InvalidateTimestamp))
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
Cache.Set(
|
||||||
|
cacheKey,
|
||||||
|
user,
|
||||||
|
TimeSpan.FromMinutes(Options.Value.ValidationCacheMinutes)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var issuedAtString = principal.FindFirstValue(IssuedAtClaim);
|
var issuedAtString = principal.FindFirstValue(IssuedAtClaim);
|
||||||
|
|
||||||
if (!long.TryParse(issuedAtString, out var issuedAtUnix))
|
if (!long.TryParse(issuedAtString, out var issuedAtUnix))
|
||||||
@@ -90,10 +118,14 @@ public class UserAuthService
|
|||||||
|
|
||||||
var issuedAt = DateTimeOffset.FromUnixTimeSeconds(issuedAtUnix).ToUniversalTime();
|
var issuedAt = DateTimeOffset.FromUnixTimeSeconds(issuedAtUnix).ToUniversalTime();
|
||||||
|
|
||||||
// If the issued at timestamp is greater than the token validation timestamp
|
// 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
|
// everything is fine. If not, it means that the token should be invalidated
|
||||||
// as it is too old
|
// as it is too old
|
||||||
|
|
||||||
return issuedAt > user.InvalidateTimestamp;
|
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<DiagnoseService>();
|
||||||
|
|
||||||
builder.Services.AddSingleton<IDiagnoseProvider, UpdateDiagnoseProvider>();
|
builder.Services.AddSingleton<IDiagnoseProvider, UpdateDiagnoseProvider>();
|
||||||
|
|
||||||
|
builder.Services.AddMemoryCache();
|
||||||
|
builder.Services.AddOptions<SessionOptions>().BindConfiguration("Moonlight:Session");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UseBase(WebApplication application)
|
private static void UseBase(WebApplication application)
|
||||||
|
|||||||
Reference in New Issue
Block a user