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

@@ -0,0 +1,204 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Helpers;
using MoonCore.Helpers;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Database.Entities;
using Moonlight.ApiServer.Implementations.LocalAuth;
namespace Moonlight.ApiServer.Http.Controllers.LocalAuth;
[ApiController]
[Route("api/localAuth")]
public class LocalAuthController : Controller
{
private readonly DatabaseRepository<User> UserRepository;
private readonly IServiceProvider ServiceProvider;
private readonly IAuthenticationService AuthenticationService;
private readonly IOptionsMonitor<LocalAuthOptions> Options;
private readonly ILogger<LocalAuthController> Logger;
private readonly AppConfiguration Configuration;
public LocalAuthController(
DatabaseRepository<User> userRepository,
IServiceProvider serviceProvider,
IAuthenticationService authenticationService,
IOptionsMonitor<LocalAuthOptions> options,
ILogger<LocalAuthController> logger,
AppConfiguration configuration
)
{
UserRepository = userRepository;
ServiceProvider = serviceProvider;
AuthenticationService = authenticationService;
Options = options;
Logger = logger;
Configuration = configuration;
}
[HttpGet]
[HttpGet("login")]
public async Task<IResult> Login()
{
var html = await ComponentHelper.RenderComponent<Login>(ServiceProvider);
return Results.Content(html, "text/html");
}
[HttpGet("register")]
public async Task<IResult> Register()
{
var html = await ComponentHelper.RenderComponent<Register>(ServiceProvider);
return Results.Content(html, "text/html");
}
[HttpPost]
[HttpPost("login")]
public async Task<IResult> Login([FromForm] string email, [FromForm] string password)
{
try
{
// Perform login
var user = await InternalLogin(email, password);
// Login user
var options = Options.Get(LocalAuthConstants.AuthenticationScheme);
await AuthenticationService.SignInAsync(HttpContext, options.SignInScheme, new ClaimsPrincipal(
new ClaimsIdentity(
[
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username)
],
LocalAuthConstants.AuthenticationScheme
)
), new AuthenticationProperties());
// Redirect back to wasm app
return Results.Redirect("/");
}
catch (Exception e)
{
string errorMessage;
if (e is HttpApiException apiException)
errorMessage = apiException.Title;
else
{
errorMessage = "An internal error occured";
Logger.LogError(e, "An unhandled error occured while logging in user");
}
var html = await ComponentHelper.RenderComponent<Login>(ServiceProvider,
parameters => { parameters["ErrorMessage"] = errorMessage; });
return Results.Content(html, "text/html");
}
}
[HttpPost("register")]
public async Task<IResult> Register([FromForm] string email, [FromForm] string password, [FromForm] string username)
{
try
{
// Perform register
var user = await InternalRegister(username, email, password);
// Login user
var options = Options.Get(LocalAuthConstants.AuthenticationScheme);
await AuthenticationService.SignInAsync(HttpContext, options.SignInScheme, new ClaimsPrincipal(
new ClaimsIdentity(
[
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username)
],
LocalAuthConstants.AuthenticationScheme
)
), new AuthenticationProperties());
// Redirect back to wasm app
return Results.Redirect("/");
}
catch (Exception e)
{
string errorMessage;
if (e is HttpApiException apiException)
errorMessage = apiException.Title;
else
{
errorMessage = "An internal error occured";
Logger.LogError(e, "An unhandled error occured while logging in user");
}
var html = await ComponentHelper.RenderComponent<Register>(ServiceProvider,
parameters => { parameters["ErrorMessage"] = errorMessage; });
return Results.Content(html, "text/html");
}
}
private async Task<User> InternalRegister(string username, string email, string password)
{
email = email.ToLower();
username = username.ToLower();
if (await UserRepository.Get().AnyAsync(x => x.Username == username))
throw new HttpApiException("A account with that username already exists", 400);
if (await UserRepository.Get().AnyAsync(x => x.Email == email))
throw new HttpApiException("A account with that email already exists", 400);
string[] permissions = [];
if (Configuration.Authentication.FirstUserAdmin)
{
var count = await UserRepository
.Get()
.CountAsync();
if (count == 0)
permissions = ["*"];
}
var user = new User()
{
Username = username,
Email = email,
Password = HashHelper.Hash(password),
Permissions = permissions
};
var finalUser = await UserRepository.Add(user);
return finalUser;
}
private async Task<User> InternalLogin(string email, string password)
{
email = email.ToLower();
var user = await UserRepository
.Get()
.FirstOrDefaultAsync(x => x.Email == email);
if (user == null)
throw new HttpApiException("Invalid combination of email and password", 400);
if (!HashHelper.Verify(password, user.Password))
throw new HttpApiException("Invalid combination of email and password", 400);
return user;
}
}

View File

@@ -0,0 +1,58 @@
<html lang="en" class="h-full bg-background">
<head>
<title>Login into your account</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" type="text/css" href="/css/style.min.css"/>
</head>
<body class="h-full">
<div class="flex h-auto min-h-screen items-center justify-center overflow-x-hidden py-10">
<div class="relative flex items-center justify-center px-4 sm:px-6 lg:px-8">
<div
class="bg-base-100 shadow-base-300/20 z-1 w-full space-y-6 rounded-xl p-6 shadow-md sm:min-w-md lg:p-8">
<div class="flex justify-center items-center gap-3">
<img src="/_content/Moonlight.Client/svg/logo.svg" class="size-12" alt="brand-logo"/>
</div>
<div class="text-center">
<h3 class="text-base-content mb-1.5 text-2xl font-semibold">Login into your account</h3>
<p class="text-base-content/80">After logging in you will be able to manage your services</p>
</div>
<div class="space-y-4">
@if (!string.IsNullOrEmpty(ErrorMessage))
{
<div class="alert alert-error text-center">
@ErrorMessage
</div>
}
<form class="mb-4 space-y-4" method="post">
<div>
<label class="label-text" for="email">Email address</label>
<input type="email" name="email" placeholder="Enter your email address" class="input" id="email"
required/>
</div>
<div>
<label class="label-text" for="password">Password</label>
<input class="input" name="password" id="password" type="password" placeholder="············"
required/>
</div>
<button class="btn btn-lg btn-primary btn-gradient btn-block">Login</button>
</form>
<p class="text-base-content/80 mb-4 text-center">
No account?
<a href="/api/localAuth/register" class="link link-animated link-primary font-normal">Create an account</a>
</p>
</div>
</div>
</div>
</div>
</body>
</html>
@code
{
[Parameter] public string? ErrorMessage { get; set; }
}

View File

@@ -0,0 +1,62 @@
<html lang="en" class="h-full bg-background">
<head>
<title>Register a new account</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" type="text/css" href="/css/style.min.css"/>
</head>
<body class="h-full">
<div class="flex h-auto min-h-screen items-center justify-center overflow-x-hidden py-10">
<div class="relative flex items-center justify-center px-4 sm:px-6 lg:px-8">
<div
class="bg-base-100 shadow-base-300/20 z-1 w-full space-y-6 rounded-xl p-6 shadow-md sm:min-w-md lg:p-8">
<div class="flex justify-center items-center gap-3">
<img src="/_content/Moonlight.Client/svg/logo.svg" class="size-12" alt="brand-logo"/>
</div>
<div class="text-center">
<h3 class="text-base-content mb-1.5 text-2xl font-semibold">Register a new account</h3>
<p class="text-base-content/80">After signing up you will be able to manage your services</p>
</div>
<div class="space-y-4">
@if (!string.IsNullOrEmpty(ErrorMessage))
{
<div class="alert alert-error text-center">
@ErrorMessage
</div>
}
<form class="mb-4 space-y-4" method="post">
<div>
<label class="label-text" for="username">Username</label>
<input type="text" name="username" placeholder="Enter your username" class="input" id="username"
required/>
</div>
<div>
<label class="label-text" for="email">Email address</label>
<input type="email" name="email" placeholder="Enter your email address" class="input" id="email"
required/>
</div>
<div>
<label class="label-text" for="password">Password</label>
<input class="input" name="password" id="password" type="password" placeholder="············"
required/>
</div>
<button class="btn btn-lg btn-primary btn-gradient btn-block">Register</button>
</form>
<p class="text-base-content/80 mb-4 text-center">
Already registered?
<a href="/api/localAuth/login" class="link link-animated link-primary font-normal">Login into your account</a>
</p>
</div>
</div>
</div>
</div>
</body>
</html>
@code
{
[Parameter] public string? ErrorMessage { get; set; }
}