From 6b3c90c618a7e9fe41855fa6d5817621bd3a2bfe Mon Sep 17 00:00:00 2001 From: Developer 02 Date: Fri, 26 Apr 2024 12:22:09 +0200 Subject: [PATCH] =?UTF-8?q?Benutzerdefinierte=20Fehlerseiten=20f=C3=BCr=20?= =?UTF-8?q?die=20Statuscodes=20404=20und=20500=20im=20HomeController=20hin?= =?UTF-8?q?zugef=C3=BCgt,=20um=20verschiedene=20Benutzerf=C3=A4lle=20zu=20?= =?UTF-8?q?behandeln.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contracts/IEnvelopeReceiverService.cs | 2 + EnvelopeGenerator.Application/EnvelopeFlag.cs | 3 +- .../EnvelopeGeneratorExtensions.cs | 18 ++-- .../Services/EnvelopeReceiverService.cs | 11 ++ .../Contracts/IEnvelopeReceiverRepository.cs | 2 + .../Repositories/EnvlopeReceiverRepository.cs | 7 +- .../Controllers/ControllerBaseExtensions.cs | 26 ++++- .../Controllers/HomeController.cs | 99 +++++++++++------- .../EnvelopeGenerator.Web.csproj | 3 + EnvelopeGenerator.Web/MessageKey.cs | 4 +- EnvelopeGenerator.Web/Program.cs | 2 +- .../wwwroot/css/error-space.css | 13 ++- EnvelopeGenerator.Web/wwwroot/favicon.ico | Bin 5430 -> 270398 bytes 13 files changed, 138 insertions(+), 52 deletions(-) diff --git a/EnvelopeGenerator.Application/Contracts/IEnvelopeReceiverService.cs b/EnvelopeGenerator.Application/Contracts/IEnvelopeReceiverService.cs index 58b246ed..59d9c595 100644 --- a/EnvelopeGenerator.Application/Contracts/IEnvelopeReceiverService.cs +++ b/EnvelopeGenerator.Application/Contracts/IEnvelopeReceiverService.cs @@ -19,5 +19,7 @@ namespace EnvelopeGenerator.Application.Contracts Task> VerifyAccessCodeAsync(string uuid, string signature, string accessCode); Task> VerifyAccessCodeAsync(string envelopeReceiverId, string accessCode); + + Task> IsExisting(string envelopeReceiverId); } } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/EnvelopeFlag.cs b/EnvelopeGenerator.Application/EnvelopeFlag.cs index 516575ae..705df692 100644 --- a/EnvelopeGenerator.Application/EnvelopeFlag.cs +++ b/EnvelopeGenerator.Application/EnvelopeFlag.cs @@ -2,6 +2,7 @@ { public enum EnvelopeFlag { - EnvelopeOrReceiverNonexists + EnvelopeOrReceiverNonexists, + NonDecodableEnvelopeReceiverId } } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/EnvelopeGeneratorExtensions.cs b/EnvelopeGenerator.Application/EnvelopeGeneratorExtensions.cs index 6d7ad6f9..43cf6390 100644 --- a/EnvelopeGenerator.Application/EnvelopeGeneratorExtensions.cs +++ b/EnvelopeGenerator.Application/EnvelopeGeneratorExtensions.cs @@ -6,19 +6,20 @@ namespace EnvelopeGenerator.Application { public static class EnvelopeGeneratorExtensions { - public static void LogEnvelopeError(this ILogger logger, string receiverId, string? message, params object?[] args) + public static void LogEnvelopeError(this ILogger logger, string envelopeEeceiverId, Exception? exception = null, string? message = null, params object?[] args) { - (string? envelopeUuid, string? receiverSignature) = receiverId.DecodeEnvelopeReceiverId(); - - var sb = new StringBuilder($"Envelope Uuid: {envelopeUuid}\nReceiver Signature: {receiverSignature}"); + var sb = new StringBuilder(envelopeEeceiverId.DecodeEnvelopeReceiverId().ToTitle()); if (message is not null) sb.AppendLine().Append(message); - logger.Log(LogLevel.Error, sb.ToString(), args); + if(exception is null) + logger.Log(LogLevel.Error, sb.ToString(), args); + else + logger.Log(LogLevel.Error, exception, sb.ToString(), args); } - public static void LogEnvelopeError(this ILogger logger, string uuid, string? signature = null, string? message = null, params object?[] args) + public static void LogEnvelopeError(this ILogger logger, string? uuid, string? signature = null, Exception? exception = null, string? message = null, params object?[] args) { var sb = new StringBuilder($"Envelope Uuid: {uuid}"); @@ -28,7 +29,10 @@ namespace EnvelopeGenerator.Application if (message is not null) sb.AppendLine().Append(message); - logger.Log(LogLevel.Error, sb.ToString(), args); + if (exception is null) + logger.Log(LogLevel.Error, sb.ToString(), args); + else + logger.Log(LogLevel.Error, exception, sb.ToString(), args); } public static string ToTitle(this (string? UUID, string? Signature) envelopeReceiverTuple) diff --git a/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs b/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs index bea643f2..cdfa33ed 100644 --- a/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs +++ b/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs @@ -95,5 +95,16 @@ namespace EnvelopeGenerator.Application.Services return await VerifyAccessCodeAsync(uuid: uuid, signature: signature, accessCode: accessCode); } + + public async Task> IsExisting(string envelopeReceiverId) + { + (string? uuid, string? signature) = envelopeReceiverId.DecodeEnvelopeReceiverId(); + + if (uuid is null || signature is null) + return Failed(false).WithFlag(EnvelopeFlag.NonDecodableEnvelopeReceiverId); + + int count = await _repository.CountAsync(uuid:uuid, signature:signature); + return Successful(count > 0); + } } } \ No newline at end of file diff --git a/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeReceiverRepository.cs b/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeReceiverRepository.cs index ba7e7de2..8f6b15dc 100644 --- a/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeReceiverRepository.cs +++ b/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeReceiverRepository.cs @@ -12,5 +12,7 @@ namespace EnvelopeGenerator.Infrastructure.Contracts Task ReadByUuidSignatureAsync(string uuid, string signature, bool withEnvelope = true, bool withReceiver = true); Task ReadAccessCodeAsync(string uuid, string signature); + + Task CountAsync(string uuid, string signature); } } \ No newline at end of file diff --git a/EnvelopeGenerator.Infrastructure/Repositories/EnvlopeReceiverRepository.cs b/EnvelopeGenerator.Infrastructure/Repositories/EnvlopeReceiverRepository.cs index 6fb35346..b19ee540 100644 --- a/EnvelopeGenerator.Infrastructure/Repositories/EnvlopeReceiverRepository.cs +++ b/EnvelopeGenerator.Infrastructure/Repositories/EnvlopeReceiverRepository.cs @@ -24,11 +24,12 @@ namespace EnvelopeGenerator.Infrastructure.Repositories query = query.Where(er => er.Receiver != null && er.Receiver.Signature == signature); if (withEnvelope) - query = query.Include(er => er.Envelope); + query = query.Include(er => er.Envelope).ThenInclude(e => e!.Documents!).ThenInclude(d => d.Elements) + .Include(er => er.Envelope).ThenInclude(e => e!.History) + .Include(er => er.Envelope).ThenInclude(e => e!.History); if (withReceiver) query = query.Include(er => er.Receiver); - return query; } @@ -45,5 +46,7 @@ namespace EnvelopeGenerator.Infrastructure.Repositories => await ReadWhere(uuid:uuid, signature:signature) .Select(er => er.AccessCode) .FirstOrDefaultAsync(); + + public async Task CountAsync(string uuid, string signature) => await ReadWhere(uuid: uuid, signature: signature).CountAsync(); } } \ No newline at end of file diff --git a/EnvelopeGenerator.Web/Controllers/ControllerBaseExtensions.cs b/EnvelopeGenerator.Web/Controllers/ControllerBaseExtensions.cs index be73e434..c6f5837f 100644 --- a/EnvelopeGenerator.Web/Controllers/ControllerBaseExtensions.cs +++ b/EnvelopeGenerator.Web/Controllers/ControllerBaseExtensions.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using EnvelopeGenerator.Web.Models; +using Microsoft.AspNetCore.Mvc; using System.Security.Claims; namespace EnvelopeGenerator.Web.Controllers @@ -38,5 +39,28 @@ namespace EnvelopeGenerator.Web.Controllers } return null; } + + public static ViewResult ViewError(this Controller controller, ErrorViewModel errorViewModel) => controller.View("_Error", errorViewModel); + + public static ViewResult ViewError404(this Controller controller) => controller.ViewError(new ErrorViewModel() + { + Title = "404", + Subtitle = "Die von Ihnen gesuchte Seite ist nicht verfügbar", + Body = "Sie können derzeit nur an Sie gerichtete Briefe einsehen und unterschreiben.", + }); + + public static ViewResult ViewEnvelopeNotFound(this Controller controller) => controller.ViewError(new ErrorViewModel() + { + Title = "404", + Subtitle = "Umschlag nicht gefunden", + Body = "Wenn Sie diese URL in Ihrer E-Mail erhalten haben, wenden Sie sich bitte an das IT-Team." + }); + + public static ViewResult ViewInnerServiceError(this Controller controller) => controller.ViewError(new ErrorViewModel() + { + Title = "500", + Subtitle = "Ein unerwarteter Fehler ist aufgetreten", + Body = "Bitte kontaktieren Sie das IT-Team." + }); } } \ No newline at end of file diff --git a/EnvelopeGenerator.Web/Controllers/HomeController.cs b/EnvelopeGenerator.Web/Controllers/HomeController.cs index b6275548..bd8d544d 100644 --- a/EnvelopeGenerator.Web/Controllers/HomeController.cs +++ b/EnvelopeGenerator.Web/Controllers/HomeController.cs @@ -1,7 +1,6 @@ using EnvelopeGenerator.Application.Contracts; using EnvelopeGenerator.Application.Services; using EnvelopeGenerator.Common; -using EnvelopeGenerator.Web.Models; using EnvelopeGenerator.Web.Services; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication; @@ -10,6 +9,10 @@ using System.Security.Claims; using Microsoft.AspNetCore.Authorization; using DigitalData.Core.API; using DigitalData.Core.Application; +using EnvelopeGenerator.Application; +using DigitalData.Core.Contracts.CultureServices; +using DigitalData.Core.CultureServices; +using Azure; namespace EnvelopeGenerator.Web.Controllers { @@ -19,12 +22,15 @@ namespace EnvelopeGenerator.Web.Controllers private readonly IEnvelopeReceiverService _envRcvService; private readonly IEnvelopeService _envelopeService; private readonly IEnvelopeHistoryService _historyService; - public HomeController(DatabaseService databaseService, EnvelopeOldService envelopeOldService, ILogger logger, IEnvelopeReceiverService envelopeReceiverService, IEnvelopeService envelopeService, IEnvelopeHistoryService historyService) : base(databaseService, logger) + private readonly IKeyTranslationService _translator; + + public HomeController(DatabaseService databaseService, EnvelopeOldService envelopeOldService, ILogger logger, IEnvelopeReceiverService envelopeReceiverService, IEnvelopeService envelopeService, IEnvelopeHistoryService historyService, IKeyTranslationService keyTranslationService) : base(databaseService, logger) { this.envelopeOldService = envelopeOldService; _envRcvService = envelopeReceiverService; _envelopeService = envelopeService; _historyService = historyService; + _translator = keyTranslationService; } [HttpGet("/EnvelopeKey/{envelopeReceiverId}")] @@ -33,15 +39,18 @@ namespace EnvelopeGenerator.Web.Controllers ViewData["EnvelopeKey"] = envelopeReceiverId; try { - (string envelopeUuid, string receiverSignature) = envelopeReceiverId.DecodeEnvelopeReceiverId(); - var envelopeResult = await _envelopeService.ReadByUuidAsync(envelopeUuid, signature: receiverSignature, withEnvelopeReceivers:true); - var envelope = envelopeResult.Data; - var envelopeReceiver = envelope?.EnvelopeReceivers?.FirstOrDefault(); - var mailAddress = envelopeReceiver?.Receiver?.EmailAddress; - var useAccessCode = envelope?.UseAccessCode; - - ViewData["EnvelopeResult"] = envelopeResult; - if(envelopeResult.IsSuccess && envelope is not null && mailAddress is not null && (envelope.UseAccessCode ?? false)) + var erResult = await _envRcvService.ReadByEnvelopeReceiverIdAsync(envelopeReceiverId: envelopeReceiverId); + var er = erResult.Data; + var receiver = er?.Receiver; + var envelope = er?.Envelope; + var mailAddress = receiver?.EmailAddress; + + if (erResult is null) + { + _logger.LogError(MessageKey.ServiceOutputNullError.TranslateWith(_translator)); + return this.ViewEnvelopeNotFound(); + } + else if (erResult.IsSuccess && mailAddress is not null && (envelope?.UseAccessCode ?? false)) { EnvelopeResponse response = await envelopeOldService.LoadEnvelope(envelopeReceiverId); @@ -56,32 +65,37 @@ namespace EnvelopeGenerator.Web.Controllers } else { - envelopeResult.WithMessageKey(MessageKey.FailedToSendAccessCode); - _logger.LogError($"{MessageKey.FailedToSendAccessCode.ToString()}"); + _logger.LogServiceMessage(erResult); + return this.ViewEnvelopeNotFound(); } } catch(Exception ex) { - _logger.LogError(ex, MessageKey.UnexpectedError.ToString()); + _logger.LogEnvelopeError(envelopeEeceiverId: envelopeReceiverId, exception:ex, message: MessageKey.UnexpectedError.TranslateWith(_translator)); + return this.ViewInnerServiceError(); } return Redirect($"{envelopeReceiverId}/Locked"); } [HttpGet("EnvelopeKey/{envelopeReceiverId}/Locked")] - public IActionResult EnvelopeLocked([FromRoute] string envelopeReceiverId) + public async Task EnvelopeLocked([FromRoute] string envelopeReceiverId) { - (string? uuid, string? signature) = envelopeReceiverId.DecodeEnvelopeReceiverId(); - if (uuid is null || signature is null) + try { - return View("_Error", new ErrorViewModel() - { - Title = "404", - Subtitle = "Anfrage fehlgeschlagen!", - Body = "Das angeforderte Umschlag wurde nicht gefunden." - }); + var result = await _envRcvService.IsExisting(envelopeReceiverId: envelopeReceiverId); + bool isExisting = result.Data; + + if (result.HasFlag(EnvelopeFlag.NonDecodableEnvelopeReceiverId) || !isExisting) + return this.ViewEnvelopeNotFound(); + + return View().WithData("EnvelopeKey", envelopeReceiverId); + } + catch(Exception ex) + { + _logger.LogEnvelopeError(envelopeEeceiverId: envelopeReceiverId, exception: ex); + return this.ViewInnerServiceError(); } - return View().WithData("EnvelopeKey", envelopeReceiverId); } [HttpPost("/EnvelopeKey/{envelopeReceiverId}/Locked")] @@ -93,7 +107,7 @@ namespace EnvelopeGenerator.Web.Controllers if(uuid is null || signature is null) { - _logger.LogWarning($"{MessageKey.WrongEnvelopeReceiverId.ToString()}"); + _logger.LogEnvelopeError(uuid: uuid, signature: signature, message: MessageKey.WrongEnvelopeReceiverId.TranslateWith(_translator)); return BadRequest(_envelopeService.CreateMessage(false, MessageKey.WrongEnvelopeReceiverId.ToString())); } @@ -117,7 +131,7 @@ namespace EnvelopeGenerator.Web.Controllers { if (envelopeOldService.ReceiverAlreadySigned(response.Envelope, response.Receiver.Id)) { - return Redirect($"/EnvelopeKey/{envelopeReceiverId}/Success"); + return View("EnvelopeSigned"); } var envelope = await _envelopeService.ReadByUuidAsync(uuid: uuid, signature: signature, withAll: true); @@ -171,9 +185,29 @@ namespace EnvelopeGenerator.Web.Controllers [HttpGet("/EnvelopeKey/{envelopeReceiverId}/Success")] public async Task EnvelopeSigned(string envelopeReceiverId) { - await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); - ViewData["EnvelopeKey"] = envelopeReceiverId; - return View(); + try + { + var result = await _envRcvService.IsExisting(envelopeReceiverId: envelopeReceiverId); + bool isExisting = result.Data; + if (result.HasFlag(EnvelopeFlag.NonDecodableEnvelopeReceiverId) || !isExisting) + return this.ViewEnvelopeNotFound(); + + EnvelopeResponse response = await envelopeOldService.LoadEnvelope(envelopeReceiverId); + + if (!envelopeOldService.ReceiverAlreadySigned(response.Envelope, response.Receiver.Id)) + { + return Redirect($"/EnvelopeKey/{envelopeReceiverId}/Locked"); + } + + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + ViewData["EnvelopeKey"] = envelopeReceiverId; + return View(); + } + catch (Exception ex) + { + _logger.LogEnvelopeError(envelopeEeceiverId: envelopeReceiverId, exception: ex); + return this.ViewInnerServiceError(); + } } [Authorize] @@ -185,11 +219,6 @@ namespace EnvelopeGenerator.Web.Controllers return Ok(new { EnvelopeUuid = envelopeUuid, ReceiverSignature = receiverSignature }); } - [HttpGet("Error")] - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public IActionResult Error() - { - return View(); - } + public IActionResult Error404() => this.ViewError404(); } } \ No newline at end of file diff --git a/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj b/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj index 4df31124..220e8fc6 100644 --- a/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj +++ b/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj @@ -94,6 +94,9 @@ Always + + PreserveNewest + Always diff --git a/EnvelopeGenerator.Web/MessageKey.cs b/EnvelopeGenerator.Web/MessageKey.cs index 47ccc718..a1b2f6de 100644 --- a/EnvelopeGenerator.Web/MessageKey.cs +++ b/EnvelopeGenerator.Web/MessageKey.cs @@ -2,9 +2,11 @@ { public enum MessageKey { + ServiceOutputNullError, UnexpectedError, FailedToSendAccessCode, WrongEnvelopeReceiverId, //the value should be about URL (like URL is not existing) as a part of security. - DataIntegrityError + DataIntegrityError, + NonDecodableEnvelopeReceiverId } } \ No newline at end of file diff --git a/EnvelopeGenerator.Web/Program.cs b/EnvelopeGenerator.Web/Program.cs index 0d424845..84d28b61 100644 --- a/EnvelopeGenerator.Web/Program.cs +++ b/EnvelopeGenerator.Web/Program.cs @@ -160,7 +160,7 @@ try app.UseAuthorization(); app.MapControllers(); - + app.MapFallbackToController("Error404", "Home"); app.Run(); } catch(Exception ex) diff --git a/EnvelopeGenerator.Web/wwwroot/css/error-space.css b/EnvelopeGenerator.Web/wwwroot/css/error-space.css index 1cf74752..9a3391b0 100644 --- a/EnvelopeGenerator.Web/wwwroot/css/error-space.css +++ b/EnvelopeGenerator.Web/wwwroot/css/error-space.css @@ -6,11 +6,16 @@ body { background: linear-gradient(90deg, rgba(47, 54, 64, 1) 23%, rgba(24, 27, 32, 1) 100%); } +:root { + --moon-left-position: 0px; +} + + .moon { background: linear-gradient(90deg, rgba(208, 208, 208, 1) 48%, rgba(145, 145, 145, 1) 100%); position: absolute; top: -100px; - left: -300px; + left: var(--moon-left-position); width: 900px; height: 900px; content: ''; @@ -28,14 +33,14 @@ body { .moon__crater1 { top: 250px; - left: 500px; + left: calc(var(--moon-left-position) + 800px); width: 60px; height: 180px; } .moon__crater2 { top: 650px; - left: 340px; + left: calc(var(--moon-left-position) + 640px); width: 40px; height: 80px; transform: rotate(55deg); @@ -43,7 +48,7 @@ body { .moon__crater3 { top: -20px; - left: 40px; + left: calc(var(--moon-left-position) + 340px); width: 65px; height: 120px; transform: rotate(250deg); diff --git a/EnvelopeGenerator.Web/wwwroot/favicon.ico b/EnvelopeGenerator.Web/wwwroot/favicon.ico index 63e859b476eff5055e0e557aaa151ca8223fbeef..60c2c4ab511fbf7d85e5be3021ecfe159b57b40b 100644 GIT binary patch literal 270398 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