Implemented node crud and status health check. Added daemon status health endpoint. Refactored project structure. Added sidebar items and ui views
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Hybrid;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Implementations.NodeToken;
|
||||
|
||||
public class NodeTokenSchemeHandler : AuthenticationHandler<NodeTokenSchemeOptions>
|
||||
{
|
||||
public const string SchemeName = "MoonlightServers.NodeToken";
|
||||
public const string CacheKeyFormat = $"MoonlightServers.{nameof(NodeTokenSchemeHandler)}.{{0}}";
|
||||
|
||||
private readonly DatabaseRepository<Node> DatabaseRepository;
|
||||
private readonly HybridCache Cache;
|
||||
|
||||
public NodeTokenSchemeHandler(
|
||||
IOptionsMonitor<NodeTokenSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
DatabaseRepository<Node> databaseRepository,
|
||||
HybridCache cache
|
||||
) : base(options, logger, encoder)
|
||||
{
|
||||
DatabaseRepository = databaseRepository;
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
// Basic format validation
|
||||
|
||||
if (!Context.Request.Headers.TryGetValue(HeaderNames.Authorization, out var authHeaderValues))
|
||||
return AuthenticateResult.Fail("No authorization header present");
|
||||
|
||||
if (authHeaderValues.Count != 1)
|
||||
return AuthenticateResult.Fail("No authorization value present");
|
||||
|
||||
var authHeaderValue = authHeaderValues[0];
|
||||
|
||||
if (string.IsNullOrEmpty(authHeaderValue))
|
||||
return AuthenticateResult.Fail("No authorization value present");
|
||||
|
||||
var authHeaderParts = authHeaderValue.Split(
|
||||
' ',
|
||||
StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries
|
||||
);
|
||||
|
||||
// Validate parts
|
||||
if (authHeaderParts.Length < 2)
|
||||
return AuthenticateResult.Fail("Malformed authorization header");
|
||||
|
||||
var tokenId = authHeaderParts[0];
|
||||
var token = authHeaderParts[1];
|
||||
|
||||
if (tokenId.Length != 10 && token.Length != 64)
|
||||
return AuthenticateResult.Fail("Malformed authorization header");
|
||||
|
||||
// Real validation
|
||||
|
||||
var cacheKey = string.Format(CacheKeyFormat, tokenId);
|
||||
|
||||
var session = await Cache.GetOrCreateAsync<NodeTokenSession?>(cacheKey, async cancellationToken =>
|
||||
{
|
||||
return await DatabaseRepository
|
||||
.Query()
|
||||
.Where(x => x.TokenId == tokenId)
|
||||
.Select(x => new NodeTokenSession(x.Id, x.Token))
|
||||
.FirstOrDefaultAsync(cancellationToken: cancellationToken);
|
||||
},
|
||||
new HybridCacheEntryOptions()
|
||||
{
|
||||
LocalCacheExpiration = Options.LookupCacheL1Expiry,
|
||||
Expiration = Options.LookupCacheL2Expiry
|
||||
}
|
||||
);
|
||||
|
||||
if(session == null || token != session.Token)
|
||||
return AuthenticateResult.Fail("Invalid authorization header");
|
||||
|
||||
// All checks have passed, create auth ticket
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(
|
||||
new ClaimsPrincipal(
|
||||
new ClaimsIdentity(
|
||||
[
|
||||
new Claim("NodeId", session.Id.ToString())
|
||||
],
|
||||
SchemeName
|
||||
)
|
||||
),
|
||||
SchemeName
|
||||
));
|
||||
}
|
||||
|
||||
private record NodeTokenSession(int Id, string Token);
|
||||
}
|
||||
Reference in New Issue
Block a user