Added basic authentication and localization

This commit is contained in:
2025-12-14 11:38:32 +01:00
parent 78f52faf46
commit c56b8a7f32
9 changed files with 184 additions and 7 deletions

3
.gitignore vendored
View File

@@ -16,4 +16,5 @@ src/Indexer/Scripts/__pycache__
src/Indexer/logs src/Indexer/logs
src/Server/logs src/Server/logs
src/Shared/bin src/Shared/bin
src/Shared/obj src/Shared/obj
src/Server/wwwroot/logs/*

View File

@@ -0,0 +1,65 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Server.Models;
namespace Server.Controllers;
[Route("[Controller]")]
public class AccountController : Controller
{
private readonly SimpleAuthOptions _options;
public AccountController(IOptions<SimpleAuthOptions> options)
{
_options = options.Value;
}
[HttpGet("Login")]
public IActionResult Login(string? returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}
[HttpPost("Login")]
public async Task<IActionResult> Login(
string username,
string password,
string? returnUrl = null)
{
var user = _options.Users.SingleOrDefault(u =>
u.Username == username && u.Password == password);
if (user == null)
{
ModelState.AddModelError("", "Invalid credentials");
return View();
}
var claims = new List<Claim>
{
new(ClaimTypes.Name, user.Username)
};
claims.AddRange(user.Roles.Select(r =>
new Claim(ClaimTypes.Role, r)));
var identity = new ClaimsIdentity(
claims, "AppCookie");
await HttpContext.SignInAsync(
"AppCookie",
new ClaimsPrincipal(identity));
return Redirect(returnUrl ?? "/");
}
[HttpGet("Logout")]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync("AppCookie");
return RedirectToAction("Login");
}
}

View File

@@ -16,6 +16,7 @@ public class HomeController : Controller
_logger = logger; _logger = logger;
} }
[Authorize]
[HttpGet("/")] [HttpGet("/")]
public IActionResult Index() public IActionResult Index()
{ {

13
src/Server/Models/Auth.cs Normal file
View File

@@ -0,0 +1,13 @@
namespace Server.Models;
public class SimpleAuthOptions
{
public List<SimpleUser> Users { get; set; } = new();
}
public class SimpleUser
{
public string Username { get; set; } = "";
public string Password { get; set; } = "";
public string[] Roles { get; set; } = Array.Empty<string>();
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="LoggedInAs" xml:space="preserve">
<value>Angemeldet als</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Abmelden</value>
</data>
<data name="Login" xml:space="preserve">
<value>Anmelden</value>
</data>
<data name="Username" xml:space="preserve">
<value>Benutzername</value>
</data>
<data name="Password" xml:space="preserve">
<value>Passwort</value>
</data>
<data name="InvalidCredentials" xml:space="preserve">
<value>Ungültiger Benutzername oder Passwort.</value>
</data>
</root>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="LoggedInAs" xml:space="preserve">
<value>Logged in as</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Log out</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="Username" xml:space="preserve">
<value>Username</value>
</data>
<data name="Password" xml:space="preserve">
<value>Password</value>
</data>
<data name="InvalidCredentials" xml:space="preserve">
<value>Invalid credentials.</value>
</data>
</root>

View File

@@ -0,0 +1,18 @@
using Microsoft.Extensions.Localization;
namespace Server.Services;
public class LocalizationService
{
private readonly IStringLocalizer _localizer;
public LocalizationService(IStringLocalizerFactory factory)
{
_localizer = factory.Create("SharedResources", "Server");
}
public string Get(string key) => _localizer[key];
public string this[string key] => _localizer[key];
public string this[string key, params object[] args] => _localizer[key, args];
}

View File

@@ -0,0 +1,28 @@
@using Server.Services
@inject LocalizationService T
@{
ViewData["Title"] = "Login";
var returnUrl = ViewData["ReturnUrl"] as string;
}
<div class="text-center">
<h1>Login</h1>
<form asp-action="Login" method="post" class="mt-4" style="max-width: 400px; margin: auto;">
<div class="form-group mb-3">
<label for="username" class="form-label">@T["Username"]</label>
<input autofocus type="text" class="form-control" id="username" name="username" autocomplete="username" required>
</div>
<div class="form-group mb-3">
<label for="password" class="form-label">@T["Password"]</label>
<input type="password" class="form-control" id="password" name="password" autocomplete="current-password" required>
</div>
<button type="submit" class="btn btn-primary w-100">@T["Login"]</button>
@if (!ViewData.ModelState.IsValid)
{
<p style="color:red">Invalid username or password</p>
}
</form>
</div>

View File

@@ -19,12 +19,21 @@
</button> </button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between"> <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1"> <ul class="navbar-nav flex-grow-1">
<li class="nav-item"> @if (User.Identity?.IsAuthenticated == true)
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a> {
</li> <li class="nav-item">
<li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a> </li>
</li> <li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Account" asp-action="Logout">Logout</a>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Account" asp-action="Login">Login</a>
</li>
}
</ul> </ul>
</div> </div>
</div> </div>