Implemented proper node authentication
This commit is contained in:
8
MoonlightServers.ApiServer/Helpers/NodeAuthOptions.cs
Normal file
8
MoonlightServers.ApiServer/Helpers/NodeAuthOptions.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Helpers;
|
||||
|
||||
public class NodeAuthOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
|
||||
}
|
||||
76
MoonlightServers.ApiServer/Helpers/NodeAuthScheme.cs
Normal file
76
MoonlightServers.ApiServer/Helpers/NodeAuthScheme.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Helpers;
|
||||
|
||||
public class NodeAuthScheme : AuthenticationHandler<NodeAuthOptions>
|
||||
{
|
||||
public NodeAuthScheme(IOptionsMonitor<NodeAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder,
|
||||
ISystemClock clock) : base(options, logger, encoder, clock)
|
||||
{
|
||||
}
|
||||
|
||||
public NodeAuthScheme(IOptionsMonitor<NodeAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder) : base(
|
||||
options, logger, encoder)
|
||||
{
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
if (!Request.Headers.ContainsKey("Authorization"))
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
var authHeaderValue = Request.Headers["Authorization"].FirstOrDefault();
|
||||
|
||||
if (string.IsNullOrEmpty(authHeaderValue))
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
if (!authHeaderValue.Contains("Bearer "))
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
var tokenParts = authHeaderValue
|
||||
.Replace("Bearer ", "")
|
||||
.Trim()
|
||||
.Split('.');
|
||||
|
||||
if (tokenParts.Length != 2)
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
var tokenId = tokenParts[0];
|
||||
var token = tokenParts[1];
|
||||
|
||||
if (tokenId.Length != 6)
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
var nodeRepo = Context.RequestServices.GetRequiredService<DatabaseRepository<Node>>();
|
||||
|
||||
var node = await nodeRepo
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.TokenId == tokenId);
|
||||
|
||||
if (node == null)
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
if (node.Token != token)
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
return AuthenticateResult.Success(
|
||||
new AuthenticationTicket(
|
||||
new ClaimsPrincipal(
|
||||
new ClaimsIdentity(
|
||||
[
|
||||
new Claim("nodeId", node.Id.ToString())
|
||||
],
|
||||
"nodeAuthentication"
|
||||
)
|
||||
),
|
||||
"nodeAuthentication"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -48,8 +48,8 @@ public class NodesController : Controller
|
||||
{
|
||||
var node = Mapper.Map<Node>(request);
|
||||
|
||||
node.Token = Formatter.GenerateString(32);
|
||||
node.TokenId = Formatter.GenerateString(6);
|
||||
node.Token = Formatter.GenerateString(32);
|
||||
|
||||
var finalNode = await NodeRepository.Add(node);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace MoonlightServers.ApiServer.Http.Controllers.Remote.Nodes;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/remote/server/node")]
|
||||
[Authorize(AuthenticationSchemes = "serverNodeAuthentication")]
|
||||
[Authorize(AuthenticationSchemes = "nodeAuthentication")]
|
||||
public class NodeTripController : Controller
|
||||
{
|
||||
[HttpGet("trip")]
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace MoonlightServers.ApiServer.Http.Controllers.Remote;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/remote/servers")]
|
||||
[Authorize(AuthenticationSchemes = "serverNodeAuthentication")]
|
||||
[Authorize(AuthenticationSchemes = "nodeAuthentication")]
|
||||
public class ServersController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Server> ServerRepository;
|
||||
@@ -21,7 +21,8 @@ public class ServersController : Controller
|
||||
public ServersController(
|
||||
DatabaseRepository<Server> serverRepository,
|
||||
DatabaseRepository<Node> nodeRepository,
|
||||
ILogger<ServersController> logger)
|
||||
ILogger<ServersController> logger
|
||||
)
|
||||
{
|
||||
ServerRepository = serverRepository;
|
||||
NodeRepository = nodeRepository;
|
||||
@@ -31,12 +32,12 @@ public class ServersController : Controller
|
||||
[HttpGet]
|
||||
public async Task<PagedData<ServerDataResponse>> Get([FromQuery] int page, [FromQuery] int pageSize)
|
||||
{
|
||||
// Load the node via the token id
|
||||
var tokenId = User.Claims.First(x => x.Type == "iss").Value;
|
||||
// Load the node via the id
|
||||
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
|
||||
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstAsync(x => x.TokenId == tokenId);
|
||||
.FirstAsync(x => x.Id == nodeId);
|
||||
|
||||
var total = await ServerRepository
|
||||
.Get()
|
||||
@@ -79,12 +80,12 @@ public class ServersController : Controller
|
||||
[HttpGet("{id:int}")]
|
||||
public async Task<ServerDataResponse> Get([FromRoute] int id)
|
||||
{
|
||||
// Load the node via the token id
|
||||
var tokenId = User.Claims.First(x => x.Type == "iss").Value;
|
||||
// Load the node via the id
|
||||
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
|
||||
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstAsync(x => x.TokenId == tokenId);
|
||||
.FirstAsync(x => x.Id == nodeId);
|
||||
|
||||
// Load the server with the star data attached. We filter by the node to ensure the node can only access
|
||||
// servers linked to it
|
||||
@@ -111,12 +112,12 @@ public class ServersController : Controller
|
||||
[HttpGet("{id:int}/install")]
|
||||
public async Task<ServerInstallDataResponse> GetInstall([FromRoute] int id)
|
||||
{
|
||||
// Load the node via the token id
|
||||
var tokenId = User.Claims.First(x => x.Type == "iss").Value;
|
||||
// Load the node via the id
|
||||
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
|
||||
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstAsync(x => x.TokenId == tokenId);
|
||||
.FirstAsync(x => x.Id == nodeId);
|
||||
|
||||
// Load the server with the star data attached. We filter by the node to ensure the node can only access
|
||||
// servers linked to it
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Implementations;
|
||||
|
||||
public class NodeJwtBearerOptions : IConfigureNamedOptions<JwtBearerOptions>
|
||||
{
|
||||
private readonly IServiceProvider ServiceProvider;
|
||||
|
||||
public NodeJwtBearerOptions(IServiceProvider serviceProvider)
|
||||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void Configure(JwtBearerOptions options)
|
||||
{
|
||||
}
|
||||
|
||||
public void Configure(string? name, JwtBearerOptions options)
|
||||
{
|
||||
// Dont configure any other scheme
|
||||
if (name != "serverNodeAuthentication")
|
||||
return;
|
||||
|
||||
options.TokenValidationParameters.IssuerSigningKeyResolver = (_, _, kid, _) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(kid))
|
||||
return [];
|
||||
|
||||
if (kid.Length != 6)
|
||||
return [];
|
||||
|
||||
using var scope = ServiceProvider.CreateScope();
|
||||
|
||||
var nodeRepo = scope.ServiceProvider.GetRequiredService<DatabaseRepository<Node>>();
|
||||
|
||||
var node = nodeRepo
|
||||
.Get()
|
||||
.FirstOrDefault(x => x.TokenId == kid);
|
||||
|
||||
if (node == null)
|
||||
return [];
|
||||
|
||||
return
|
||||
[
|
||||
new SymmetricSecurityKey(
|
||||
Encoding.UTF8.GetBytes(node.Token)
|
||||
)
|
||||
];
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Database\Migrations\"/>
|
||||
<Folder Include="Helpers\"/>
|
||||
<Folder Include="Http\Middleware\"/>
|
||||
<Folder Include="Implementations\" />
|
||||
<Folder Include="Interfaces\"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -69,29 +69,6 @@ public class NodeService
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
private string GenerateJwt(Node node)
|
||||
{
|
||||
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
|
||||
|
||||
var securityTokenDescriptor = new SecurityTokenDescriptor()
|
||||
{
|
||||
//Expires = DateTime.UtcNow.AddYears(1),
|
||||
Expires = DateTime.UtcNow.AddMinutes(1),
|
||||
NotBefore = DateTime.UtcNow.AddSeconds(-1),
|
||||
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 HttpApiClient CreateApiClient(Node node)
|
||||
{
|
||||
@@ -105,8 +82,7 @@ public class NodeService
|
||||
BaseAddress = new Uri(url)
|
||||
};
|
||||
|
||||
var jwt = GenerateJwt(node);
|
||||
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwt}");
|
||||
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {node.Token}");
|
||||
|
||||
return new HttpApiClient(httpClient);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MoonCore.Extensions;
|
||||
using Moonlight.ApiServer.Interfaces.Startup;
|
||||
using MoonlightServers.ApiServer.Database;
|
||||
using MoonlightServers.ApiServer.Implementations;
|
||||
using MoonlightServers.ApiServer.Helpers;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Startup;
|
||||
|
||||
@@ -19,20 +17,7 @@ public class PluginStartup : IPluginStartup
|
||||
// Configure authentication for the remote endpoints
|
||||
builder.Services
|
||||
.AddAuthentication()
|
||||
.AddJwtBearer("serverNodeAuthentication", options =>
|
||||
{
|
||||
options.TokenValidationParameters = new()
|
||||
{
|
||||
ClockSkew = TimeSpan.Zero,
|
||||
ValidateIssuer = false,
|
||||
ValidateActor = false,
|
||||
ValidateLifetime = true,
|
||||
ValidateAudience = false,
|
||||
ValidateIssuerSigningKey = true
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<IConfigureOptions<JwtBearerOptions>, NodeJwtBearerOptions>();
|
||||
.AddScheme<NodeAuthOptions, NodeAuthScheme>("nodeAuthentication", null);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user