Compare commits

...

6 Commits

Author SHA1 Message Date
Developer 02
ee49538f1e feat(Receiver): EnvelopeReceiver-Eigenschaft zu Entität, ReadDto und Updated Dto hinzugefügt. 2025-02-05 17:17:44 +01:00
Developer 02
311009bc97 feat(Reg): CSS zu .tfaQrCode hinzugefügt 2025-02-05 16:48:11 +01:00
Developer 02
f5028a82fa feat(Reg View): TFA-Registrierungsschritt erstellt 2025-02-05 16:12:42 +01:00
Developer 02
07d70dbd22 feat(TFARegController): QR-Code zu RegView hinzugefügt. 2025-02-05 13:42:55 +01:00
Developer 02
152050ebf4 feat(ViewControllerBase): Erstellt, um allgemeine Eigenschaften von ViewControllern zu behandeln.
- Implementiert in TFARegController.
 - Implementiert in HomeController.
2025-02-05 12:58:30 +01:00
Developer 02
e27daa4b90 feat(TFARegController): Initialisiert den MVC-Controller und den View zur Bearbeitung der TFA-Registrierung. 2025-02-05 11:32:34 +01:00
9 changed files with 687 additions and 534 deletions

View File

@@ -4,23 +4,24 @@ using DigitalData.EmailProfilerDispatcher.Abstraction.Attributes;
using EnvelopeGenerator.Application.DTOs.EnvelopeReceiver;
using System.Text.Json.Serialization;
namespace EnvelopeGenerator.Application.DTOs.Receiver
namespace EnvelopeGenerator.Application.DTOs.Receiver;
public record ReceiverReadDto(
int Id,
string EmailAddress,
string Signature,
DateTime AddedWhen
) : BaseDTO<int>(Id), IUnique<int>
{
public record ReceiverReadDto(
int Id,
string EmailAddress,
string Signature,
DateTime AddedWhen
) : BaseDTO<int>(Id), IUnique<int>
{
[JsonIgnore]
public IEnumerable<EnvelopeReceiverBasicDto>? EnvelopeReceivers { get; init; }
[JsonIgnore]
public IEnumerable<EnvelopeReceiverBasicDto>? EnvelopeReceivers { get; init; }
public string? LastUsedName => EnvelopeReceivers?.LastOrDefault()?.Name;
public string? LastUsedName => EnvelopeReceivers?.LastOrDefault()?.Name;
public string? TotpSecretkey { get; set; } = null;
public string? TotpSecretkey { get; set; } = null;
[TemplatePlaceholder("[TFA_QR_EXPIRATION]")]
public DateTime? TotpExpiration { get; set; } = null;
};
}
[TemplatePlaceholder("[TFA_QR_EXPIRATION]")]
public DateTime? TotpExpiration { get; set; } = null;
public DateTime? TfaRegDeadline { get; set; }
};

View File

@@ -1,6 +1,5 @@
using DigitalData.Core.Abstractions;
namespace EnvelopeGenerator.Application.DTOs.Receiver
{
public record ReceiverUpdateDto(int Id, string? TotpSecretkey = null, DateTime? TotpExpiration = null) : IUnique<int>;
}
namespace EnvelopeGenerator.Application.DTOs.Receiver;
public record ReceiverUpdateDto(int Id, string? TotpSecretkey = null, DateTime? TotpExpiration = null, DateTime? TfaRegDeadline = null) : IUnique<int>;

View File

@@ -2,34 +2,36 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace EnvelopeGenerator.Domain.Entities
namespace EnvelopeGenerator.Domain.Entities;
[Table("TBSIG_RECEIVER", Schema = "dbo")]
public class Receiver : IUnique<int>
{
[Table("TBSIG_RECEIVER", Schema = "dbo")]
public class Receiver : IUnique<int>
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("GUID")]
public int Id { get; set; }
[Required, EmailAddress]
[Column("EMAIL_ADDRESS", TypeName = "nvarchar(128)")]
public required string EmailAddress { get; set; }
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("GUID")]
public int Id { get; set; }
[Required, EmailAddress]
[Column("EMAIL_ADDRESS", TypeName = "nvarchar(128)")]
public required string EmailAddress { get; set; }
[Required]
[Column("SIGNATURE", TypeName = "nvarchar(64)")]
public required string Signature { get; set; }
[Required]
[Column("SIGNATURE", TypeName = "nvarchar(64)")]
public required string Signature { get; set; }
[Required]
[Column("ADDED_WHEN", TypeName = "datetime")]
public DateTime AddedWhen { get; set; }
[Required]
[Column("ADDED_WHEN", TypeName = "datetime")]
public DateTime AddedWhen { get; set; }
[Column("TOTP_SECRET_KEY", TypeName = "nvarchar(MAX)")]
public string? TotpSecretkey { get; set; }
[Column("TOTP_SECRET_KEY", TypeName = "nvarchar(MAX)")]
public string? TotpSecretkey { get; set; }
[Column("TOTP_EXPIRATION", TypeName = "datetime")]
public DateTime? TotpExpiration { get; set; }
[Column("TOTP_EXPIRATION", TypeName = "datetime")]
public DateTime? TotpExpiration { get; set; }
public IEnumerable<EnvelopeReceiver>? EnvelopeReceivers { get; init; }
}
[Column("TFA_REG_DEADLINE", TypeName = "datetime")]
public DateTime? TfaRegDeadline { get; set; }
public IEnumerable<EnvelopeReceiver>? EnvelopeReceivers { get; init; }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Web.Models;
using Ganss.Xss;
using Microsoft.AspNetCore.Mvc;
using EnvelopeGenerator.Extensions;
using Microsoft.Extensions.Localization;
using EnvelopeGenerator.Application.Resources;
using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.Extensions;
namespace EnvelopeGenerator.Web.Controllers;
//TODO: Add authorization as well as limiting the link duration (intermediate token with different role) or sign it
[Route("tfa")]
public class TFARegController : ViewControllerBase
{
private readonly IEnvelopeReceiverService _envRcvService;
private readonly IAuthenticator _authenticator;
private readonly IReceiverService _rcvService;
public TFARegController(ILogger<TFARegController> logger, HtmlSanitizer sanitizer, Cultures cultures, IStringLocalizer<Resource> localizer, IEnvelopeReceiverService erService, IAuthenticator authenticator, IReceiverService receiverService) : base(logger, sanitizer, cultures, localizer)
{
_envRcvService = erService;
_authenticator = authenticator;
_rcvService = receiverService;
}
[HttpGet("{envelopeReceiverId}")]
public async Task<IActionResult> Reg(string envelopeReceiverId)
{
envelopeReceiverId = _sanitizer.Sanitize(envelopeReceiverId);
(string? uuid, string? signature) = envelopeReceiverId.DecodeEnvelopeReceiverId();
if (uuid is null || signature is null)
{
_logger.LogEnvelopeError(uuid: uuid, signature: signature, message: _localizer[WebKey.WrongEnvelopeReceiverId]);
return Unauthorized();
}
var er_secret_res = await _envRcvService.ReadWithSecretByUuidSignatureAsync(uuid: uuid, signature: signature);
if (er_secret_res.IsFailed)
{
_logger.LogNotice(er_secret_res.Notices);
return this.ViewEnvelopeNotFound();
}
var er_secret = er_secret_res.Data;
if (!er_secret.Envelope!.TFAEnabled)
return Unauthorized();
var rcv = er_secret.Receiver;
// Generate QR code as base 64
rcv!.TotpSecretkey = _authenticator.GenerateTotpSecretKey();
rcv.TotpExpiration = DateTime.Now.AddMonths(1);
await _rcvService.UpdateAsync(rcv);
var totp_qr_64 = _authenticator.GenerateTotpQrCode(userEmail: rcv.EmailAddress, secretKey: rcv.TotpSecretkey).ToBase64String();
ViewData["TotpQR64"] = totp_qr_64;
return View();
}
}

View File

@@ -0,0 +1,23 @@
using EnvelopeGenerator.Web.Models;
using Ganss.Xss;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using EnvelopeGenerator.Application.Resources;
namespace EnvelopeGenerator.Web.Controllers;
public class ViewControllerBase : Controller
{
protected readonly ILogger _logger;
protected readonly HtmlSanitizer _sanitizer;
protected readonly Cultures _cultures;
protected readonly IStringLocalizer<Resource> _localizer;
public ViewControllerBase(ILogger logger, HtmlSanitizer sanitizer, Cultures cultures, IStringLocalizer<Resource> localizer)
{
_logger = logger;
_sanitizer = sanitizer;
_cultures = cultures;
_localizer = localizer;
}
}

View File

@@ -0,0 +1,70 @@
@{
ViewData["Title"] = "2FA Registrierung";
var totpQR64 = ViewData["TotpQR64"] as string;
}
<div class="page container p-5">
<header class="text-center">
<div class="icon locked mt-4 mb-1">
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="72" fill="currentColor" class="bi bi-shield-lock" viewBox="0 0 16 16">
<path d="M5.338 1.59a61 61 0 0 0-2.837.856.48.48 0 0 0-.328.39c-.554 4.157.726 7.19 2.253 9.188a10.7 10.7 0 0 0 2.287 2.233c.346.244.652.42.893.533q.18.085.293.118a1 1 0 0 0 .101.025 1 1 0 0 0 .1-.025q.114-.034.294-.118c.24-.113.547-.29.893-.533a10.7 10.7 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067c-.53 0-1.552.223-2.662.524zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.8 11.8 0 0 1-2.517 2.453 7 7 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7 7 0 0 1-1.048-.625 11.8 11.8 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 63 63 0 0 1 5.072.56" />
<path d="M9.5 6.5a1.5 1.5 0 0 1-1 1.415l.385 1.99a.5.5 0 0 1-.491.595h-.788a.5.5 0 0 1-.49-.595l.384-1.99a1.5 1.5 0 1 1 2-1.415" />
</svg>
</div>
<h2 class="mb-0">2-Factor Authentication (2FA)</h2>
<h2>Registrierung</h2>
</header>
<section class="text-start mt-4">
<div class="accordion" id="tfaRegStep">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
<p class="fw-bolder">Schritt 1 - Download einer 2FA Applikation</p>
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" data-bs-parent="#tfaRegStep">
<div class="accordion-body">
<p class="text-wrap fw-medium">Bitte nehmen Sie Ihr Smartphone zur Hand und laden eine Applikation herunter, die zur Zwei-Faktor-Authentifizierung (2FA) benutzt werden kann.</p>
<p class="text-wrap fw-light">Folgende Applikationen empfehlen wir</p>
<ul class="list-group text-start">
<li class="list-group-item">
<a href="https://support.google.com/accounts/answer/1066447?hl=de&co=GENIE.Platform%3DAndroid" target="_blank" style="text-decoration: none;">
<samp>Google Authenticator</samp>
</a>
</li>
<li class="list-group-item">
<a href="https://support.microsoft.com/de-de/account-billing/microsoft-authenticator-herunterladen-351498fc-850a-45da-b7b6-27e523b8702a" target="_blank" style="text-decoration: none;">
<samp>Microsoft Authenticator</samp>
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
<p class="fw-bolder">Schritt 2 - Scannen des QR-Codes</p>
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#tfaRegStep">
<div class="accordion-body">
<div class="text-center m-0 p-0"><img class="tfaQrCode" src="data:image/png;base64,@totpQR64"></div>
<p class="text-wrap fw-medium">Sobald Sie eine Zwei-Faktor-Authentifizierung App installiert haben, können Sie fortfahren und innerhalb der Applikation die Option zum Scannen eines QR-Codes suchen und bestätigen. Im Anschluss, sobald die Kamera freigegeben wurde, können Sie den QR-Code von uns scannen.</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
<p class="fw-bolder">Schritt 3 - Verifizierung des Codes</p>
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" data-bs-parent="#tfaRegStep">
<div class="accordion-body">
<p class="text-wrap fw-medium">Sie können nun in der Zwei-Faktor-Authentifizierung App einen Zahlencode zur Verifizierung des Vorganges ablesen. Bitte tragen Sie diesen Code in das unten aufgeführte Eingabefeld ein und Klicken auf <samp>Absenden</samp>.</p>
</div>
</div>
</div>
</div>
</section>
</div>

View File

@@ -618,4 +618,8 @@ footer#page-footer {
.collapse {
height: 4rem;
}
}
#tfaRegStep .tfaQrCode {
width: 7rem;
}

File diff suppressed because one or more lines are too long