Updated MoonCore dependencies. Switched to asp.net core native authentication scheme abstractions. Updated claim usage in frontend

This commit is contained in:
2025-08-20 16:16:31 +02:00
parent 60178dc54b
commit 3cc48fb8f7
42 changed files with 1459 additions and 858 deletions

View File

@@ -1,11 +1,12 @@
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using MoonCore.Extended.JwtInvalidation;
using MoonCore.Permissions;
using Moonlight.ApiServer.Implementations;
using Moonlight.ApiServer.Interfaces;
using Moonlight.ApiServer.Implementations.LocalAuth;
using Moonlight.ApiServer.Services;
namespace Moonlight.ApiServer.Startup;
@@ -15,8 +16,25 @@ public partial class Startup
private Task RegisterAuth()
{
WebApplicationBuilder.Services
.AddAuthentication("coreAuthentication")
.AddJwtBearer("coreAuthentication", options =>
.AddAuthentication(options => { options.DefaultScheme = "MainScheme"; })
.AddPolicyScheme("MainScheme", null, options =>
{
// If an api key is specified via the bearer auth header
// we want to use the ApiKey scheme for authenticating the request
options.ForwardDefaultSelector = context =>
{
if (!context.Request.Headers.TryGetValue("Authorization", out var authHeader))
return "Session";
var auth = authHeader.FirstOrDefault();
if (string.IsNullOrEmpty(auth) || !auth.StartsWith("Bearer "))
return "Session";
return "ApiKey";
};
})
.AddJwtBearer("ApiKey", null, options =>
{
options.TokenValidationParameters = new()
{
@@ -31,22 +49,116 @@ public partial class Startup
ValidateIssuer = true,
ValidIssuer = Configuration.PublicUrl
};
options.Events = new JwtBearerEvents()
{
OnTokenValidated = async context =>
{
var apiKeyAuthService = context
.HttpContext
.RequestServices
.GetRequiredService<ApiKeyAuthService>();
var result = await apiKeyAuthService.Validate(context.Principal);
if (!result)
context.Fail("API key has been deleted");
}
};
})
.AddCookie("Session", null, options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(Configuration.Authentication.Sessions.ExpiresIn);
options.Cookie = new CookieBuilder()
{
Name = Configuration.Authentication.Sessions.CookieName,
Path = "/",
IsEssential = true,
SecurePolicy = CookieSecurePolicy.SameAsRequest
};
// As redirects won't work in our spa which uses API calls
// we need to customize the responses when certain actions happen
options.Events.OnRedirectToLogin = async context =>
{
await Results.Problem(
title: "Unauthenticated",
detail: "You need to authenticate yourself to use this endpoint",
statusCode: 401
)
.ExecuteAsync(context.HttpContext);
};
options.Events.OnRedirectToAccessDenied = async context =>
{
await Results.Problem(
title: "Permission denied",
detail: "You are missing the required permissions to access this endpoint",
statusCode: 403
)
.ExecuteAsync(context.HttpContext);
};
options.Events.OnSigningIn = async context =>
{
var userSyncService = context
.HttpContext
.RequestServices
.GetRequiredService<UserAuthService>();
var result = await userSyncService.Sync(context.Principal);
if (!result)
context.Principal = new();
else
context.Properties.IsPersistent = true;
};
options.Events.OnValidatePrincipal = async context =>
{
var userSyncService = context
.HttpContext
.RequestServices
.GetRequiredService<UserAuthService>();
var result = await userSyncService.Validate(context.Principal);
if (!result)
context.RejectPrincipal();
};
})
.AddScheme<LocalAuthOptions, LocalAuthHandler>(LocalAuthConstants.AuthenticationScheme, "Local Auth", options =>
{
options.ForwardAuthenticate = "Session";
options.ForwardSignIn = "Session";
options.ForwardSignOut = "Session";
options.SignInScheme = "Session";
});
WebApplicationBuilder.Services.AddJwtBearerInvalidation("coreAuthentication");
WebApplicationBuilder.Services.AddScoped<IJwtInvalidateHandler, UserAuthInvalidation>();
WebApplicationBuilder.Services.AddAuthorization();
WebApplicationBuilder.Services.AddAuthorizationPermissions(options =>
{
options.ClaimName = "permissions";
options.ClaimName = "Permissions";
options.Prefix = "permissions:";
});
WebApplicationBuilder.Services.AddScoped<UserAuthService>();
WebApplicationBuilder.Services.AddScoped<ApiKeyAuthService>();
// Add local oauth2 provider if enabled
if (Configuration.Authentication.EnableLocalOAuth2)
WebApplicationBuilder.Services.AddScoped<IOAuth2Provider, LocalOAuth2Provider>();
// Setup data protection storage within storage folder
// so its persists in containers
var dpKeyPath = Path.Combine("storage", "dataProtectionKeys");
Directory.CreateDirectory(dpKeyPath);
WebApplicationBuilder.Services
.AddDataProtection()
.PersistKeysToFileSystem(
new DirectoryInfo(dpKeyPath)
);
WebApplicationBuilder.Services.AddScoped<UserDeletionService>();