using DigitalData.Core.Abstractions.Application; using DigitalData.UserManager.Application.Contracts; using DigitalData.UserManager.Application.DTOs.User; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; using System.Security.Claims; using DigitalData.UserManager.Application.DTOs.Auth; using Microsoft.AspNetCore.Authorization; using EnvelopeGenerator.GeneratorAPI.Models; namespace EnvelopeGenerator.GeneratorAPI.Controllers { /// /// Controller verantwortlich für die Benutzer-Authentifizierung, einschließlich Anmelden, Abmelden und Überprüfung des Authentifizierungsstatus. /// [Route("api/[controller]")] [ApiController] public partial class AuthController : ControllerBase { private readonly ILogger _logger; private readonly IUserService _userService; private readonly IDirectorySearchService _dirSearchService; /// /// Initializes a new instance of the class. /// /// The logger instance. /// The user service instance. /// The directory search service instance. public AuthController(ILogger logger, IUserService userService, IDirectorySearchService dirSearchService) { _logger = logger; _userService = userService; _dirSearchService = dirSearchService; } /// /// Authentifiziert einen Benutzer und generiert ein JWT-Token. Wenn 'cookie' wahr ist, wird das Token als HTTP-Only-Cookie zurückgegeben. /// /// Benutzeranmeldedaten (Benutzername und Passwort). /// Wenn wahr, wird das JWT-Token auch als HTTP-Only-Cookie gesendet. /// /// Gibt eine HTTP 200 oder 401. /// /// /// Sample request: /// /// POST /api/auth?cookie=true /// { /// "username": "MaxMustermann", /// "password": "Geheim123!" /// } /// /// POST /api/auth?cookie=true /// { /// "id": "1", /// "password": "Geheim123!" /// } /// /// /// Erfolgreiche Anmeldung. Gibt das JWT-Token im Antwortkörper oder als Cookie zurück, wenn 'cookie' wahr ist. /// Unbefugt. Ungültiger Benutzername oder Passwort. [ProducesResponseType(typeof(string), StatusCodes.Status200OK, "text/javascript")] [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] [AllowAnonymous] [HttpPost] public async Task Login([FromBody] Login login, [FromQuery] bool cookie = false) { try { bool isValid = _dirSearchService.ValidateCredentials(login.Username, login.Password); if (!isValid) return Unauthorized(); //find the user var uRes = await _userService.ReadByUsernameAsync(login.Username); if (!uRes.IsSuccess || uRes.Data is null) { return Forbid(); } UserReadDto user = uRes.Data; // Create claims var claims = new List { new (ClaimTypes.NameIdentifier, user.Id.ToString()), new (ClaimTypes.Name, user.Username), new (ClaimTypes.Surname, user.Name!), new (ClaimTypes.GivenName, user.Prename!), new (ClaimTypes.Email, user.Email!), }; // Create claimsIdentity var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); // Create authProperties var authProperties = new AuthenticationProperties { IsPersistent = true, AllowRefresh = true, ExpiresUtc = DateTime.Now.AddMinutes(180) }; // Sign in await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties); return Ok(); } catch (Exception ex) { _logger.LogError(ex, "Unexpected error occurred.\n{ErrorMessage}", ex.Message); return StatusCode(StatusCodes.Status500InternalServerError); } } /// /// Authentifiziert einen Benutzer und generiert ein JWT-Token. Das Token wird als HTTP-only-Cookie zurückgegeben. /// /// Benutzeranmeldedaten (Benutzername und Passwort). /// /// Gibt eine HTTP 200 oder 401. /// /// /// Sample request: /// /// POST /api/auth/form /// { /// "username": "MaxMustermann", /// "password": "Geheim123!" /// } /// /// /// Erfolgreiche Anmeldung. Gibt das JWT-Token im Antwortkörper oder als Cookie zurück, wenn 'cookie' wahr ist. /// Unbefugt. Ungültiger Benutzername oder Passwort. [ProducesResponseType(typeof(string), StatusCodes.Status200OK, "text/javascript")] [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] [AllowAnonymous] [HttpPost] [Route("form")] public async Task Login([FromForm] Login login) { return await Login(login, true); } /// /// Entfernt das Authentifizierungs-Cookie des Benutzers (AuthCookie) /// /// /// Gibt eine HTTP 200 oder 401. /// /// /// Sample request: /// /// POST /api/auth/logout /// /// /// Erfolgreich gelöscht, wenn der Benutzer ein berechtigtes Cookie hat. /// Wenn es kein zugelassenes Cookie gibt, wird „nicht zugelassen“ zurückgegeben. [ProducesResponseType(typeof(string), StatusCodes.Status200OK, "text/javascript")] [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] [Authorize] [HttpPost("logout")] public async Task Logout() { try { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return Ok(); } catch (Exception ex) { _logger.LogError(ex, "Unexpected error occurred.\n{ErrorMessage}", ex.Message); return StatusCode(StatusCodes.Status500InternalServerError); } } /// /// Prüft, ob der Benutzer ein autorisiertes Token hat. /// /// Wenn ein autorisiertes Token vorhanden ist HTTP 200 asynchron 401 /// /// Sample request: /// /// GET /api/auth /// /// /// Wenn es einen autorisierten Cookie gibt. /// Wenn kein Cookie vorhanden ist oder nicht autorisierte. [ProducesResponseType(typeof(string), StatusCodes.Status200OK, "text/javascript")] [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] [Authorize] [HttpGet] public IActionResult IsAuthenticated() => Ok(); } }