Compare commits

...

16 Commits

Author SHA1 Message Date
Developer 02
3688373481 fix(_Layout): sanitzer entfernt, um Json-Deserilisationsfehler zu vermeiden. 2025-02-11 13:43:01 +01:00
Developer 02
b8fbeee322 fix(HomeController): Zugehörige Datenbindungen hinzugefügt 2025-02-10 17:17:11 +01:00
Developer 02
57e4dfb3fb feat(EnvelopeLocked): Link zum Senden von E-Mails zu LockedFooterBodyAccess hinzugefügt.
- Gebunden mit Modell.
2025-02-10 16:58:57 +01:00
Developer 02
afc8d3baf0 chore: Upgegradet auf 2.10.4
- Resx-Felder aktualisiert.
2025-02-10 15:23:51 +01:00
Developer 02
51d77367ca fix(DIExtensions): aktualisiert, um den richtigen Abschnittsnamen einzugeben.
- ConfigureByTypeName entfernt
2025-02-10 15:05:46 +01:00
Developer 02
614f3768d9 chore(EnvelopeGenerator): Hochgestuft auf 2.10.3 2025-02-10 13:08:57 +01:00
Developer 02
5f780f8d1e fix(EnvelopeSmsHandler): Korrekte Ablaufprüfung und Cache-Aktualisierung in SendTotpAsync
- Die Bedingung für die Überprüfung des Ablaufs wurde korrigiert, so dass sie korrekt null zurückgibt, wenn der gespeicherte Ablauf in der Zukunft liegt.
- Fehlende Cache-Aktualisierung zur Speicherung des neuen Verfallsdatums nach dem Versand der TOTP-SMS hinzugefügt.
2025-02-10 11:48:36 +01:00
Developer 02
20825aa3ea feat(HomeController): Rollenprüfung für 2FA hinzugefügt
- wenn der Benutzer keine PreAuth Rolle hat, wird Status401Unauthorized zurückgegeben
2025-02-10 11:18:54 +01:00
Developer 02
c5b508d274 chore(EnvelopeGenerator): Hochgestuft auf 2.10.2 2025-02-07 14:49:39 +01:00
Developer 02
4eec4451b2 feat(TFARegController): Authentifizierungsbedingung zum Registrierungsendpunkt hinzugefügt 2025-02-07 13:31:54 +01:00
Developer 02
ca4718e159 feat(ControllerBaseExtensions): Erstellte Erweiterungsmethode zum Login über HttpContext mit Umschlag Empfänger und Rolle.
- Implementiert in HomeController
2025-02-07 13:12:27 +01:00
Developer 02
33fcb5b70e refactor(Controllers): FullyAuth-Rollenbedingung für jedes bestehende Auth-Attribut hinzugefügt, um die Autorisierung in Stufen aufzuteilen. 2025-02-07 10:53:17 +01:00
Developer 02
82d8521a25 feat(Constants): Erstellen von Konstanten für die Empfängerrolle, um die Authentifizierungsschritte des Empfängers zu trennen 2025-02-07 09:47:32 +01:00
Developer 02
2f9d07312b chore(Web): Hochgestuft auf 2.10.0 2025-02-06 19:41:47 +01:00
Developer 02
fa36593b26 refactor(Receiver): Entfernt TotpExpiration aus allen DTOs und Entitäten. 2025-02-06 19:41:11 +01:00
Developer 02
9cdb1409c0 feat(TFARegController): Try-Catch zur Methode reg'e hinzugefügt.
- Ausnahme ist so eingestellt, dass sie protokolliert wird.
2025-02-06 19:31:50 +01:00
20 changed files with 204 additions and 181 deletions

View File

@@ -4,7 +4,7 @@ using System.Text;
namespace EnvelopeGenerator.Application.DTOs.Receiver namespace EnvelopeGenerator.Application.DTOs.Receiver
{ {
public record ReceiverCreateDto([EmailAddress] string EmailAddress, string? TotpSecretkey = null, DateTime? TotpExpiration = null) public record ReceiverCreateDto([EmailAddress] string EmailAddress, string? TotpSecretkey = null)
{ {
public string Signature => sha256HexOfMail.Value; public string Signature => sha256HexOfMail.Value;

View File

@@ -20,8 +20,5 @@ public record ReceiverReadDto(
public string? TotpSecretkey { get; set; } = null; public string? TotpSecretkey { get; set; } = null;
[TemplatePlaceholder("[TFA_QR_EXPIRATION]")]
public DateTime? TotpExpiration { get; set; } = null;
public DateTime? TfaRegDeadline { get; set; } public DateTime? TfaRegDeadline { get; set; }
}; };

View File

@@ -2,4 +2,4 @@
namespace EnvelopeGenerator.Application.DTOs.Receiver; namespace EnvelopeGenerator.Application.DTOs.Receiver;
public record ReceiverUpdateDto(int Id, string? TotpSecretkey = null, DateTime? TotpExpiration = null, DateTime? TfaRegDeadline = null) : IUnique<int>; public record ReceiverUpdateDto(int Id, string? TotpSecretkey = null, DateTime? TfaRegDeadline = null) : IUnique<int>;

View File

@@ -11,64 +11,59 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using DigitalData.Core.Client; using DigitalData.Core.Client;
using QRCoder; using QRCoder;
namespace EnvelopeGenerator.Application.Extensions namespace EnvelopeGenerator.Application.Extensions;
public static class DIExtensions
{ {
public static class DIExtensions public static IServiceCollection AddEnvelopeGenerator(this IServiceCollection services, IConfiguration config)
{ {
public static IServiceCollection AddEnvelopeGenerator(this IServiceCollection services, IConfiguration config) //Inject CRUD Service and repositoriesad
{ services.TryAddScoped<IConfigRepository, ConfigRepository>();
//Inject CRUD Service and repositoriesad services.TryAddScoped<IDocumentReceiverElementRepository, DocumentReceiverElementRepository>();
services.TryAddScoped<IConfigRepository, ConfigRepository>(); services.TryAddScoped<IEnvelopeDocumentRepository, EnvelopeDocumentRepository>();
services.TryAddScoped<IDocumentReceiverElementRepository, DocumentReceiverElementRepository>(); services.TryAddScoped<IConfigRepository, ConfigRepository>();
services.TryAddScoped<IEnvelopeDocumentRepository, EnvelopeDocumentRepository>(); services.TryAddScoped<IDocumentReceiverElementRepository, DocumentReceiverElementRepository>();
services.TryAddScoped<IConfigRepository, ConfigRepository>(); services.TryAddScoped<IDocumentStatusRepository, DocumentStatusRepository>();
services.TryAddScoped<IDocumentReceiverElementRepository, DocumentReceiverElementRepository>(); services.TryAddScoped<IEmailTemplateRepository, EmailTemplateRepository>();
services.TryAddScoped<IDocumentStatusRepository, DocumentStatusRepository>(); services.TryAddScoped<IEnvelopeRepository, EnvelopeRepository>();
services.TryAddScoped<IEmailTemplateRepository, EmailTemplateRepository>(); services.TryAddScoped<IEnvelopeCertificateRepository, EnvelopeCertificateRepository>();
services.TryAddScoped<IEnvelopeRepository, EnvelopeRepository>(); services.TryAddScoped<IEnvelopeDocumentRepository, EnvelopeDocumentRepository>();
services.TryAddScoped<IEnvelopeCertificateRepository, EnvelopeCertificateRepository>(); services.TryAddScoped<IEnvelopeHistoryRepository, EnvelopeHistoryRepository>();
services.TryAddScoped<IEnvelopeDocumentRepository, EnvelopeDocumentRepository>(); services.TryAddScoped<IEnvelopeReceiverRepository, EnvelopeReceiverRepository>();
services.TryAddScoped<IEnvelopeHistoryRepository, EnvelopeHistoryRepository>(); services.TryAddScoped<IEnvelopeTypeRepository, EnvelopeTypeRepository>();
services.TryAddScoped<IEnvelopeReceiverRepository, EnvelopeReceiverRepository>(); services.TryAddScoped<IReceiverRepository, ReceiverRepository>();
services.TryAddScoped<IEnvelopeTypeRepository, EnvelopeTypeRepository>(); services.TryAddScoped<IUserReceiverRepository, UserReceiverRepository>();
services.TryAddScoped<IReceiverRepository, ReceiverRepository>(); services.TryAddScoped<IEnvelopeReceiverReadOnlyRepository, EnvelopeReceiverReadOnlyRepository>();
services.TryAddScoped<IUserReceiverRepository, UserReceiverRepository>(); services.TryAddScoped<IConfigService, ConfigService>();
services.TryAddScoped<IEnvelopeReceiverReadOnlyRepository, EnvelopeReceiverReadOnlyRepository>(); services.TryAddScoped<IDocumentReceiverElementService, DocumentReceiverElementService>();
services.TryAddScoped<IConfigService, ConfigService>(); services.TryAddScoped<IEnvelopeDocumentService, EnvelopeDocumentService>();
services.TryAddScoped<IDocumentReceiverElementService, DocumentReceiverElementService>(); services.TryAddScoped<IEnvelopeHistoryService, EnvelopeHistoryService>();
services.TryAddScoped<IEnvelopeDocumentService, EnvelopeDocumentService>(); services.TryAddScoped<IDocumentStatusService, DocumentStatusService>();
services.TryAddScoped<IEnvelopeHistoryService, EnvelopeHistoryService>(); services.TryAddScoped<IEmailTemplateService, EmailTemplateService>();
services.TryAddScoped<IDocumentStatusService, DocumentStatusService>(); services.TryAddScoped<IEnvelopeService, EnvelopeService>();
services.TryAddScoped<IEmailTemplateService, EmailTemplateService>(); services.TryAddScoped<IEnvelopeCertificateService, EnvelopeCertificateService>();
services.TryAddScoped<IEnvelopeService, EnvelopeService>(); services.TryAddScoped<IEnvelopeDocumentService, EnvelopeDocumentService>();
services.TryAddScoped<IEnvelopeCertificateService, EnvelopeCertificateService>(); services.TryAddScoped<IEnvelopeReceiverService, EnvelopeReceiverService>();
services.TryAddScoped<IEnvelopeDocumentService, EnvelopeDocumentService>(); services.TryAddScoped<IEnvelopeTypeService, EnvelopeTypeService>();
services.TryAddScoped<IEnvelopeReceiverService, EnvelopeReceiverService>(); services.TryAddScoped<IReceiverService, ReceiverService>();
services.TryAddScoped<IEnvelopeTypeService, EnvelopeTypeService>(); services.TryAddScoped<IUserReceiverService, UserReceiverService>();
services.TryAddScoped<IReceiverService, ReceiverService>(); services.TryAddScoped<IEnvelopeReceiverReadOnlyService, EnvelopeReceiverReadOnlyService>();
services.TryAddScoped<IUserReceiverService, UserReceiverService>();
services.TryAddScoped<IEnvelopeReceiverReadOnlyService, EnvelopeReceiverReadOnlyService>();
//Auto mapping profiles //Auto mapping profiles
services.AddAutoMapper(typeof(BasicDtoMappingProfile).Assembly); services.AddAutoMapper(typeof(BasicDtoMappingProfile).Assembly);
services.AddAutoMapper(typeof(UserMappingProfile).Assembly); services.AddAutoMapper(typeof(UserMappingProfile).Assembly);
services.ConfigureByTypeName<DispatcherParams>(config); services.Configure<DispatcherParams>(config.GetSection(nameof(DispatcherParams)));
services.ConfigureByTypeName<MailParams>(config); services.Configure<MailParams>(config.GetSection(nameof(MailParams)));
services.ConfigureByTypeName<AuthenticatorParams>(config); services.Configure<AuthenticatorParams>(config.GetSection(nameof(AuthenticatorParams)));
services.ConfigureByTypeName<TotpSmsParams>(config); services.Configure<TotpSmsParams>(config.GetSection(nameof(TotpSmsParams)));
services.AddHttpClientService<GtxMessagingParams>(config.GetSection(nameof(GtxMessagingParams))); services.AddHttpClientService<GtxMessagingParams>(config.GetSection(nameof(GtxMessagingParams)));
services.TryAddSingleton<ISmsSender, GTXSmsSender>(); services.TryAddSingleton<ISmsSender, GTXSmsSender>();
services.TryAddSingleton<IEnvelopeSmsHandler, EnvelopeSmsHandler>(); services.TryAddSingleton<IEnvelopeSmsHandler, EnvelopeSmsHandler>();
services.TryAddSingleton<IAuthenticator, Authenticator>(); services.TryAddSingleton<IAuthenticator, Authenticator>();
services.TryAddSingleton<QRCodeGenerator>(); services.TryAddSingleton<QRCodeGenerator>();
return services; return services;
}
//TODO: move to DigitalData.Core
private static IServiceCollection ConfigureByTypeName<TOptions>(this IServiceCollection services, IConfiguration configuration) where TOptions : class
=> services.Configure<TOptions>(configuration.GetSection(nameof(TOptions)));
} }
} }

View File

@@ -1,22 +0,0 @@
using EnvelopeGenerator.Application.DTOs.Receiver;
using EnvelopeGenerator.Extensions;
using Newtonsoft.Json;
namespace EnvelopeGenerator.Application.Extensions
{
public static class DTOExtensions
{
public static bool IsTotpSecretExpired(this ReceiverReadDto dto, int minutesBeforeExpiration = 30)
=> dto.TotpExpiration < DateTime.Now.AddMinutes(minutesBeforeExpiration * -1);
public static bool IsTotpSecretInvalid(this ReceiverReadDto dto, int minutesBeforeExpiration = 30)
=> dto.IsTotpSecretExpired(minutesBeforeExpiration) || dto.TotpSecretkey is null;
public static bool IsTotpSecretValid(this ReceiverReadDto dto, int minutesBeforeExpiration = 30)
=> !dto.IsTotpSecretInvalid(minutesBeforeExpiration);
public static bool IsTotpValid(this ReceiverReadDto dto, string totp) => dto.TotpSecretkey is null ? throw new ArgumentNullException(nameof(dto), $"TotpSecretkey of DTO cannot validate without TotpSecretkey. Dto: {JsonConvert.SerializeObject(dto)}") : totp.IsValidTotp(dto.TotpSecretkey);
public static bool IsTotpInvalid(this ReceiverReadDto dto, string totp) => !dto.IsTotpValid(totp: totp);
}
}

View File

@@ -163,7 +163,7 @@
<value>Bitte überprüfen Sie die Standortinformationen. Wenn sie falsch sind, korrigieren Sie diese bitte.</value> <value>Bitte überprüfen Sie die Standortinformationen. Wenn sie falsch sind, korrigieren Sie diese bitte.</value>
</data> </data>
<data name="LockedBodyAccess" xml:space="preserve"> <data name="LockedBodyAccess" xml:space="preserve">
<value>Wir haben Ihnen gerade den Zugriffscode an die hinterlegte Email Adresse gesendet. Dies kann evtl. einige Minuten dauern.</value> <value>Wir senden Ihnen nun einen Zugriffscode an Ihre hinterlegte Email-Adresse. Dies kann evtl. einige Minuten dauern!</value>
</data> </data>
<data name="LockedBodyAuthenticator" xml:space="preserve"> <data name="LockedBodyAuthenticator" xml:space="preserve">
<value>Bitte geben Sie den in Ihrer Authenticator-App angegebenen TOTP-Code ein.</value> <value>Bitte geben Sie den in Ihrer Authenticator-App angegebenen TOTP-Code ein.</value>
@@ -184,7 +184,7 @@
<value>SMS-Code</value> <value>SMS-Code</value>
</data> </data>
<data name="LockedFooterBodyAccess" xml:space="preserve"> <data name="LockedFooterBodyAccess" xml:space="preserve">
<value>Bitte überprüfen Sie Ihr Email Postfach inklusive Spam-Ordner. Sie können auch den Absender bitten, Ihnen den Code auf anderem Wege zukommen zu lassen.</value> <value>Bitte überprüfen Sie Ihr Email Postfach inklusive Spam-Ordner. Sie können auch den Absender &lt;a class="mail-link" href="mailto:{0}?subject={1}&amp;body={2}" target="_blank"&gt;{0}&lt;/a&gt; bitten, Ihnen den Code auf anderem Wege zukommen zu lassen.</value>
</data> </data>
<data name="LockedFooterBodyAuthenticator" xml:space="preserve"> <data name="LockedFooterBodyAuthenticator" xml:space="preserve">
<value>Der neue QR-Code wird nur einmal für einen bestimmten Zeitraum gesendet und nach dem Scannen in Ihrer Authenticator-App gespeichert. Er kann für alle Umschläge verwendet werden, die an dieselbe E-Mail-Adresse gesendet werden, bis er abläuft. Wenn Sie die QR-Code-Mail nicht erhalten oder sie sowohl aus der Mail als auch aus authenticator löschen, kontaktieren Sie bitte den Absender.</value> <value>Der neue QR-Code wird nur einmal für einen bestimmten Zeitraum gesendet und nach dem Scannen in Ihrer Authenticator-App gespeichert. Er kann für alle Umschläge verwendet werden, die an dieselbe E-Mail-Adresse gesendet werden, bis er abläuft. Wenn Sie die QR-Code-Mail nicht erhalten oder sie sowohl aus der Mail als auch aus authenticator löschen, kontaktieren Sie bitte den Absender.</value>

View File

@@ -163,7 +163,7 @@
<value>Please review the location information. If it is incorrect, kindly make the necessary corrections.</value> <value>Please review the location information. If it is incorrect, kindly make the necessary corrections.</value>
</data> </data>
<data name="LockedBodyAccess" xml:space="preserve"> <data name="LockedBodyAccess" xml:space="preserve">
<value>We have just sent you the access code to the email address you provided. This may take a few minutes.</value> <value>We will now send you an access code to your registered e-mail address. This may take a few minutes!</value>
</data> </data>
<data name="LockedBodyAuthenticator" xml:space="preserve"> <data name="LockedBodyAuthenticator" xml:space="preserve">
<value>Please enter the TOTP provided in your Authenticator app.</value> <value>Please enter the TOTP provided in your Authenticator app.</value>
@@ -184,7 +184,7 @@
<value>SMS Code</value> <value>SMS Code</value>
</data> </data>
<data name="LockedFooterBodyAccess" xml:space="preserve"> <data name="LockedFooterBodyAccess" xml:space="preserve">
<value>Please check your email inbox including your spam folder. Furthermore, you can also ask the sender to send the code by other means.</value> <value>Please check your email inbox including the spam folder. You can also ask the sender &lt;a class="mail-link" href="mailto:{0}?subject={1}&amp;body={2}" target="_blank"&gt;{0}&lt;/a&gt; to send you the code by other means.</value>
</data> </data>
<data name="LockedFooterBodyAuthenticator" xml:space="preserve"> <data name="LockedFooterBodyAuthenticator" xml:space="preserve">
<value>The new QR code is sent only once for a given period and is saved in your authenticator app once scanned. It can be used for all envelopes received at the same email address until it expires. If you do not receive the QR code mail or delete it both from the mail and from authenticator, please contact the sender.</value> <value>The new QR code is sent only once for a given period and is saved in your authenticator app once scanned. It can be used for all envelopes received at the same email address until it expires. If you do not receive the QR code mail or delete it both from the mail and from authenticator, please contact the sender.</value>

View File

@@ -164,14 +164,11 @@ namespace EnvelopeGenerator.Application.Services
throw new ArgumentNullException(nameof(dto), $"TFA Qr Code cannot sent. Receiver information is missing. Envelope receiver dto is {JsonConvert.SerializeObject(dto)}"); throw new ArgumentNullException(nameof(dto), $"TFA Qr Code cannot sent. Receiver information is missing. Envelope receiver dto is {JsonConvert.SerializeObject(dto)}");
if (dto.Receiver.TotpSecretkey is null) if (dto.Receiver.TotpSecretkey is null)
throw new ArgumentNullException(nameof(dto), $"TFA Qr Code cannot sent. Receiver.TotpSecretKey is null. Envelope receiver dto is {JsonConvert.SerializeObject(dto)}"); throw new ArgumentNullException(nameof(dto), $"TFA Qr Code cannot sent. Receiver.TotpSecretKey is null. Envelope receiver dto is {JsonConvert.SerializeObject(dto)}");
if (dto.Receiver.TotpExpiration is null)
throw new ArgumentNullException(nameof(dto), $"TFA Qr Code cannot sent. Receiver.TotpExpiration is null. Envelope receiver dto is {JsonConvert.SerializeObject(dto)}");
var totp_qr_64 = _authenticator.GenerateTotpQrCode(userEmail: dto.Receiver.EmailAddress, secretKey: dto.Receiver.TotpSecretkey).ToBase64String(); var totp_qr_64 = _authenticator.GenerateTotpQrCode(userEmail: dto.Receiver.EmailAddress, secretKey: dto.Receiver.TotpSecretkey).ToBase64String();
return SendAsync(dto, EmailTemplateType.TotpSecret, new() return SendAsync(dto, EmailTemplateType.TotpSecret, new()
{ {
{"[TFA_QR_CODE]", totp_qr_64 }, {"[TFA_QR_CODE]", totp_qr_64 },
{"[TFA_EXPIRATION]", dto.Receiver.TotpExpiration }
}); });
} }
} }

View File

@@ -37,13 +37,14 @@ public class EnvelopeSmsHandler : IEnvelopeSmsHandler
var key = string.Format(_totpSmsParams.Expiration.CacheKeyFormat, er_secret.EnvelopeId, er_secret.ReceiverId); var key = string.Format(_totpSmsParams.Expiration.CacheKeyFormat, er_secret.EnvelopeId, er_secret.ReceiverId);
var expiration = await _dCache.GetDateTimeAsync(key, cToken); var expiration = await _dCache.GetDateTimeAsync(key, cToken);
if(expiration is DateTime expirationDateTime && expirationDateTime < DateTime.Now) if(expiration is DateTime expirationDateTime && expirationDateTime >= DateTime.Now)
return (null, expirationDateTime); return (null, expirationDateTime);
else else
{ {
var new_expiration = DateTime.Now.AddSeconds(_totpSmsParams.TotpStep); var new_expiration = DateTime.Now.AddSeconds(_totpSmsParams.TotpStep);
var totp = _authenticator.GenerateTotp(er_secret.Receiver!.TotpSecretkey!, _totpSmsParams.TotpStep); var totp = _authenticator.GenerateTotp(er_secret.Receiver!.TotpSecretkey!, _totpSmsParams.TotpStep);
var msg = string.Format(_totpSmsParams.Format, totp, new_expiration.ToString(_totpSmsParams.Expiration.Format, _totpSmsParams.Expiration.CultureInfo)); var msg = string.Format(_totpSmsParams.Format, totp, new_expiration.ToString(_totpSmsParams.Expiration.Format, _totpSmsParams.Expiration.CultureInfo));
await _dCache.SetDateTimeAsync(key, new_expiration, cToken: cToken);
return (await _sender.SendSmsAsync(er_secret.PhoneNumber!, msg), new_expiration); return (await _sender.SendSmsAsync(er_secret.PhoneNumber!, msg), new_expiration);
} }
} }

View File

@@ -112,6 +112,13 @@
End Enum End Enum
#End Region #End Region
#Region "Role"
Public NotInheritable Class ReceiverRole
Public Const PreAuth As String = "PreAuth"
Public Const FullyAuth As String = "FullyAuth"
End Class
#End Region
#Region "Constants" #Region "Constants"
Public Const DATABASE = "DATABASE" Public Const DATABASE = "DATABASE"

View File

@@ -27,9 +27,6 @@ public class Receiver : IUnique<int>
[Column("TOTP_SECRET_KEY", TypeName = "nvarchar(MAX)")] [Column("TOTP_SECRET_KEY", TypeName = "nvarchar(MAX)")]
public string? TotpSecretkey { get; set; } public string? TotpSecretkey { get; set; }
[Column("TOTP_EXPIRATION", TypeName = "datetime")]
public DateTime? TotpExpiration { get; set; }
[Column("TFA_REG_DEADLINE", TypeName = "datetime")] [Column("TFA_REG_DEADLINE", TypeName = "datetime")]
public DateTime? TfaRegDeadline { get; set; } public DateTime? TfaRegDeadline { get; set; }

View File

@@ -1,4 +1,7 @@
using EnvelopeGenerator.Web.Models; using EnvelopeGenerator.Application.DTOs.EnvelopeReceiver;
using EnvelopeGenerator.Web.Models;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Security.Claims; using System.Security.Claims;
@@ -6,6 +9,7 @@ namespace EnvelopeGenerator.Web.Controllers
{ {
public static class ControllerBaseExtensions public static class ControllerBaseExtensions
{ {
#region Auth
public static string? GetClaimValue(this ControllerBase controller, string claimType) => controller.User.FindFirstValue(claimType); public static string? GetClaimValue(this ControllerBase controller, string claimType) => controller.User.FindFirstValue(claimType);
public static string? GetAuthEnvelopeUuid(this ControllerBase controller) => controller.User.FindFirstValue(ClaimTypes.NameIdentifier); public static string? GetAuthEnvelopeUuid(this ControllerBase controller) => controller.User.FindFirstValue(ClaimTypes.NameIdentifier);
@@ -23,7 +27,35 @@ namespace EnvelopeGenerator.Web.Controllers
var env_id_str = controller.User.FindFirstValue(EnvelopeClaimTypes.Id); var env_id_str = controller.User.FindFirstValue(EnvelopeClaimTypes.Id);
return int.TryParse(env_id_str, out int env_id) ? env_id : null; return int.TryParse(env_id_str, out int env_id) ? env_id : null;
} }
public static async Task SignInEnvelopeAsync(this HttpContext context, EnvelopeReceiverDto er, string receiverRole)
{
var claims = new List<Claim> {
new(ClaimTypes.NameIdentifier, er.Envelope!.Uuid),
new(ClaimTypes.Hash, er.Receiver!.Signature),
new(ClaimTypes.Name, er.Name ?? string.Empty),
new(ClaimTypes.Email, er.Receiver.EmailAddress),
new(EnvelopeClaimTypes.Title, er.Envelope.Title),
new(EnvelopeClaimTypes.Id, er.Envelope.Id.ToString()),
new(ClaimTypes.Role, receiverRole)
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
AllowRefresh = false,
IsPersistent = false
};
await context.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
}
#endregion
#region View error
//TODO: integrate localizer for ready-to-use views //TODO: integrate localizer for ready-to-use views
public static ViewResult ViewError(this Controller controller, ErrorViewModel errorViewModel) => controller.View("_Error", errorViewModel); public static ViewResult ViewError(this Controller controller, ErrorViewModel errorViewModel) => controller.View("_Error", errorViewModel);
@@ -61,5 +93,6 @@ namespace EnvelopeGenerator.Web.Controllers
Subtitle = "Ein unerwarteter Fehler ist aufgetreten", Subtitle = "Ein unerwarteter Fehler ist aufgetreten",
Body = "Bitte kontaktieren Sie das IT-Team." Body = "Bitte kontaktieren Sie das IT-Team."
}); });
} #endregion
}
} }

View File

@@ -3,12 +3,12 @@ using EnvelopeGenerator.Common;
using EnvelopeGenerator.Web.Services; using EnvelopeGenerator.Web.Services;
using EnvelopeGenerator.Application.Contracts; using EnvelopeGenerator.Application.Contracts;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using EnvelopeGenerator.Application;
using EnvelopeGenerator.Extensions; using EnvelopeGenerator.Extensions;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.Web.Controllers namespace EnvelopeGenerator.Web.Controllers
{ {
[Authorize] [Authorize(Roles = ReceiverRole.FullyAuth)]
[Route("api/[controller]")] [Route("api/[controller]")]
public class DocumentController : BaseController public class DocumentController : BaseController
{ {
@@ -48,7 +48,7 @@ namespace EnvelopeGenerator.Web.Controllers
} }
} }
[Authorize] [Authorize(Roles = ReceiverRole.FullyAuth)]
[HttpPost("{envelopeKey}")] [HttpPost("{envelopeKey}")]
public async Task<IActionResult> Open(string envelopeKey) public async Task<IActionResult> Open(string envelopeKey)
{ {

View File

@@ -10,7 +10,7 @@ using EnvelopeGenerator.Extensions;
namespace EnvelopeGenerator.Web.Controllers namespace EnvelopeGenerator.Web.Controllers
{ {
[Authorize] [Authorize(Roles = ReceiverRole.FullyAuth)]
[ApiController] [ApiController]
[Route("api/[controller]")] [Route("api/[controller]")]
public class EnvelopeController : BaseController public class EnvelopeController : BaseController
@@ -64,7 +64,7 @@ namespace EnvelopeGenerator.Web.Controllers
} }
} }
[Authorize] [Authorize(Roles = ReceiverRole.FullyAuth)]
[HttpPost("{envelopeKey}")] [HttpPost("{envelopeKey}")]
public async Task<IActionResult> Update(string envelopeKey, int index) public async Task<IActionResult> Update(string envelopeKey, int index)
{ {
@@ -110,7 +110,7 @@ namespace EnvelopeGenerator.Web.Controllers
} }
} }
[Authorize] [Authorize(Roles = ReceiverRole.FullyAuth)]
[HttpPost("reject")] [HttpPost("reject")]
public async Task<IActionResult> Reject([FromBody] string? reason = null) public async Task<IActionResult> Reject([FromBody] string? reason = null)
{ {

View File

@@ -19,7 +19,7 @@ using Ganss.Xss;
using Newtonsoft.Json; using Newtonsoft.Json;
using EnvelopeGenerator.Application.DTOs; using EnvelopeGenerator.Application.DTOs;
using DigitalData.Core.Client; using DigitalData.Core.Client;
using EnvelopeGenerator.Application.Extensions; using OtpNet;
namespace EnvelopeGenerator.Web.Controllers; namespace EnvelopeGenerator.Web.Controllers;
@@ -137,7 +137,9 @@ public class HomeController : ViewControllerBase
Success: er => View() Success: er => View()
.WithData("EnvelopeKey", envelopeReceiverId) .WithData("EnvelopeKey", envelopeReceiverId)
.WithData("TFAEnabled", er.Envelope!.TFAEnabled) .WithData("TFAEnabled", er.Envelope!.TFAEnabled)
.WithData("HasPhoneNumber", er.HasPhoneNumber), .WithData("HasPhoneNumber", er.HasPhoneNumber)
.WithData("SenderEmail", er.Envelope.User!.Email)
.WithData("EnvelopeTitle", er.Envelope.Title),
Fail: IActionResult (messages, notices) => Fail: IActionResult (messages, notices) =>
{ {
_logger.LogNotice(notices); _logger.LogNotice(notices);
@@ -160,7 +162,13 @@ public class HomeController : ViewControllerBase
{ {
var (smsRes, expiration) = await _envSmsHandler.SendTotpAsync(er_secret); var (smsRes, expiration) = await _envSmsHandler.SendTotpAsync(er_secret);
if (smsRes is not null && smsRes.Failed) ViewData["EnvelopeKey"] = envelopeReceiverId;
ViewData["TFAEnabled"] = er_secret.Envelope!.TFAEnabled;
ViewData["HasPhoneNumber"] = er_secret.HasPhoneNumber;
ViewData["SenderEmail"] = er_secret.Envelope.User!.Email;
ViewData["EnvelopeTitle"] = er_secret.Envelope.Title;
if (smsRes?.Failed ?? false)
{ {
var res_json = JsonConvert.SerializeObject(smsRes); var res_json = JsonConvert.SerializeObject(smsRes);
_logger.LogEnvelopeError(envelopeReceiverId: envelopeReceiverId, message: $"An unexpected error occurred while sending an SMS code. Response: ${res_json}"); _logger.LogEnvelopeError(envelopeReceiverId: envelopeReceiverId, message: $"An unexpected error occurred while sending an SMS code. Response: ${res_json}");
@@ -173,7 +181,6 @@ public class HomeController : ViewControllerBase
{ {
return View("EnvelopeLocked") return View("EnvelopeLocked")
.WithData("CodeType", "authenticatorCode") .WithData("CodeType", "authenticatorCode")
.WithData("QRCodeExpiration", er_secret.Receiver?.TotpExpiration)
.WithData("TfaRegDeadline", er_secret.Receiver?.TfaRegDeadline); .WithData("TfaRegDeadline", er_secret.Receiver?.TfaRegDeadline);
} }
} }
@@ -188,6 +195,11 @@ public class HomeController : ViewControllerBase
await _historyService.RecordAsync(er_secret.EnvelopeId, er_secret.Receiver!.EmailAddress, EnvelopeStatus.AccessCodeIncorrect); await _historyService.RecordAsync(er_secret.EnvelopeId, er_secret.Receiver!.EmailAddress, EnvelopeStatus.AccessCodeIncorrect);
Response.StatusCode = StatusCodes.Status401Unauthorized; Response.StatusCode = StatusCodes.Status401Unauthorized;
return View("EnvelopeLocked") return View("EnvelopeLocked")
.WithData("EnvelopeKey", envelopeReceiverId)
.WithData("TFAEnabled", er_secret.Envelope!.TFAEnabled)
.WithData("HasPhoneNumber", er_secret.HasPhoneNumber)
.WithData("SenderEmail", er_secret.Envelope.User!.Email)
.WithData("EnvelopeTitle", er_secret.Envelope.Title)
.WithData("ErrorMessage", _localizer[WebKey.WrongAccessCode].Value); .WithData("ErrorMessage", _localizer[WebKey.WrongAccessCode].Value);
} }
@@ -197,13 +209,14 @@ public class HomeController : ViewControllerBase
if (er_secret.Envelope!.TFAEnabled) if (er_secret.Envelope!.TFAEnabled)
{ {
var rcv = er_secret.Receiver; var rcv = er_secret.Receiver;
if (rcv.IsTotpSecretInvalid()) if (rcv.TotpSecretkey is null)
{ {
rcv.TotpSecretkey = _authenticator.GenerateTotpSecretKey(); rcv.TotpSecretkey = _authenticator.GenerateTotpSecretKey();
rcv.TotpExpiration = DateTime.Now.AddMonths(1);
await _rcvService.UpdateAsync(rcv); await _rcvService.UpdateAsync(rcv);
await _mailService.SendTFAQrCodeAsync(er_secret);
} }
await HttpContext.SignInEnvelopeAsync(er_secret, ReceiverRole.PreAuth);
return await TFAViewAsync(auth.UserSelectSMS, er_secret, envelopeReceiverId); return await TFAViewAsync(auth.UserSelectSMS, er_secret, envelopeReceiverId);
} }
@@ -216,7 +229,7 @@ public class HomeController : ViewControllerBase
if (er_secret.Receiver!.TotpSecretkey is null) if (er_secret.Receiver!.TotpSecretkey is null)
throw new InvalidOperationException($"TotpSecretkey of DTO cannot validate without TotpSecretkey. Dto: {JsonConvert.SerializeObject(er_secret)}"); throw new InvalidOperationException($"TotpSecretkey of DTO cannot validate without TotpSecretkey. Dto: {JsonConvert.SerializeObject(er_secret)}");
if (_envSmsHandler.VerifyTotp(auth.SmsCode!, er_secret.Receiver.TotpSecretkey)) if (!User.IsInRole(ReceiverRole.PreAuth) || !_envSmsHandler.VerifyTotp(auth.SmsCode!, er_secret.Receiver.TotpSecretkey))
{ {
Response.StatusCode = StatusCodes.Status401Unauthorized; Response.StatusCode = StatusCodes.Status401Unauthorized;
ViewData["ErrorMessage"] = _localizer[WebKey.WrongAccessCode].Value; ViewData["ErrorMessage"] = _localizer[WebKey.WrongAccessCode].Value;
@@ -229,7 +242,10 @@ public class HomeController : ViewControllerBase
[NonAction] [NonAction]
private async Task<IActionResult?> HandleAuthenticatorAsync(Auth auth, EnvelopeReceiverSecretDto er_secret, string envelopeReceiverId) private async Task<IActionResult?> HandleAuthenticatorAsync(Auth auth, EnvelopeReceiverSecretDto er_secret, string envelopeReceiverId)
{ {
if (er_secret.Receiver!.IsTotpInvalid(totp: auth.AuthenticatorCode!)) if (er_secret.Receiver!.TotpSecretkey is null)
throw new InvalidOperationException($"TotpSecretkey of DTO cannot validate without TotpSecretkey. Dto: {JsonConvert.SerializeObject(er_secret)}");
if (!User.IsInRole(ReceiverRole.PreAuth) || !_authenticator.VerifyTotp(auth.AuthenticatorCode!, er_secret.Receiver.TotpSecretkey, window: VerificationWindow.RfcSpecifiedNetworkDelay))
{ {
Response.StatusCode = StatusCodes.Status401Unauthorized; Response.StatusCode = StatusCodes.Status401Unauthorized;
ViewData["ErrorMessage"] = _localizer[WebKey.WrongAccessCode].Value; ViewData["ErrorMessage"] = _localizer[WebKey.WrongAccessCode].Value;
@@ -273,23 +289,32 @@ public class HomeController : ViewControllerBase
if (auth.HasMulti) if (auth.HasMulti)
{ {
Response.StatusCode = StatusCodes.Status401Unauthorized; return Unauthorized();
return View("EnvelopeLocked")
.WithData("ErrorMessage", _localizer[WebKey.WrongAccessCode].Value);
} }
else if (auth.HasAccessCode) else if (auth.HasAccessCode)
if(await HandleAccessCodeAsync(auth, er_secret, envelopeReceiverId) is IActionResult acView) {
if (await HandleAccessCodeAsync(auth, er_secret, envelopeReceiverId) is IActionResult acView)
return acView; return acView;
}
else if (auth.HasSmsCode) else if (auth.HasSmsCode)
if(await HandleSmsAsync(auth, er_secret, envelopeReceiverId) is IActionResult smsView) {
if (await HandleSmsAsync(auth, er_secret, envelopeReceiverId) is IActionResult smsView)
return smsView; return smsView;
}
else if (auth.HasAuthenticatorCode) else if (auth.HasAuthenticatorCode)
{
if(await HandleAuthenticatorAsync(auth, er_secret, envelopeReceiverId) is IActionResult aView) if(await HandleAuthenticatorAsync(auth, er_secret, envelopeReceiverId) is IActionResult aView)
return aView; return aView;
}
else else
{ {
Response.StatusCode = StatusCodes.Status401Unauthorized; Response.StatusCode = StatusCodes.Status401Unauthorized;
return View("EnvelopeLocked") return View("EnvelopeLocked")
.WithData("EnvelopeKey", envelopeReceiverId)
.WithData("TFAEnabled", er_secret.Envelope!.TFAEnabled)
.WithData("HasPhoneNumber", er_secret.HasPhoneNumber)
.WithData("SenderEmail", er_secret.Envelope.User!.Email)
.WithData("EnvelopeTitle", er_secret.Envelope.Title)
.WithData("ErrorMessage", _localizer[WebKey.WrongAccessCode].Value); .WithData("ErrorMessage", _localizer[WebKey.WrongAccessCode].Value);
} }
@@ -317,27 +342,8 @@ public class HomeController : ViewControllerBase
_logger.LogEnvelopeError(envelopeReceiverId: envelopeReceiverId, message: "No document byte-data was found in ENVELOPE_DOCUMENT table."); _logger.LogEnvelopeError(envelopeReceiverId: envelopeReceiverId, message: "No document byte-data was found in ENVELOPE_DOCUMENT table.");
return this.ViewDocumentNotFound(); return this.ViewDocumentNotFound();
} }
var claims = new List<Claim> { await HttpContext.SignInEnvelopeAsync(er, ReceiverRole.FullyAuth);
new(ClaimTypes.NameIdentifier, uuid),
new(ClaimTypes.Hash, signature),
new(ClaimTypes.Name, er.Name ?? string.Empty),
new(ClaimTypes.Email, er.Receiver.EmailAddress),
new(EnvelopeClaimTypes.Title, er.Envelope.Title),
new(EnvelopeClaimTypes.Id, er.Envelope.Id.ToString())
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
AllowRefresh = false,
IsPersistent = false
};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
//add PSPDFKit licence key //add PSPDFKit licence key
ViewData["PSPDFKitLicenseKey"] = _configuration["PSPDFKitLicenseKey"]; ViewData["PSPDFKitLicenseKey"] = _configuration["PSPDFKitLicenseKey"];
@@ -350,8 +356,8 @@ public class HomeController : ViewControllerBase
return this.ViewInnerServiceError(); return this.ViewInnerServiceError();
} }
} }
[Authorize] [Authorize(Roles = ReceiverRole.FullyAuth)]
[HttpGet("EnvelopeKey/{envelopeReceiverId}/Success")] [HttpGet("EnvelopeKey/{envelopeReceiverId}/Success")]
public async Task<IActionResult> EnvelopeSigned(string envelopeReceiverId) public async Task<IActionResult> EnvelopeSigned(string envelopeReceiverId)
{ {
@@ -386,7 +392,7 @@ public class HomeController : ViewControllerBase
} }
} }
[Authorize] [Authorize(Roles = ReceiverRole.FullyAuth)]
[HttpGet("EnvelopeKey/{envelopeReceiverId}/Rejected")] [HttpGet("EnvelopeKey/{envelopeReceiverId}/Rejected")]
public async Task<IActionResult> EnvelopeRejected(string envelopeReceiverId) public async Task<IActionResult> EnvelopeRejected(string envelopeReceiverId)
{ {
@@ -492,7 +498,7 @@ public class HomeController : ViewControllerBase
} }
} }
[Authorize] [Authorize(Roles = ReceiverRole.FullyAuth)]
[HttpGet("IsAuthenticated")] [HttpGet("IsAuthenticated")]
public IActionResult IsAuthenticated() public IActionResult IsAuthenticated()
{ {

View File

@@ -4,6 +4,7 @@ using EnvelopeGenerator.Application.DTOs.EnvelopeReceiverReadOnly;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json; using Newtonsoft.Json;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.Web.Controllers namespace EnvelopeGenerator.Web.Controllers
{ {
@@ -28,7 +29,7 @@ namespace EnvelopeGenerator.Web.Controllers
} }
[HttpPost] [HttpPost]
[Authorize] [Authorize(Roles = ReceiverRole.FullyAuth)]
public async Task<IActionResult> CreateAsync([FromBody] EnvelopeReceiverReadOnlyCreateDto createDto) public async Task<IActionResult> CreateAsync([FromBody] EnvelopeReceiverReadOnlyCreateDto createDto)
{ {
try try

View File

@@ -8,6 +8,7 @@ using EnvelopeGenerator.Application.Resources;
using DigitalData.Core.DTO; using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.Extensions; using EnvelopeGenerator.Application.Extensions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authorization;
namespace EnvelopeGenerator.Web.Controllers; namespace EnvelopeGenerator.Web.Controllers;
@@ -28,51 +29,59 @@ public class TFARegController : ViewControllerBase
_params = tfaRegParamsOptions.Value; _params = tfaRegParamsOptions.Value;
} }
[Authorize]
[HttpGet("{envelopeReceiverId}")] [HttpGet("{envelopeReceiverId}")]
public async Task<IActionResult> Reg(string envelopeReceiverId) public async Task<IActionResult> Reg(string envelopeReceiverId)
{ {
envelopeReceiverId = _sanitizer.Sanitize(envelopeReceiverId); try
(string? uuid, string? signature) = envelopeReceiverId.DecodeEnvelopeReceiverId();
if (uuid is null || signature is null)
{ {
_logger.LogEnvelopeError(uuid: uuid, signature: signature, message: _localizer[WebKey.WrongEnvelopeReceiverId]); envelopeReceiverId = _sanitizer.Sanitize(envelopeReceiverId);
return Unauthorized(); (string? uuid, string? signature) = envelopeReceiverId.DecodeEnvelopeReceiverId();
}
var er_secret_res = await _envRcvService.ReadWithSecretByUuidSignatureAsync(uuid: uuid, signature: signature); if (uuid is null || signature is null)
{
_logger.LogEnvelopeError(uuid: uuid, signature: signature, message: _localizer[WebKey.WrongEnvelopeReceiverId]);
return Unauthorized();
}
if (er_secret_res.IsFailed) var er_secret_res = await _envRcvService.ReadWithSecretByUuidSignatureAsync(uuid: uuid, signature: signature);
{
_logger.LogNotice(er_secret_res.Notices);
return this.ViewEnvelopeNotFound();
}
var er_secret = er_secret_res.Data;
if (!er_secret.Envelope!.TFAEnabled) if (er_secret_res.IsFailed)
return Unauthorized(); {
_logger.LogNotice(er_secret_res.Notices);
return this.ViewEnvelopeNotFound();
}
var er_secret = er_secret_res.Data;
var rcv = er_secret.Receiver; if (!er_secret.Envelope!.TFAEnabled)
return Unauthorized();
// Generate QR code as base 64 var rcv = er_secret.Receiver;
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();
// Calculate RFA registiration deadline // Generate QR code as base 64
if(rcv.TfaRegDeadline is null) rcv!.TotpSecretkey = _authenticator.GenerateTotpSecretKey();
{
rcv.TfaRegDeadline = _params.Deadline;
await _rcvService.UpdateAsync(rcv); await _rcvService.UpdateAsync(rcv);
var totp_qr_64 = _authenticator.GenerateTotpQrCode(userEmail: rcv.EmailAddress, secretKey: rcv.TotpSecretkey).ToBase64String();
// Calculate RFA registiration deadline
if (rcv.TfaRegDeadline is null)
{
rcv.TfaRegDeadline = _params.Deadline;
await _rcvService.UpdateAsync(rcv);
}
else if (rcv.TfaRegDeadline <= DateTime.Now)
return View("_Expired");
ViewData["RegDeadline"] = rcv.TfaRegDeadline;
ViewData["TotpQR64"] = totp_qr_64;
return View();
}
catch(Exception ex)
{
_logger.LogEnvelopeError(envelopeReceiverId: envelopeReceiverId, exception: ex, message: _localizer[WebKey.UnexpectedError]);
return this.ViewInnerServiceError();
} }
else if(rcv.TfaRegDeadline <= DateTime.Now)
return View("_Expired");
ViewData["RegDeadline"] = rcv.TfaRegDeadline;
ViewData["TotpQR64"] = totp_qr_64;
return View();
} }
} }

View File

@@ -5,7 +5,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<PackageId>EnvelopeGenerator.Web</PackageId> <PackageId>EnvelopeGenerator.Web</PackageId>
<Version>2.10.0</Version> <Version>2.10.4</Version>
<Authors>Digital Data GmbH</Authors> <Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company> <Company>Digital Data GmbH</Company>
<Product>EnvelopeGenerator.Web</Product> <Product>EnvelopeGenerator.Web</Product>
@@ -13,8 +13,8 @@
<PackageTags>digital data envelope generator web</PackageTags> <PackageTags>digital data envelope generator web</PackageTags>
<Description>EnvelopeGenerator.Web is an ASP.NET MVC application developed to manage signing processes. It uses Entity Framework Core (EF Core) for database operations. The user interface for signing processes is developed with Razor View Engine (.cshtml files) and JavaScript under wwwroot, integrated with PSPDFKit. This integration allows users to view and sign documents seamlessly.</Description> <Description>EnvelopeGenerator.Web is an ASP.NET MVC application developed to manage signing processes. It uses Entity Framework Core (EF Core) for database operations. The user interface for signing processes is developed with Razor View Engine (.cshtml files) and JavaScript under wwwroot, integrated with PSPDFKit. This integration allows users to view and sign documents seamlessly.</Description>
<ApplicationIcon>Assets\icon.ico</ApplicationIcon> <ApplicationIcon>Assets\icon.ico</ApplicationIcon>
<AssemblyVersion>2.10.0</AssemblyVersion> <AssemblyVersion>2.10.4</AssemblyVersion>
<FileVersion>2.10.0</FileVersion> <FileVersion>2.10.4</FileVersion>
<Copyright>Copyright © 2024 Digital Data GmbH. All rights reserved.</Copyright> <Copyright>Copyright © 2024 Digital Data GmbH. All rights reserved.</Copyright>
</PropertyGroup> </PropertyGroup>

View File

@@ -2,6 +2,7 @@
@using Newtonsoft.Json @using Newtonsoft.Json
@model Auth; @model Auth;
@{ @{
//TODO: Create view model
var nonce = _accessor.HttpContext?.Items["csp-nonce"] as string; var nonce = _accessor.HttpContext?.Items["csp-nonce"] as string;
var logo = _logoOpt.Value; var logo = _logoOpt.Value;
ViewData["Title"] = _localizer[WebKey.DocProtected]; ViewData["Title"] = _localizer[WebKey.DocProtected];
@@ -12,11 +13,12 @@
bool viaAuthenticator = codeType == "authenticatorCode"; bool viaAuthenticator = codeType == "authenticatorCode";
bool viaTFA = viaSms || viaAuthenticator; bool viaTFA = viaSms || viaAuthenticator;
DateTime? smsExpiration = ViewData["SmsExpiration"] is DateTime _smsExpiration ? _smsExpiration : null; DateTime? smsExpiration = ViewData["SmsExpiration"] is DateTime _smsExpiration ? _smsExpiration : null;
DateTime? qrCodeExpiration = ViewData["QRCodeExpiration"] is DateTime _qrCodeExpiration ? _qrCodeExpiration : null;
bool tfaEnabled = ViewData["TFAEnabled"] is bool _tfaEnabled && _tfaEnabled; bool tfaEnabled = ViewData["TFAEnabled"] is bool _tfaEnabled && _tfaEnabled;
bool hasPhoneNumber = ViewData["HasPhoneNumber"] is bool _hasPhoneNumber && _hasPhoneNumber; bool hasPhoneNumber = ViewData["HasPhoneNumber"] is bool _hasPhoneNumber && _hasPhoneNumber;
var envelopeKey = ViewData["EnvelopeKey"] as string; var envelopeKey = ViewData["EnvelopeKey"] as string;
DateTime? tfaRegDeadline = ViewData["TfaRegDeadline"] is DateTime _deadline ? _deadline : null; DateTime? tfaRegDeadline = ViewData["TfaRegDeadline"] is DateTime _deadline ? _deadline : null;
var senderEmail = ViewData["SenderEmail"] as string ?? string.Empty;
var envelopeTitle = ViewData["EnvelopeTitle"] as string ?? string.Empty;
} }
<div class="page container py-4 px-4"> <div class="page container py-4 px-4">
<header class="text-center"> <header class="text-center">
@@ -32,7 +34,7 @@
</div> </div>
<h1>@_localizer[WebKey.Formats.LockedTitle.Format(codeKeyName)]</h1> <h1>@_localizer[WebKey.Formats.LockedTitle.Format(codeKeyName)]</h1>
</header> </header>
@if (tfaRegDeadline is not null && tfaRegDeadline > DateTime.Now) @if (viaAuthenticator && (tfaRegDeadline is null || tfaRegDeadline > DateTime.Now))
{ {
<section class="text-center"> <section class="text-center">
<p class="m-0 p-0"> <p class="m-0 p-0">
@@ -46,7 +48,7 @@
</section> </section>
} }
<section class="text-center"> <section class="text-center">
<p>@_localizer[WebKey.Formats.LockedBody.Format(codeKeyName)].Value.Format(qrCodeExpiration.ToString())</p> <p>@_localizer[WebKey.Formats.LockedBody.Format(codeKeyName)].Value</p>
</section> </section>
<div class="row m-0 p-0"> <div class="row m-0 p-0">
<div class="access-code-panel justify-content-center align-items-center p-0 m-0"> <div class="access-code-panel justify-content-center align-items-center p-0 m-0">
@@ -91,7 +93,7 @@
<section class="no-receiver-explanation text-center"> <section class="no-receiver-explanation text-center">
<details> <details>
<summary>@_localizer[WebKey.Formats.LockedFooterTitle.Format(codeKeyName)]</summary> <summary>@_localizer[WebKey.Formats.LockedFooterTitle.Format(codeKeyName)]</summary>
<p>@_localizer[WebKey.Formats.LockedFooterBody.Format(codeKeyName)]</p> <p>@Html.Raw(_localizer[WebKey.Formats.LockedFooterBody.Format(codeKeyName)].Value.Format(senderEmail, "Envelope - " + envelopeTitle, string.Empty))</p>
</details> </details>
</section> </section>
</div> </div>

View File

@@ -68,7 +68,7 @@
{ {
ContractResolver = new CamelCasePropertyNamesContractResolver() ContractResolver = new CamelCasePropertyNamesContractResolver()
}; };
var lStrsJson = JsonConvert.SerializeObject(_localizer.ToDictionary(), settings).TrySanitize(_sanitizer); var lStrsJson = JsonConvert.SerializeObject(_localizer.ToDictionary(), settings);
} }
<script nonce="@nonce"> <script nonce="@nonce">
var localized = @Html.Raw(lStrsJson) var localized = @Html.Raw(lStrsJson)