using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Security.Claims; using watcher_monitoring.Configuration; using watcher_monitoring.Data; using watcher_monitoring.Models; namespace watcher_monitoring.Controllers; public class AuthController : Controller { private readonly WatcherDbContext _context; private readonly ILogger _logger; private readonly OidcSettings _oidcSettings; public AuthController(WatcherDbContext context, ILogger logger, OidcSettings oidcSettings) { _context = context; _logger = logger; _oidcSettings = oidcSettings; } [AllowAnonymous] [HttpGet] public IActionResult Login(string? returnUrl = null) { // Wenn der Benutzer bereits angemeldet ist, zur Startseite weiterleiten if (User.Identity?.IsAuthenticated == true) { return RedirectToAction("Index", "Home"); } ViewData["ReturnUrl"] = returnUrl; ViewData["OidcEnabled"] = _oidcSettings.IsValid; return View(); } [AllowAnonymous] [HttpGet] public IActionResult OidcLogin(string? returnUrl = null) { if (!_oidcSettings.IsValid) { _logger.LogWarning("OIDC-Login versucht, aber OIDC ist nicht konfiguriert"); return RedirectToAction("Login"); } var properties = new AuthenticationProperties { RedirectUri = Url.Action("OidcCallback", "Auth", new { returnUrl }), Items = { { "returnUrl", returnUrl ?? "/" } } }; return Challenge(properties, OpenIdConnectDefaults.AuthenticationScheme); } [AllowAnonymous] [HttpGet] public async Task OidcCallback(string? returnUrl = null) { var authenticateResult = await HttpContext.AuthenticateAsync(OpenIdConnectDefaults.AuthenticationScheme); if (!authenticateResult.Succeeded) { _logger.LogWarning("OIDC-Authentifizierung fehlgeschlagen: {Failure}", authenticateResult.Failure?.Message); TempData["Error"] = "OIDC-Anmeldung fehlgeschlagen. Bitte versuchen Sie es erneut."; return RedirectToAction("Login"); } var oidcClaims = authenticateResult.Principal?.Claims; var oidcSubject = oidcClaims?.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value ?? oidcClaims?.FirstOrDefault(c => c.Type == "sub")?.Value; if (string.IsNullOrEmpty(oidcSubject)) { _logger.LogError("OIDC-Claims enthalten keine Subject-ID"); TempData["Error"] = "OIDC-Anmeldung fehlgeschlagen: Keine Benutzer-ID erhalten."; return RedirectToAction("Login"); } var username = oidcClaims?.FirstOrDefault(c => c.Type == _oidcSettings.ClaimUsername)?.Value ?? oidcClaims?.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value ?? oidcClaims?.FirstOrDefault(c => c.Type == "name")?.Value ?? oidcSubject; var email = oidcClaims?.FirstOrDefault(c => c.Type == _oidcSettings.ClaimEmail)?.Value ?? oidcClaims?.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value ?? $"{username}@oidc.local"; var user = await _context.Users.FirstOrDefaultAsync(u => u.OidcSubject == oidcSubject); if (user == null) { if (!_oidcSettings.AutoProvisionUsers) { _logger.LogWarning("OIDC-User {Subject} existiert nicht und Auto-Provisioning ist deaktiviert", oidcSubject); TempData["Error"] = "Ihr Benutzerkonto existiert nicht. Bitte kontaktieren Sie den Administrator."; return RedirectToAction("Login"); } var existingUsername = await _context.Users.AnyAsync(u => u.Username == username); if (existingUsername) { username = $"{username}_{oidcSubject[..Math.Min(8, oidcSubject.Length)]}"; } user = new User { Username = username, Email = email, OidcSubject = oidcSubject, Password = string.Empty, IsActive = true, CreatedAt = DateTime.UtcNow, LastLogin = DateTime.UtcNow }; _context.Users.Add(user); await _context.SaveChangesAsync(); _logger.LogInformation("OIDC-User {Username} wurde automatisch erstellt (Subject: {Subject})", username, oidcSubject); } else { if (!user.IsActive) { _logger.LogWarning("OIDC-User {Username} ist deaktiviert", user.Username); TempData["Error"] = "Ihr Benutzerkonto ist deaktiviert."; return RedirectToAction("Login"); } user.LastLogin = DateTime.UtcNow; user.Email = email; await _context.SaveChangesAsync(); } var claims = new List { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.Username), new Claim(ClaimTypes.Email, user.Email), new Claim("LastLogin", user.LastLogin.ToString("o")), new Claim("AuthMethod", "OIDC") }; var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); var authProperties = new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.AddHours(8) }; await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, authProperties); _logger.LogInformation("OIDC-User {Username} erfolgreich angemeldet", user.Username); if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } return RedirectToAction("Index", "Home"); } [AllowAnonymous] [HttpPost] [ValidateAntiForgeryToken] public async Task Login(LoginViewModel model, string? returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; if (!ModelState.IsValid) { return View(model); } try { // Benutzer suchen var user = await _context.Users .FirstOrDefaultAsync(u => u.Username == model.Username && u.IsActive); if (user == null) { _logger.LogWarning("Login-Versuch mit ungültigem Benutzernamen: {Username}", model.Username); TempData["Error"] = "Ungültiger Benutzername oder Passwort"; return View(model); } // Passwort überprüfen (BCrypt) if (!BCrypt.Net.BCrypt.Verify(model.Password, user.Password)) { _logger.LogWarning("Login-Versuch mit falschem Passwort für Benutzer: {Username}", model.Username); TempData["Error"] = "Ungültiger Benutzername oder Passwort"; return View(model); } // LastLogin aktualisieren user.LastLogin = DateTime.UtcNow; await _context.SaveChangesAsync(); // Claims erstellen var claims = new List { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.Username), new Claim(ClaimTypes.Email, user.Email), new Claim("LastLogin", user.LastLogin.ToString("o")) }; var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); var authProperties = new AuthenticationProperties { IsPersistent = model.RememberMe, ExpiresUtc = model.RememberMe ? DateTimeOffset.UtcNow.AddDays(30) : DateTimeOffset.UtcNow.AddHours(8) }; await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, authProperties); _logger.LogInformation("Benutzer {Username} erfolgreich angemeldet", user.Username); if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } return RedirectToAction("Index", "Home"); } catch (Exception ex) { _logger.LogError(ex, "Fehler beim Login-Vorgang"); TempData["Error"] = "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut."; return View(model); } } [Authorize] [HttpPost] [ValidateAntiForgeryToken] public async Task Logout() { var username = User.Identity?.Name ?? "Unbekannt"; await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); _logger.LogInformation("Benutzer {Username} erfolgreich abgemeldet", username); return RedirectToAction("Login", "Auth"); } [AllowAnonymous] public IActionResult AccessDenied() { return View(); } }