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:
2026-03-05 10:56:52 +00:00
parent 2d1b48b0d4
commit 7c5dc657dc
54 changed files with 1808 additions and 222 deletions

View File

@@ -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);
}