From 1412f7cc554b17fcb585ca34ce25a070d75e887d Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sun, 14 Dec 2025 11:38:32 +0100 Subject: [PATCH] Added basic authentication and localization --- .gitignore | 3 +- src/Server/Controllers/AccountController.cs | 65 +++++++++++++++++++ .../Controllers/Frontend/HomeController.cs | 1 + src/Server/Models/Auth.cs | 13 ++++ src/Server/Program.cs | 45 +++++++++++++ src/Server/Resources/SharedResources.de.resx | 21 ++++++ src/Server/Resources/SharedResources.en.resx | 21 ++++++ src/Server/Services/LocalizationService.cs | 18 +++++ src/Server/Views/Account/Login.cshtml | 28 ++++++++ src/Server/Views/Shared/_Layout.cshtml | 21 ++++-- 10 files changed, 229 insertions(+), 7 deletions(-) create mode 100644 src/Server/Controllers/AccountController.cs create mode 100644 src/Server/Models/Auth.cs create mode 100644 src/Server/Resources/SharedResources.de.resx create mode 100644 src/Server/Resources/SharedResources.en.resx create mode 100644 src/Server/Services/LocalizationService.cs create mode 100644 src/Server/Views/Account/Login.cshtml diff --git a/.gitignore b/.gitignore index d5d7fbf..45efc47 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ src/Indexer/Scripts/__pycache__ src/Indexer/logs src/Server/logs src/Shared/bin -src/Shared/obj \ No newline at end of file +src/Shared/obj +src/Server/wwwroot/logs/* diff --git a/src/Server/Controllers/AccountController.cs b/src/Server/Controllers/AccountController.cs new file mode 100644 index 0000000..9c0f19d --- /dev/null +++ b/src/Server/Controllers/AccountController.cs @@ -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 options) + { + _options = options.Value; + } + + [HttpGet("Login")] + public IActionResult Login(string? returnUrl = null) + { + ViewData["ReturnUrl"] = returnUrl; + return View(); + } + + [HttpPost("Login")] + public async Task 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 + { + 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 Logout() + { + await HttpContext.SignOutAsync("AppCookie"); + return RedirectToAction("Login"); + } +} diff --git a/src/Server/Controllers/Frontend/HomeController.cs b/src/Server/Controllers/Frontend/HomeController.cs index cb6d4b6..f0467ab 100644 --- a/src/Server/Controllers/Frontend/HomeController.cs +++ b/src/Server/Controllers/Frontend/HomeController.cs @@ -16,6 +16,7 @@ public class HomeController : Controller _logger = logger; } + [Authorize] [HttpGet("/")] public IActionResult Index() { diff --git a/src/Server/Models/Auth.cs b/src/Server/Models/Auth.cs new file mode 100644 index 0000000..1bd0fa4 --- /dev/null +++ b/src/Server/Models/Auth.cs @@ -0,0 +1,13 @@ +namespace Server.Models; + +public class SimpleAuthOptions +{ + public List Users { get; set; } = new(); +} + +public class SimpleUser +{ + public string Username { get; set; } = ""; + public string Password { get; set; } = ""; + public string[] Roles { get; set; } = Array.Empty(); +} diff --git a/src/Server/Program.cs b/src/Server/Program.cs index 2dadd78..df02526 100644 --- a/src/Server/Program.cs +++ b/src/Server/Program.cs @@ -1,15 +1,33 @@ using ElmahCore; using ElmahCore.Mvc; using Serilog; +using System.Globalization; +using Microsoft.AspNetCore.Localization; using Server; using Server.HealthChecks; using Server.Helper; +using Server.Models; +using Server.Services; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); + +// Add Localization +builder.Services.AddLocalization(options => options.ResourcesPath = "Resources"); +builder.Services.Configure(options => +{ + var supportedCultures = new[] { new CultureInfo("en"), new CultureInfo("de") }; + options.DefaultRequestCulture = new RequestCulture("en"); + options.SupportedCultures = supportedCultures; + options.SupportedUICultures = supportedCultures; +}); + +// Add LocalizationService +builder.Services.AddScoped(); + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); @@ -30,6 +48,24 @@ builder.Services.AddElmah(Options => Options.LogPath = builder.Configuration.GetValue("Embeddingsearch:Elmah:LogFolder") ?? "~/logs"; }); +builder.Services + .AddAuthentication("AppCookie") + .AddCookie("AppCookie", options => + { + options.LoginPath = "/Account/Login"; + options.LogoutPath = "/Account/Logout"; + options.AccessDeniedPath = "/Account/Denied"; + }); + +builder.Services.AddAuthorization(options => +{ + options.AddPolicy("AdminOnly", + policy => policy.RequireRole("Admin")); +}); + +IConfigurationSection simpleAuthSection = builder.Configuration.GetSection("Embeddingsearch:SimpleAuth"); +if (simpleAuthSection.Exists()) builder.Services.Configure(simpleAuthSection); + var app = builder.Build(); List? allowedIps = builder.Configuration.GetSection("Embeddingsearch:Elmah:AllowedHosts") @@ -77,6 +113,15 @@ if (UseMiddleware == true && !IsDevelopment) app.UseMiddleware(); } +// Add localization +var supportedCultures = new[] { "de", "de-DE", "en-US" }; +var localizationOptions = new RequestLocalizationOptions() + .SetDefaultCulture("de") + .AddSupportedCultures(supportedCultures) + .AddSupportedUICultures(supportedCultures); +app.UseRequestLocalization(localizationOptions); + +app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); diff --git a/src/Server/Resources/SharedResources.de.resx b/src/Server/Resources/SharedResources.de.resx new file mode 100644 index 0000000..e18c07d --- /dev/null +++ b/src/Server/Resources/SharedResources.de.resx @@ -0,0 +1,21 @@ + + + + Angemeldet als + + + Abmelden + + + Anmelden + + + Benutzername + + + Passwort + + + Ungültiger Benutzername oder Passwort. + + \ No newline at end of file diff --git a/src/Server/Resources/SharedResources.en.resx b/src/Server/Resources/SharedResources.en.resx new file mode 100644 index 0000000..bd6e064 --- /dev/null +++ b/src/Server/Resources/SharedResources.en.resx @@ -0,0 +1,21 @@ + + + + Logged in as + + + Log out + + + Login + + + Username + + + Password + + + Invalid credentials. + + \ No newline at end of file diff --git a/src/Server/Services/LocalizationService.cs b/src/Server/Services/LocalizationService.cs new file mode 100644 index 0000000..2e51070 --- /dev/null +++ b/src/Server/Services/LocalizationService.cs @@ -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]; +} \ No newline at end of file diff --git a/src/Server/Views/Account/Login.cshtml b/src/Server/Views/Account/Login.cshtml new file mode 100644 index 0000000..e3b132f --- /dev/null +++ b/src/Server/Views/Account/Login.cshtml @@ -0,0 +1,28 @@ +@using Server.Services +@inject LocalizationService T +@{ + ViewData["Title"] = "Login"; + var returnUrl = ViewData["ReturnUrl"] as string; +} + +
+

Login

+
+
+ + +
+ +
+ + +
+ + + + @if (!ViewData.ModelState.IsValid) + { +

Invalid username or password

+ } +
+
\ No newline at end of file diff --git a/src/Server/Views/Shared/_Layout.cshtml b/src/Server/Views/Shared/_Layout.cshtml index 2c3ee38..042d42e 100644 --- a/src/Server/Views/Shared/_Layout.cshtml +++ b/src/Server/Views/Shared/_Layout.cshtml @@ -19,12 +19,21 @@