using DigitalData.Core.Abstraction.Application.DTO; using EnvelopeGenerator.Application.Common.Extensions; using EnvelopeGenerator.Application.Common.Interfaces.Services; using EnvelopeGenerator.Application.Resources; using EnvelopeGenerator.Domain.Constants; using EnvelopeGenerator.API.Models; using Ganss.Xss; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; namespace EnvelopeGenerator.API.Controllers; /// /// Exposes endpoints for registering and managing two-factor authentication for envelope receivers. /// [ApiController] [Route("api/tfa")] public class TfaRegistrationController : ControllerBase { private readonly ILogger _logger; private readonly IEnvelopeReceiverService _envelopeReceiverService; private readonly IAuthenticator _authenticator; private readonly IReceiverService _receiverService; private readonly TFARegParams _parameters; private readonly IStringLocalizer _localizer; /// /// Initializes a new instance of the class. /// public TfaRegistrationController( ILogger logger, IEnvelopeReceiverService envelopeReceiverService, IAuthenticator authenticator, IReceiverService receiverService, IOptions tfaRegParamsOptions, IStringLocalizer localizer) { _logger = logger; _envelopeReceiverService = envelopeReceiverService; _authenticator = authenticator; _receiverService = receiverService; _parameters = tfaRegParamsOptions.Value; _localizer = localizer; } /// /// Generates registration metadata (QR code and deadline) for a receiver. /// /// Encoded envelope receiver id. [Authorize] [HttpGet("{envelopeReceiverId}")] public async Task RegisterAsync(string envelopeReceiverId) { try { var (uuid, signature) = envelopeReceiverId.DecodeEnvelopeReceiverId(); if (uuid is null || signature is null) { _logger.LogEnvelopeError(uuid: uuid, signature: signature, message: _localizer.WrongEnvelopeReceiverId()); return Unauthorized(new { message = _localizer.WrongEnvelopeReceiverId() }); } var secretResult = await _envelopeReceiverService.ReadWithSecretByUuidSignatureAsync(uuid: uuid, signature: signature); if (secretResult.IsFailed) { _logger.LogNotice(secretResult.Notices); return NotFound(new { message = _localizer.WrongEnvelopeReceiverId() }); } var envelopeReceiver = secretResult.Data; if (!envelopeReceiver.Envelope!.TFAEnabled) return Unauthorized(new { message = _localizer.WrongAccessCode() }); var receiver = envelopeReceiver.Receiver; receiver!.TotpSecretkey = _authenticator.GenerateTotpSecretKey(); await _receiverService.UpdateAsync(receiver); var totpQr64 = _authenticator.GenerateTotpQrCode(userEmail: receiver.EmailAddress, secretKey: receiver.TotpSecretkey).ToBase64String(); if (receiver.TfaRegDeadline is null) { receiver.TfaRegDeadline = _parameters.Deadline; await _receiverService.UpdateAsync(receiver); } else if (receiver.TfaRegDeadline <= DateTime.Now) { return StatusCode(StatusCodes.Status410Gone, new { message = _localizer.WrongAccessCode() }); } return Ok(new { envelopeReceiver.EnvelopeId, envelopeReceiver.Envelope!.Uuid, envelopeReceiver.Receiver!.Signature, receiver.TfaRegDeadline, TotpQR64 = totpQr64 }); } catch (Exception ex) { _logger.LogEnvelopeError(envelopeReceiverId: envelopeReceiverId, exception: ex, message: _localizer.WrongEnvelopeReceiverId()); return StatusCode(StatusCodes.Status500InternalServerError, new { message = _localizer.UnexpectedError() }); } } /// /// Logs out the envelope receiver from cookie authentication. /// [Authorize(Roles = ReceiverRole.FullyAuth)] [HttpPost("auth/logout")] public async Task LogOutAsync() { try { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return Ok(); } catch (Exception ex) { _logger.LogError(ex, "{message}", ex.Message); return StatusCode(StatusCodes.Status500InternalServerError, new { message = _localizer.UnexpectedError() }); } } }