diff --git a/EnvelopeGenerator.GeneratorAPI/Controllers/TfaRegistrationController.cs b/EnvelopeGenerator.GeneratorAPI/Controllers/TfaRegistrationController.cs new file mode 100644 index 00000000..607b6c8e --- /dev/null +++ b/EnvelopeGenerator.GeneratorAPI/Controllers/TfaRegistrationController.cs @@ -0,0 +1,130 @@ +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.GeneratorAPI.Extensions; +using EnvelopeGenerator.GeneratorAPI.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.GeneratorAPI.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() }); + } + } +}