Implemented user logout and deletion service. Added Auth, Deletion and Logout hook. Restructed controllers
This commit is contained in:
@@ -6,6 +6,7 @@ using Microsoft.Extensions.Options;
|
||||
using Moonlight.Api.Configuration;
|
||||
using Moonlight.Api.Database;
|
||||
using Moonlight.Api.Database.Entities;
|
||||
using Moonlight.Api.Interfaces;
|
||||
using Moonlight.Shared;
|
||||
|
||||
namespace Moonlight.Api.Services;
|
||||
@@ -16,21 +17,25 @@ public class UserAuthService
|
||||
private readonly IMemoryCache Cache;
|
||||
private readonly ILogger<UserAuthService> Logger;
|
||||
private readonly IOptions<SessionOptions> Options;
|
||||
private readonly IEnumerable<IUserAuthHook> Hooks;
|
||||
|
||||
private const string UserIdClaim = "UserId";
|
||||
private const string IssuedAtClaim = "IssuedAt";
|
||||
|
||||
|
||||
public const string CacheKeyPattern = $"Moonlight.{nameof(UserAuthService)}.{nameof(ValidateAsync)}-{{0}}";
|
||||
|
||||
public UserAuthService(
|
||||
DatabaseRepository<User> userRepository,
|
||||
ILogger<UserAuthService> logger,
|
||||
IMemoryCache cache, IOptions<SessionOptions> options)
|
||||
IMemoryCache cache, IOptions<SessionOptions> options,
|
||||
IEnumerable<IUserAuthHook> hooks
|
||||
)
|
||||
{
|
||||
UserRepository = userRepository;
|
||||
Logger = logger;
|
||||
Cache = cache;
|
||||
Options = options;
|
||||
Hooks = hooks;
|
||||
}
|
||||
|
||||
public async Task<bool> SyncAsync(ClaimsPrincipal? principal)
|
||||
@@ -50,7 +55,6 @@ public class UserAuthService
|
||||
// We use email as the primary identifier here
|
||||
var user = await UserRepository
|
||||
.Query()
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(user => user.Email == email);
|
||||
|
||||
if (user == null) // Sync user if not already existing in the database
|
||||
@@ -74,6 +78,13 @@ public class UserAuthService
|
||||
new Claim(IssuedAtClaim, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())
|
||||
]);
|
||||
|
||||
foreach (var hook in Hooks)
|
||||
{
|
||||
// Run every hook and if any returns false we return false as well
|
||||
if(!await hook.SyncAsync(principal, user))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -89,7 +100,7 @@ public class UserAuthService
|
||||
return false;
|
||||
|
||||
var cacheKey = string.Format(CacheKeyPattern, userId);
|
||||
|
||||
|
||||
if (!Cache.TryGetValue<UserSession>(cacheKey, out var user))
|
||||
{
|
||||
user = await UserRepository
|
||||
@@ -131,9 +142,17 @@ public class UserAuthService
|
||||
if (issuedAt < user.InvalidateTimestamp)
|
||||
return false;
|
||||
|
||||
// Load every permission as claim
|
||||
principal.Identities.First().AddClaims(
|
||||
user.Permissions.Select(x => new Claim(Permissions.ClaimType, x))
|
||||
);
|
||||
|
||||
foreach (var hook in Hooks)
|
||||
{
|
||||
// Run every hook and if any returns false we return false as well
|
||||
if(!await hook.ValidateAsync(principal, userId))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
61
Moonlight.Api/Services/UserDeletionService.cs
Normal file
61
Moonlight.Api/Services/UserDeletionService.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Moonlight.Api.Database;
|
||||
using Moonlight.Api.Database.Entities;
|
||||
using Moonlight.Api.Interfaces;
|
||||
|
||||
namespace Moonlight.Api.Services;
|
||||
|
||||
public class UserDeletionService
|
||||
{
|
||||
private readonly DatabaseRepository<User> Repository;
|
||||
private readonly IEnumerable<IUserDeletionHook> Hooks;
|
||||
private readonly IMemoryCache Cache;
|
||||
|
||||
public UserDeletionService(DatabaseRepository<User> repository, IEnumerable<IUserDeletionHook> hooks, IMemoryCache cache)
|
||||
{
|
||||
Repository = repository;
|
||||
Hooks = hooks;
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
public async Task<UserDeletionValidationResult> ValidateAsync(int userId)
|
||||
{
|
||||
var user = await Repository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
|
||||
if(user == null)
|
||||
throw new AggregateException($"User with id {userId} not found");
|
||||
|
||||
var errorMessages = new List<string>();
|
||||
|
||||
foreach (var hook in Hooks)
|
||||
{
|
||||
if (await hook.ValidateAsync(user, errorMessages))
|
||||
continue;
|
||||
|
||||
return new UserDeletionValidationResult(false, errorMessages);
|
||||
}
|
||||
|
||||
return new UserDeletionValidationResult(true, []);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int userId)
|
||||
{
|
||||
var user = await Repository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
|
||||
if(user == null)
|
||||
throw new AggregateException($"User with id {userId} not found");
|
||||
|
||||
foreach (var hook in Hooks)
|
||||
await hook.ExecuteAsync(user);
|
||||
|
||||
await Repository.RemoveAsync(user);
|
||||
Cache.Remove(string.Format(UserAuthService.CacheKeyPattern, userId));
|
||||
}
|
||||
}
|
||||
|
||||
public record UserDeletionValidationResult(bool IsValid, IEnumerable<string> ErrorMessages);
|
||||
43
Moonlight.Api/Services/UserLogoutService.cs
Normal file
43
Moonlight.Api/Services/UserLogoutService.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Moonlight.Api.Database;
|
||||
using Moonlight.Api.Database.Entities;
|
||||
using Moonlight.Api.Interfaces;
|
||||
|
||||
namespace Moonlight.Api.Services;
|
||||
|
||||
public class UserLogoutService
|
||||
{
|
||||
private readonly DatabaseRepository<User> Repository;
|
||||
private readonly IEnumerable<IUserLogoutHook> Hooks;
|
||||
private readonly IMemoryCache Cache;
|
||||
|
||||
public UserLogoutService(
|
||||
DatabaseRepository<User> repository,
|
||||
IEnumerable<IUserLogoutHook> hooks,
|
||||
IMemoryCache cache
|
||||
)
|
||||
{
|
||||
Repository = repository;
|
||||
Hooks = hooks;
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
public async Task LogoutAsync(int userId)
|
||||
{
|
||||
var user = await Repository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == userId);
|
||||
|
||||
if(user == null)
|
||||
throw new AggregateException($"User with id {userId} not found");
|
||||
|
||||
foreach (var hook in Hooks)
|
||||
await hook.ExecuteAsync(user);
|
||||
|
||||
user.InvalidateTimestamp = DateTimeOffset.UtcNow;
|
||||
await Repository.UpdateAsync(user);
|
||||
|
||||
Cache.Remove(string.Format(UserAuthService.CacheKeyPattern, userId));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user