Improved jwt handling for node access tokens. Switched to di plugin system

This commit is contained in:
2025-02-24 21:03:23 +01:00
parent 67efe71247
commit a8d867c3c7
11 changed files with 101 additions and 48 deletions

View File

@@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore;
using MoonCore.Exceptions; using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions; using MoonCore.Extended.Abstractions;
using MoonCore.Extensions; using MoonCore.Extensions;
using MoonCore.Helpers;
using MoonlightServers.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Services; using MoonlightServers.ApiServer.Services;
@@ -117,7 +118,9 @@ public class ServerPowerController : Controller
if (server.OwnerId == userId) // The current user is the owner if (server.OwnerId == userId) // The current user is the owner
return server; return server;
if (User.HasPermission("admin.servers.get")) // The current user is an admin var permissions = User.Claims.First(x => x.Type == "permissions").Value.Split(";", StringSplitOptions.RemoveEmptyEntries);
if (PermissionHelper.HasPermission(permissions, "admin.servers.get")) // The current user is an admin
return server; return server;
throw new HttpApiException("No server with this id found", 404); throw new HttpApiException("No server with this id found", 404);

View File

@@ -5,6 +5,7 @@ using MoonCore.Extended.PermFilter;
using MoonCore.Exceptions; using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions; using MoonCore.Extended.Abstractions;
using MoonCore.Extensions; using MoonCore.Extensions;
using MoonCore.Helpers;
using MoonCore.Models; using MoonCore.Models;
using Moonlight.ApiServer.Database.Entities; using Moonlight.ApiServer.Database.Entities;
using MoonlightServers.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Database.Entities;
@@ -198,7 +199,9 @@ public class ServersController : Controller
if (server.OwnerId == userId) // The current user is the owner if (server.OwnerId == userId) // The current user is the owner
return server; return server;
if (User.HasPermission("admin.servers.get")) // The current user is an admin var permissions = User.Claims.First(x => x.Type == "permissions").Value.Split(";", StringSplitOptions.RemoveEmptyEntries);
if (PermissionHelper.HasPermission(permissions, "admin.servers.get")) // The current user is an admin
return server; return server;
throw new HttpApiException("No server with this id found", 404); throw new HttpApiException("No server with this id found", 404);

View File

@@ -1,3 +1,7 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using MoonCore.Attributes; using MoonCore.Attributes;
using MoonCore.Extended.Helpers; using MoonCore.Extended.Helpers;
using MoonCore.Helpers; using MoonCore.Helpers;
@@ -20,20 +24,43 @@ public class NodeService
url += "http://"; url += "http://";
url += $"{node.Fqdn}:{node.HttpPort}/"; url += $"{node.Fqdn}:{node.HttpPort}/";
var httpClient = new HttpClient() var httpClient = new HttpClient()
{ {
BaseAddress = new Uri(url) BaseAddress = new Uri(url)
}; };
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {node.Token}"); httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {node.Token}");
return new HttpApiClient(httpClient); return new HttpApiClient(httpClient);
} }
public string CreateAccessToken(Node node, Action<Dictionary<string, object>> parameters, TimeSpan duration) public string CreateAccessToken(Node node, Action<Dictionary<string, object>> parameters, TimeSpan duration)
=> JwtHelper.Encode(node.Token, parameters, duration); {
var claims = new Dictionary<string, object>();
parameters.Invoke(claims);
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
var securityTokenDescriptor = new SecurityTokenDescriptor()
{
Expires = DateTime.UtcNow.Add(duration),
NotBefore = DateTime.UtcNow.AddSeconds(-1),
Claims = claims,
IssuedAt = DateTime.UtcNow,
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
node.Token
)),
SecurityAlgorithms.HmacSha256
)
};
var securityToken = jwtSecurityTokenHandler.CreateJwtSecurityToken(securityTokenDescriptor);
return jwtSecurityTokenHandler.WriteToken(securityToken);
}
public async Task<SystemStatusResponse> GetSystemStatus(Node node) public async Task<SystemStatusResponse> GetSystemStatus(Node node)
{ {
using var apiClient = await CreateApiClient(node); using var apiClient = await CreateApiClient(node);
@@ -47,13 +74,13 @@ public class NodeService
using var apiClient = await CreateApiClient(node); using var apiClient = await CreateApiClient(node);
return await apiClient.GetJson<StatisticsApplicationResponse>("api/statistics/application"); return await apiClient.GetJson<StatisticsApplicationResponse>("api/statistics/application");
} }
public async Task<StatisticsHostResponse> GetHostStatistics(Node node) public async Task<StatisticsHostResponse> GetHostStatistics(Node node)
{ {
using var apiClient = await CreateApiClient(node); using var apiClient = await CreateApiClient(node);
return await apiClient.GetJson<StatisticsHostResponse>("api/statistics/host"); return await apiClient.GetJson<StatisticsHostResponse>("api/statistics/host");
} }
public async Task<StatisticsDockerResponse> GetDockerStatistics(Node node) public async Task<StatisticsDockerResponse> GetDockerStatistics(Node node)
{ {
using var apiClient = await CreateApiClient(node); using var apiClient = await CreateApiClient(node);

View File

@@ -1,15 +0,0 @@
using Moonlight.ApiServer.Helpers;
using Moonlight.ApiServer.Interfaces.Startup;
using MoonlightServers.ApiServer.Database;
namespace MoonlightServers.ApiServer.Startup;
public class DatabaseStartup : IDatabaseStartup
{
public Task ConfigureDatabase(DatabaseContextCollection collection)
{
collection.Add<ServersDataContext>();
return Task.CompletedTask;
}
}

View File

@@ -1,10 +1,12 @@
using MoonCore.Extensions; using MoonCore.Extensions;
using Moonlight.ApiServer.Helpers;
using Moonlight.ApiServer.Interfaces.Startup; using Moonlight.ApiServer.Interfaces.Startup;
using Moonlight.ApiServer.Services; using Moonlight.ApiServer.Services;
using MoonlightServers.ApiServer.Database;
namespace MoonlightServers.ApiServer.Startup; namespace MoonlightServers.ApiServer.Startup;
public class PluginStartup : IAppStartup public class PluginStartup : IPluginStartup
{ {
private readonly BundleService BundleService; private readonly BundleService BundleService;
@@ -13,19 +15,27 @@ public class PluginStartup : IAppStartup
BundleService = bundleService; BundleService = bundleService;
} }
public Task BuildApp(IHostApplicationBuilder builder) public Task BuildApplication(IHostApplicationBuilder builder)
{ {
// Scan the current plugin assembly for di services // Scan the current plugin assembly for di services
builder.Services.AutoAddServices<PluginStartup>(); builder.Services.AutoAddServices<PluginStartup>();
BundleService.BundleCss("css/MoonlightServers.min.css"); BundleService.BundleCss("css/MoonlightServers.min.css");
BundleService.BundleCss("css/XtermBlazor.min.css"); BundleService.BundleCss("css/XtermBlazor.min.css");
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task ConfigureApp(IApplicationBuilder app) public Task ConfigureApplication(IApplicationBuilder app)
=> Task.CompletedTask;
public Task ConfigureDatabase(DatabaseContextCollection collection)
{ {
collection.Add<ServersDataContext>();
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task ConfigureEndpoints(IEndpointRouteBuilder routeBuilder)
=> Task.CompletedTask;
} }

View File

@@ -35,7 +35,7 @@ public partial class Server
public async Task InternalError() public async Task InternalError()
{ {
await LogToConsole("An unhandled error occured performing action"); await LogToConsole("An unhandled error occured performing action");
// TODO:
Logger.LogInformation("Reporting or smth"); Logger.LogInformation("Reporting or smth");
} }
} }

View File

@@ -1,4 +1,8 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Text.Json; using System.Text.Json;
using Microsoft.IdentityModel.Tokens;
using MoonCore.Attributes; using MoonCore.Attributes;
using MoonCore.Extended.Helpers; using MoonCore.Extended.Helpers;
using MoonlightServers.Daemon.Configuration; using MoonlightServers.Daemon.Configuration;
@@ -15,8 +19,33 @@ public class AccessTokenHelper
Configuration = configuration; Configuration = configuration;
} }
public bool Process(string accessToken, out Dictionary<string, JsonElement> data) // TODO: Improve
public bool Process(string accessToken, out Claim[] claims)
{ {
return JwtHelper.TryVerifyAndDecodePayload(Configuration.Security.Token, accessToken, out data); var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
try
{
var data = jwtSecurityTokenHandler.ValidateToken(accessToken, new()
{
ClockSkew = TimeSpan.Zero,
ValidateLifetime = true,
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration.Security.Token)
)
}, out var _);
claims = data.Claims.ToArray();
return true;
}
catch (Exception e)
{
claims = [];
return false;
}
} }
} }

View File

@@ -50,7 +50,7 @@ public class ServerWebSocketConnection
} }
// Validate access token data // Validate access token data
if (!accessData.ContainsKey("type") || !accessData.ContainsKey("serverId")) if (accessData.All(x => x.Type != "type") || accessData.All(x => x.Type != "serverId"))
{ {
Logger.LogDebug("Received invalid access token: Required parameters are missing"); Logger.LogDebug("Received invalid access token: Required parameters are missing");
@@ -63,7 +63,7 @@ public class ServerWebSocketConnection
} }
// Validate access token type // Validate access token type
var type = accessData["type"].GetString()!; var type = accessData.First(x => x.Type == "type").Value;
if (type != "websocket") if (type != "websocket")
{ {
@@ -77,7 +77,7 @@ public class ServerWebSocketConnection
return; return;
} }
var serverId = accessData["serverId"].GetInt32(); var serverId = int.Parse(accessData.First(x => x.Type == "serverId").Value);
// Check that the access token isn't for another server // Check that the access token isn't for another server
if (ServerId != -1 && ServerId == serverId) if (ServerId != -1 && ServerId == serverId)

View File

@@ -9,9 +9,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Docker.DotNet" Version="3.125.15" /> <PackageReference Include="Docker.DotNet" Version="3.125.15" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
<PackageReference Include="MoonCore" Version="1.8.2" /> <PackageReference Include="MoonCore" Version="1.8.3" />
<PackageReference Include="MoonCore.Extended" Version="1.2.7" /> <PackageReference Include="MoonCore.Extended" Version="1.2.8" />
<PackageReference Include="MoonCore.Unix" Version="1.0.0" /> <PackageReference Include="MoonCore.Unix" Version="1.0.2" />
<PackageReference Include="Stateless" Version="5.17.0" /> <PackageReference Include="Stateless" Version="5.17.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/>
</ItemGroup> </ItemGroup>

View File

@@ -1,28 +1,24 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using MoonCore.Extensions; using MoonCore.Extensions;
using MoonCore.PluginFramework.Extensions;
using Moonlight.Client.Interfaces; using Moonlight.Client.Interfaces;
using MoonlightServers.Frontend.Implementations;
using MoonlightServers.Frontend.Interfaces; using MoonlightServers.Frontend.Interfaces;
namespace MoonlightServers.Frontend.Startup; namespace MoonlightServers.Frontend.Startup;
public class PluginStartup : IAppStartup public class PluginStartup : IPluginStartup
{ {
public Task BuildApp(WebAssemblyHostBuilder builder) public Task BuildApplication(WebAssemblyHostBuilder builder)
{ {
builder.Services.AddSingleton<ISidebarItemProvider, SidebarImplementation>();
builder.Services.AddSingleton<IServerTabProvider, DefaultServerTabProvider>();
builder.Services.AutoAddServices<PluginStartup>(); builder.Services.AutoAddServices<PluginStartup>();
builder.Services.AddInterfaces(configuration =>
{
configuration.AddAssembly(GetType().Assembly);
configuration.AddInterface<IServerTabProvider>();
});
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task ConfigureApp(WebAssemblyHost app) public Task ConfigureApplication(WebAssemblyHost app)
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@@ -12,7 +12,7 @@
@using MoonlightServers.Frontend.UI.Components.Servers.ServerTabs @using MoonlightServers.Frontend.UI.Components.Servers.ServerTabs
@inject HttpApiClient ApiClient @inject HttpApiClient ApiClient
@inject IServerTabProvider[] TabProviders @inject IEnumerable<IServerTabProvider> TabProviders
@implements IAsyncDisposable @implements IAsyncDisposable