using DigitalData.Auth.Claims; using EnvelopeGenerator.Application.Common.Interfaces.Services; using EnvelopeGenerator.Application.EnvelopeReceivers.Queries; using EnvelopeGenerator.Server.Controllers.Interfaces; using EnvelopeGenerator.Server.Models; using EnvelopeGenerator.Domain.Constants; using MediatR; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; namespace EnvelopeGenerator.Server.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( IOptions authTokenKeyOptions, IAuthorizationService authService) : ControllerBase, IAuthController { private readonly AuthTokenKeys authTokenKeys = authTokenKeyOptions.Value; /// /// /// public IAuthorizationService AuthService { get; } = authService; /// /// 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(void), StatusCodes.Status200OK)] [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] [Authorize(AuthenticationSchemes = AuthScheme.Sender)] [HttpPost("logout")] public IActionResult Logout() { Response.Cookies.Delete(authTokenKeys.Cookie); return Ok(); } /// /// 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(void), StatusCodes.Status200OK)] [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] [HttpGet("check")] [Authorize(AuthenticationSchemes = AuthScheme.Sender)] public IActionResult Check(string? role = null) => role is not null && !User.IsInRole(role) ? Unauthorized() : Ok(); /// /// Checks whether the caller holds a valid per-envelope receiver token for the given envelope key. /// The request must carry a cookie named AuthTokenSignFLOWReceiver.{envelopeKey}. /// /// The unique envelope key extracted from the route. /// Valid per-envelope token found. /// Token is missing, expired or invalid. [ProducesResponseType(typeof(void), StatusCodes.Status200OK)] [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] [Authorize(Policy = AuthPolicy.Receiver)] [HttpGet("check/envelope/{envelopeKey}")] public IActionResult CheckEnvelopeReceiver([FromRoute] string envelopeKey) => Ok(); /// /// Sends the receiver access code e-mail on the first envelope login-page visit. /// The operation is idempotent: if the code was already requested, it returns 200 without re-sending. /// /// Envelope receiver key. /// Access code is already available or requested successfully. /// Envelope receiver key was not found. /// Access code could not be persisted or e-mail could not be sent. [AllowAnonymous] [HttpPost("envelope-receiver/{key}/request-access-code")] public async Task RequestEnvelopeReceiverAccessCode( [FromRoute] string key, [FromServices] IMediator mediator, [FromServices] IEnvelopeHistoryService historyService, [FromServices] IEnvelopeMailService mailService, [FromServices] ILogger logger, CancellationToken cancel) { var envelopeReceiver = (await mediator.Send(new ReadEnvelopeReceiverQuery { Key = key }, cancel)).SingleOrDefault(); if (envelopeReceiver is null || envelopeReceiver.Envelope is null || envelopeReceiver.Receiver is null) return NotFound(); if (envelopeReceiver.Envelope.UseAccessCode != true) return Ok(); var userReference = envelopeReceiver.Receiver.EmailAddress; if (await historyService.IsSigned(envelopeReceiver.EnvelopeId, userReference)) return Ok(); var accessCodeAlreadyRequested = await historyService.AccessCodeAlreadyRequested(envelopeReceiver.EnvelopeId, userReference); if (accessCodeAlreadyRequested) return Ok(); var historyResult = await historyService.RecordAsync(envelopeReceiver.EnvelopeId, userReference, EnvelopeStatus.AccessCodeRequested); if (historyResult.IsFailed) { logger.LogWarning( "Failed to record AccessCodeRequested history for envelope {EnvelopeId} and receiver {ReceiverEmail}.", envelopeReceiver.EnvelopeId, userReference); return StatusCode(StatusCodes.Status500InternalServerError); } var mailResult = await mailService.SendAccessCodeAsync(envelopeReceiver); if (mailResult.IsFailed) { logger.LogWarning( "Failed to send access code mail for envelope {EnvelopeId} and receiver {ReceiverEmail}.", envelopeReceiver.EnvelopeId, userReference); return StatusCode(StatusCodes.Status500InternalServerError); } return Ok(); } /// /// Removes the per-envelope receiver cookie for the given envelope key. /// /// The unique envelope key whose cookie should be deleted. /// Cookie successfully deleted. [ProducesResponseType(typeof(void), StatusCodes.Status200OK)] [HttpPost("logout/envelope/{envelopeKey}")] public IActionResult LogoutEnvelopeReceiver([FromRoute] string envelopeKey) { var cookieName = CookieNames.GetEnvelopeReceiverCookieName(authTokenKeys.Cookie, envelopeKey); Response.Cookies.Delete(cookieName); return Ok(); } /// /// Removes all per-envelope receiver cookies from the current request. /// /// All envelope receiver cookies successfully deleted. [ProducesResponseType(typeof(void), StatusCodes.Status200OK)] [HttpPost("logout/envelope")] public IActionResult LogoutAllEnvelopeReceivers() { foreach (var cookieName in Request.Cookies.Keys.Where(k => CookieNames.IsEnvelopeReceiverCookie(k, authTokenKeys.Cookie))) Response.Cookies.Delete(cookieName); return Ok(); } }