diff --git a/EnvelopeGenerator.Application/Contracts/IEnvelopeHistoryService.cs b/EnvelopeGenerator.Application/Contracts/IEnvelopeHistoryService.cs index 9192756f..933037cd 100644 --- a/EnvelopeGenerator.Application/Contracts/IEnvelopeHistoryService.cs +++ b/EnvelopeGenerator.Application/Contracts/IEnvelopeHistoryService.cs @@ -2,10 +2,14 @@ using EnvelopeGenerator.Application.DTOs; using EnvelopeGenerator.Domain.Entities; using EnvelopeGenerator.Infrastructure.Contracts; +using static EnvelopeGenerator.Common.Constants; namespace EnvelopeGenerator.Application.Contracts { public interface IEnvelopeHistoryService : IBasicCRUDService { + Task CountAsync(int? envelopeId = null, string? userReference = null, int? status = null); + + Task AccessCodeAlreadyRequested(int envelopeId, string userReference); } } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/Contracts/IEnvelopeReceiverService.cs b/EnvelopeGenerator.Application/Contracts/IEnvelopeReceiverService.cs index 348257e0..59d9c595 100644 --- a/EnvelopeGenerator.Application/Contracts/IEnvelopeReceiverService.cs +++ b/EnvelopeGenerator.Application/Contracts/IEnvelopeReceiverService.cs @@ -7,6 +7,19 @@ namespace EnvelopeGenerator.Application.Contracts { public interface IEnvelopeReceiverService : IBasicCRUDService { - Task VerifyAccessCode(string envelopeUuid, string accessCode); + + Task>> ReadByUuidAsync(string uuid, bool withEnvelope = true, bool withReceiver = false); + + Task>> ReadBySignatureAsync(string signature, bool withEnvelope = false, bool withReceiver = true); + + Task> ReadByUuidSignatureAsync(string uuid, string signature, bool withEnvelope = true, bool withReceiver = true); + + Task> ReadByEnvelopeReceiverIdAsync(string envelopeReceiverId, bool withEnvelope = true, bool withReceiver = true); + + 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/Contracts/IEnvelopeService.cs b/EnvelopeGenerator.Application/Contracts/IEnvelopeService.cs index 6ce6d9e3..1c275258 100644 --- a/EnvelopeGenerator.Application/Contracts/IEnvelopeService.cs +++ b/EnvelopeGenerator.Application/Contracts/IEnvelopeService.cs @@ -7,8 +7,8 @@ namespace EnvelopeGenerator.Application.Contracts { public interface IEnvelopeService : IBasicCRUDService { - Task>> ReadAllWithAsync(bool documents = false, bool receivers = false, bool history = false, bool documentReceiverElement = false); + Task>> ReadAllWithAsync(bool documents = false, bool envelopeReceivers = false, bool history = false, bool documentReceiverElement = false); - Task> ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withAll = false); + Task> ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withEnvelopeReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withUser = false, bool withAll = false); } } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/DTOs/EnvelopeDto.cs b/EnvelopeGenerator.Application/DTOs/EnvelopeDto.cs index 0a352d7f..7203c1d2 100644 --- a/EnvelopeGenerator.Application/DTOs/EnvelopeDto.cs +++ b/EnvelopeGenerator.Application/DTOs/EnvelopeDto.cs @@ -1,4 +1,5 @@ -using EnvelopeGenerator.Domain.Entities; +using DigitalData.UserManager.Domain.Entities; +using EnvelopeGenerator.Domain.Entities; namespace EnvelopeGenerator.Application.DTOs { @@ -26,13 +27,13 @@ namespace EnvelopeGenerator.Application.DTOs int? ExpiresWhenDays, int? ExpiresWarningWhenDays, bool DmzMoved, - ReceiverDto? User, + User? User, EnvelopeType? EnvelopeType, string? EnvelopeTypeTitle, bool IsAlreadySent, string? StatusTranslated, string? ContractTypeTranslated, IEnumerable? Documents, - IEnumerable? Receivers, + IEnumerable? EnvelopeReceivers, IEnumerable? History); } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/DTOs/EnvelopeReceiverDto.cs b/EnvelopeGenerator.Application/DTOs/EnvelopeReceiverDto.cs index 02404c47..42de4e65 100644 --- a/EnvelopeGenerator.Application/DTOs/EnvelopeReceiverDto.cs +++ b/EnvelopeGenerator.Application/DTOs/EnvelopeReceiverDto.cs @@ -6,10 +6,10 @@ namespace EnvelopeGenerator.Application.DTOs int EnvelopeId, int ReceiverId, int Sequence, - string Name, - string JobTitle, - string CompanyName, - string PrivateMessage, + string? Name, + string? JobTitle, + string? CompanyName, + string? PrivateMessage, DateTime AddedWhen, DateTime? ChangedWhen, Envelope? Envelope, diff --git a/EnvelopeGenerator.Application/DTOs/ReceiverDto.cs b/EnvelopeGenerator.Application/DTOs/ReceiverDto.cs index 8e3a3f96..cc27ef93 100644 --- a/EnvelopeGenerator.Application/DTOs/ReceiverDto.cs +++ b/EnvelopeGenerator.Application/DTOs/ReceiverDto.cs @@ -4,6 +4,6 @@ int Id, string EmailAddress, string Signature, - DateTime AddedWhen, - IEnumerable? EnvelopeReceivers); + DateTime AddedWhen + ); } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/EnvelopeFlag.cs b/EnvelopeGenerator.Application/EnvelopeFlag.cs new file mode 100644 index 00000000..705df692 --- /dev/null +++ b/EnvelopeGenerator.Application/EnvelopeFlag.cs @@ -0,0 +1,8 @@ +namespace EnvelopeGenerator.Application +{ + public enum EnvelopeFlag + { + EnvelopeOrReceiverNonexists, + NonDecodableEnvelopeReceiverId + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.Application/EnvelopeGenerator.Application.csproj b/EnvelopeGenerator.Application/EnvelopeGenerator.Application.csproj index 01eaa890..8607f5a3 100644 --- a/EnvelopeGenerator.Application/EnvelopeGenerator.Application.csproj +++ b/EnvelopeGenerator.Application/EnvelopeGenerator.Application.csproj @@ -22,6 +22,18 @@ ..\..\WebCoreModules\DigitalData.Core.Application\bin\Debug\net7.0\DigitalData.Core.Contracts.dll + + ..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.Core.Infrastructure.dll + + + ..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Application.dll + + + ..\..\WebUserManager\DigitalData.UserManager.Domain\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll + + + ..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Infrastructure.dll + diff --git a/EnvelopeGenerator.Application/EnvelopeGeneratorExtensions.cs b/EnvelopeGenerator.Application/EnvelopeGeneratorExtensions.cs new file mode 100644 index 00000000..43cf6390 --- /dev/null +++ b/EnvelopeGenerator.Application/EnvelopeGeneratorExtensions.cs @@ -0,0 +1,43 @@ +using EnvelopeGenerator.Application.Services; +using Microsoft.Extensions.Logging; +using System.Text; + +namespace EnvelopeGenerator.Application +{ + public static class EnvelopeGeneratorExtensions + { + public static void LogEnvelopeError(this ILogger logger, string envelopeEeceiverId, Exception? exception = null, string? message = null, params object?[] args) + { + var sb = new StringBuilder(envelopeEeceiverId.DecodeEnvelopeReceiverId().ToTitle()); + + if (message is not null) + sb.AppendLine().Append(message); + + 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, Exception? exception = null, string? message = null, params object?[] args) + { + var sb = new StringBuilder($"Envelope Uuid: {uuid}"); + + if(signature is not null) + sb.AppendLine().Append($"Receiver Signature: {signature}"); + + if (message is not null) + sb.AppendLine().Append(message); + + 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) + { + return $"Envelope UUID: {envelopeReceiverTuple.UUID}\n Receiver Signature: {envelopeReceiverTuple.Signature}"; + } + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.Application/MessageKey.cs b/EnvelopeGenerator.Application/MessageKey.cs new file mode 100644 index 00000000..54469ef2 --- /dev/null +++ b/EnvelopeGenerator.Application/MessageKey.cs @@ -0,0 +1,19 @@ +namespace EnvelopeGenerator.Application +{ + public enum MessageKey + { + EnvelopeNotFound, + EnvelopeReceiverNotFound, + AccessCodeNull2Client, + AccessCodeNull2Logger, + WrongAccessCode, + DataIntegrityIssue, + SecurityBreachOrDataIntegrity, + PossibleDataIntegrityIssue, + SecurityBreach, + PossibleSecurityBreach, + WrongEnvelopeReceiverId2Client, //Do not leak information about the creation of the url. For example, the envelope you are looking for does not exist + WrongEnvelopeReceiverId2Logger, + EnvelopeOrReceiverNonexists + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.Application/Services/EnvelopeGeneratorExtensions.cs b/EnvelopeGenerator.Application/Services/EnvelopeGeneratorExtensions.cs index d111202b..7eb3e9cd 100644 --- a/EnvelopeGenerator.Application/Services/EnvelopeGeneratorExtensions.cs +++ b/EnvelopeGenerator.Application/Services/EnvelopeGeneratorExtensions.cs @@ -5,13 +5,76 @@ /// public static class EnvelopeGeneratorExtensions { + /// + /// Validates whether a given string is a correctly formatted Base-64 encoded string. + /// + /// + /// This method checks the string for proper Base-64 formatting, which includes validating + /// the length of the string (must be divisible by 4). It also checks each character to ensure + /// it belongs to the Base-64 character set (A-Z, a-z, 0-9, '+', '/', and '=' for padding). + /// The method ensures that padding characters ('=') only appear at the end of the string and + /// are in a valid configuration (either one '=' at the end if the string's length % 4 is 3, + /// or two '==' if the length % 4 is 2). + /// + /// The Base-64 encoded string to validate. + /// + /// true if the string is a valid Base-64 encoded string; otherwise, false. + /// + /// + /// + /// string testString = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnk="; + /// bool isValid = IsValidBase64String(testString); + /// Console.WriteLine(isValid); // Output: true + /// + /// + public static bool IsBase64String(this string input) + { + // Check if the string is null or empty + if (string.IsNullOrEmpty(input)) + { + return false; + } + + // Replace valid base-64 padding + input = input.Trim(); + int mod4 = input.Length % 4; + if (mod4 > 0) + { + // Base-64 string lengths should be divisible by 4 + return false; + } + + // Check each character to ensure it is valid base-64 + foreach (char c in input) + { + if (!char.IsLetterOrDigit(c) && c != '+' && c != '/' && c != '=') + { + // Invalid character detected + return false; + } + } + + // Ensure no invalid padding scenarios exist + if (input.EndsWith("==") && (input.Length % 4 == 0) || + input.EndsWith("=") && (input.Length % 4 == 3)) + { + return true; + } + + return input.IndexOf('=') == -1; // No padding allowed except at the end + } + /// /// Decodes the envelope receiver ID and extracts the envelope UUID and receiver signature. /// /// The base64 encoded string containing the envelope UUID and receiver signature. /// A tuple containing the envelope UUID and receiver signature. - public static (string EnvelopeUuid, string ReceiverSignature) DecodeEnvelopeReceiverId(this string envelopeReceiverId) + public static (string? EnvelopeUuid, string? ReceiverSignature) DecodeEnvelopeReceiverId(this string envelopeReceiverId) { + if (!envelopeReceiverId.IsBase64String()) + { + return (null, null); + } byte[] bytes = Convert.FromBase64String(envelopeReceiverId); string decodedString = System.Text.Encoding.UTF8.GetString(bytes); string[] parts = decodedString.Split(new string[] { "::" }, StringSplitOptions.None); diff --git a/EnvelopeGenerator.Application/Services/EnvelopeHistoryService.cs b/EnvelopeGenerator.Application/Services/EnvelopeHistoryService.cs index 67784a0f..f2f9fd63 100644 --- a/EnvelopeGenerator.Application/Services/EnvelopeHistoryService.cs +++ b/EnvelopeGenerator.Application/Services/EnvelopeHistoryService.cs @@ -5,6 +5,7 @@ using EnvelopeGenerator.Application.Contracts; using EnvelopeGenerator.Application.DTOs; using EnvelopeGenerator.Domain.Entities; using EnvelopeGenerator.Infrastructure.Contracts; +using static EnvelopeGenerator.Common.Constants; namespace EnvelopeGenerator.Application.Services { @@ -14,5 +15,9 @@ namespace EnvelopeGenerator.Application.Services : base(repository, translationService, mapper) { } + + public async Task CountAsync(int? envelopeId = null, string? userReference = null, int? status = null) => await _repository.CountAsync(envelopeId: envelopeId, userReference: userReference, status: status); + + public async Task AccessCodeAlreadyRequested(int envelopeId, string userReference) => await _repository.CountAsync(envelopeId: envelopeId, userReference:userReference, status: (int) EnvelopeStatus.AccessCodeRequested) > 0; } } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs b/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs index 787ed26f..cdfa33ed 100644 --- a/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs +++ b/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs @@ -6,7 +6,6 @@ using EnvelopeGenerator.Application.Contracts; using EnvelopeGenerator.Application.DTOs; using EnvelopeGenerator.Domain.Entities; using EnvelopeGenerator.Infrastructure.Contracts; -using Microsoft.EntityFrameworkCore; namespace EnvelopeGenerator.Application.Services { @@ -17,10 +16,95 @@ namespace EnvelopeGenerator.Application.Services { } - public async Task VerifyAccessCode(string envelopeUuid, string accessCode) + public async Task>> ReadBySignatureAsync(string signature, bool withEnvelope = false, bool withReceiver = true) { - var envelopeAccessCode = await _repository.ReadAccessCodeByEnvelopeUuid(envelopeUuid); - return CreateMessage(isSuccess: accessCode == envelopeAccessCode) ; + var env_rcvs = await _repository.ReadBySignatureAsync(signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver); + return Successful(_mapper.MapOrThrow>(env_rcvs)); + } + + public async Task>> ReadByUuidAsync(string uuid, bool withEnvelope = true, bool withReceiver = false) + { + var env_rcvs = await _repository.ReadByUuidAsync(uuid: uuid, withEnvelope: withEnvelope, withReceiver: withReceiver); + return Successful(_mapper.MapOrThrow>(env_rcvs)); + } + + public async Task> ReadByUuidSignatureAsync(string uuid, string signature, bool withEnvelope = true, bool withReceiver = true) + { + var env_rcv = await _repository.ReadByUuidSignatureAsync(uuid: uuid, signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver); + if (env_rcv is null) + return Failed() + .WithClientMessageKey(MessageKey.EnvelopeReceiverNotFound); + + return Successful(_mapper.MapOrThrow(env_rcv)); + } + + public async Task> ReadByEnvelopeReceiverIdAsync(string envelopeReceiverId, bool withEnvelope = true, bool withReceiver = true) + { + (string? uuid, string? signature) = envelopeReceiverId.DecodeEnvelopeReceiverId(); + + if (uuid is null || signature is null) + return Failed() + .WithClientMessageKey(MessageKey.WrongEnvelopeReceiverId2Client) + .WithWarningMessage((uuid, signature).ToTitle()) + .WithWarningMessageKey(MessageKey.WrongEnvelopeReceiverId2Logger) + .WithWarningMessageKey(MessageKey.PossibleSecurityBreach) + .WithFlag(Flag.PossibleSecurityBreach); + + return await ReadByUuidSignatureAsync(uuid: uuid, signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver); + } + + public async Task> VerifyAccessCodeAsync(string uuid, string signature, string accessCode) + { + var er = await _repository.ReadByUuidSignatureAsync(uuid: uuid, signature: signature); + + if (er is null) + return Failed() + .WithClientMessageKey(MessageKey.EnvelopeOrReceiverNonexists) + .WithWarningMessage((uuid, signature).ToTitle()) + .WithWarningMessageKey(MessageKey.EnvelopeOrReceiverNonexists) + .WithWarningMessageKey(MessageKey.PossibleDataIntegrityIssue) + .WithFlag(MessageKey.PossibleDataIntegrityIssue); + + var actualAccessCode = er.AccessCode; + + if (actualAccessCode is null) + return Failed() + .WithClientMessageKey(MessageKey.AccessCodeNull2Client) + .WithCriticalMessage((uuid, signature).ToTitle()) + .WithCriticalMessageKey(MessageKey.AccessCodeNull2Logger) + .WithCriticalMessageKey(MessageKey.DataIntegrityIssue) + .WithFlag(Flag.DataIntegrityIssue); + + else if(accessCode != actualAccessCode) + return Successful(false).WithClientMessageKey(MessageKey.WrongAccessCode); + else + return Successful(true); + } + + public async Task> VerifyAccessCodeAsync(string envelopeReceiverId, string accessCode) + { + (string? uuid, string? signature) = envelopeReceiverId.DecodeEnvelopeReceiverId(); + + if (uuid is null || signature is null) + return Failed() + .WithClientMessageKey(MessageKey.WrongEnvelopeReceiverId2Client) + .WithCriticalMessageKey(MessageKey.WrongEnvelopeReceiverId2Logger) + .WithCriticalMessageKey(MessageKey.SecurityBreach) + .WithCriticalMessage("Attempt to verify access code detected. Such actions are generally not initiated by well-intentioned users. Potential security breach suspected. Immediate investigation required.") + .WithFlag(Flag.SecurityBreach); + + 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.Application/Services/EnvelopeService.cs b/EnvelopeGenerator.Application/Services/EnvelopeService.cs index efd6055f..9bdd7837 100644 --- a/EnvelopeGenerator.Application/Services/EnvelopeService.cs +++ b/EnvelopeGenerator.Application/Services/EnvelopeService.cs @@ -6,26 +6,29 @@ using EnvelopeGenerator.Application.Contracts; using EnvelopeGenerator.Application.DTOs; using EnvelopeGenerator.Domain.Entities; using EnvelopeGenerator.Infrastructure.Contracts; +using Microsoft.Extensions.Logging; namespace EnvelopeGenerator.Application.Services { public class EnvelopeService : BasicCRUDService, IEnvelopeService { - public EnvelopeService(IEnvelopeRepository repository, IKeyTranslationService translationService, IMapper mapper) + private readonly ILogger _logger; + public EnvelopeService(IEnvelopeRepository repository, IKeyTranslationService translationService, IMapper mapper, ILogger logger) : base(repository, translationService, mapper) { + _logger = logger; } - public async Task>> ReadAllWithAsync(bool documents = false, bool receivers = false, bool history = false, bool documentReceiverElement = false) + public async Task>> ReadAllWithAsync(bool documents = false, bool envelopeReceivers = false, bool history = false, bool documentReceiverElement = false) { - var envelopes = await _repository.ReadAllWithAsync(documents: documents, receivers: receivers, history: history, documentReceiverElement: documentReceiverElement); + var envelopes = await _repository.ReadAllWithAsync(documents: documents, envelopeReceivers: envelopeReceivers, history: history, documentReceiverElement: documentReceiverElement); var readDto = _mapper.MapOrThrow>(envelopes); return Successful(readDto); } - public async Task> ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withAll = false) + public async Task> ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withEnvelopeReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withUser = false, bool withAll = false) { - var envelope = await _repository.ReadByUuidAsync(uuid: uuid, signature: signature, withDocuments: withDocuments, withReceivers: withReceivers, withHistory: withHistory, withDocumentReceiverElement: withDocumentReceiverElement, withAll:withAll); + var envelope = await _repository.ReadByUuidAsync(uuid: uuid, signature: signature, withDocuments: withDocuments, withEnvelopeReceivers: withEnvelopeReceivers, withHistory: withHistory, withDocumentReceiverElement: withDocumentReceiverElement, withUser:withUser, withAll:withAll); if (envelope is null) return Failed(); diff --git a/EnvelopeGenerator.Domain/Constants.cs b/EnvelopeGenerator.Domain/Constants.cs deleted file mode 100644 index eeccbea2..00000000 --- a/EnvelopeGenerator.Domain/Constants.cs +++ /dev/null @@ -1,119 +0,0 @@ -namespace EnvelopeGenerator.Domain -{ - public class Constants - { - #region Status Fields - public enum EnvelopeStatus - { - Invalid = 0, - EnvelopeCreated = 1001, - EnvelopeSaved = 1002, - EnvelopeQueued = 1003, - EnvelopeSent = 1004, // Not used - EnvelopePartlySigned = 1005, - EnvelopeCompletelySigned = 1006, - EnvelopeReportCreated = 1007, - EnvelopeArchived = 1008, - EnvelopeDeleted = 1009, - AccessCodeRequested = 2001, - AccessCodeCorrect = 2002, - AccessCodeIncorrect = 2003, - DocumentOpened = 2004, - DocumentSigned = 2005, - SignatureConfirmed = 2006, - MessageInvitationSent = 3001, // Used by Trigger - MessageAccessCodeSent = 3002, - MessageConfirmationSent = 3003, - MessageDeletionSent = 3004, - MessageCompletionSent = 3005 - } - - public enum ElementStatus - { - Created = 0 - } - - public enum DocumentStatus - { - Created = 0, - Signed = 1 - } - - public enum ReceiverStatus - { - Unsigned = 0, - Signed = 1 - } - - #endregion - - #region Type Fields - - public enum ElementType - { - Signature = 1 - } - - public enum ContractType - { - Contract = 1, - ReadAndSign = 2 - } - - public enum ColorType - { - ReceiverColor1 = 1, - ReceiverColor2 = 2, - ReceiverColor3 = 3, - ReceiverColor4 = 4, - ReceiverColor5 = 5, - ReceiverColor6 = 6, - ReceiverColor7 = 7, - ReceiverColor8 = 8, - ReceiverColor9 = 9, - ReceiverColor10 = 10 - } - - public enum CertificationType - { - ElectronicSignature = 1, - QualifiedSignature = 2 - } - - public enum FinalEmailType - { - No = 0, - Yes = 1, - YesWithAttachment = 2 - } - - public enum PageOrientation - { - Portrait = 0, - Landscape = 1 - } - - public enum EmailTemplateType - { - DocumentReceived, - DocumentSigned, - DocumentDeleted, - DocumentCompleted, - DocumentAccessCodeReceived - } - - #endregion - - #region Constants - - public const string DATABASE = "DATABASE"; - public const string LOGCONFIG = "LOGCONFIG"; - public const string GDPICTURE = "GDPICTURE"; - - public const string GREEN_300 = "#bbf7d0"; - public const string RED_300 = "#fecaca"; - public const string ORANGE_300 = "#fed7aa"; - - #endregion - } -} \ No newline at end of file diff --git a/EnvelopeGenerator.Domain/Entities/Envelope.cs b/EnvelopeGenerator.Domain/Entities/Envelope.cs index 95e17c12..710ceb5c 100644 --- a/EnvelopeGenerator.Domain/Entities/Envelope.cs +++ b/EnvelopeGenerator.Domain/Entities/Envelope.cs @@ -1,4 +1,5 @@ -using EnvelopeGenerator.Common.My.Resources; +using EnvelopeGenerator.Common; +using EnvelopeGenerator.Common.My.Resources; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -84,8 +85,11 @@ namespace EnvelopeGenerator.Domain.Entities [Column("DMZ_MOVED")] public bool DmzMoved { get; set; } + /// + /// The sender of envelope + /// [ForeignKey("UserId")] - public Receiver? User { get; set; } + public DigitalData.UserManager.Domain.Entities.User? User { get; set; } [ForeignKey("EnvelopeTypeId")] public EnvelopeType? EnvelopeType { get; set; } @@ -111,7 +115,7 @@ namespace EnvelopeGenerator.Domain.Entities public IEnumerable? Documents { get; set; } - public IEnumerable? Receivers { get; set; } + public IEnumerable? EnvelopeReceivers { get; set; } public IEnumerable? History { get; set; } } diff --git a/EnvelopeGenerator.Domain/Entities/EnvelopeReceiver.cs b/EnvelopeGenerator.Domain/Entities/EnvelopeReceiver.cs index 6c78bfa5..beef339d 100644 --- a/EnvelopeGenerator.Domain/Entities/EnvelopeReceiver.cs +++ b/EnvelopeGenerator.Domain/Entities/EnvelopeReceiver.cs @@ -19,19 +19,19 @@ namespace EnvelopeGenerator.Domain.Entities public int Sequence { get; set; } [Column("NAME", TypeName = "nvarchar(128)")] - public string Name { get; set; } + public string? Name { get; set; } [Column("JOB_TITLE", TypeName = "nvarchar(128)")] - public string JobTitle { get; set; } + public string? JobTitle { get; set; } [Column("COMPANY_NAME", TypeName = "nvarchar(128)")] - public string CompanyName { get; set; } + public string? CompanyName { get; set; } [Column("PRIVATE_MESSAGE", TypeName = "nvarchar(max)")] - public string PrivateMessage { get; set; } + public string? PrivateMessage { get; set; } [Column("ACCESS_CODE", TypeName = "nvarchar(64)")] - public string AccessCode { get; set; } + public string? AccessCode { get; set; } [Required] [Column("ADDED_WHEN", TypeName = "datetime")] diff --git a/EnvelopeGenerator.Domain/Entities/Receiver.cs b/EnvelopeGenerator.Domain/Entities/Receiver.cs index a447e770..193ba895 100644 --- a/EnvelopeGenerator.Domain/Entities/Receiver.cs +++ b/EnvelopeGenerator.Domain/Entities/Receiver.cs @@ -22,7 +22,5 @@ namespace EnvelopeGenerator.Domain.Entities [Required] [Column("ADDED_WHEN", TypeName = "datetime")] public DateTime AddedWhen { get; set; } - - public IEnumerable? EnvelopeReceivers { get; set; } } } \ No newline at end of file diff --git a/EnvelopeGenerator.Domain/EnvelopeGenerator.Domain.csproj b/EnvelopeGenerator.Domain/EnvelopeGenerator.Domain.csproj index 49f45937..5f342ff9 100644 --- a/EnvelopeGenerator.Domain/EnvelopeGenerator.Domain.csproj +++ b/EnvelopeGenerator.Domain/EnvelopeGenerator.Domain.csproj @@ -10,4 +10,10 @@ + + + ..\..\WebUserManager\DigitalData.UserManager.Domain\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll + + + diff --git a/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeHistoryRepository.cs b/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeHistoryRepository.cs index 264e31b9..e442dac2 100644 --- a/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeHistoryRepository.cs +++ b/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeHistoryRepository.cs @@ -5,5 +5,6 @@ namespace EnvelopeGenerator.Infrastructure.Contracts { public interface IEnvelopeHistoryRepository : ICRUDRepository { + Task CountAsync(int? envelopeId = null, string? userReference = null, int? status = null); } } \ No newline at end of file diff --git a/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeReceiverRepository.cs b/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeReceiverRepository.cs index 816baf10..8f6b15dc 100644 --- a/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeReceiverRepository.cs +++ b/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeReceiverRepository.cs @@ -5,6 +5,14 @@ namespace EnvelopeGenerator.Infrastructure.Contracts { public interface IEnvelopeReceiverRepository : ICRUDRepository { - Task ReadAccessCodeByEnvelopeUuid(string envelopeUuid); + Task> ReadByUuidAsync(string uuid, bool withEnvelope = true, bool withReceiver = false); + + Task> ReadBySignatureAsync(string signature, bool withEnvelope = false, bool withReceiver = true); + + 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/Contracts/IEnvelopeRepository.cs b/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeRepository.cs index 132c7d06..facf3815 100644 --- a/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeRepository.cs +++ b/EnvelopeGenerator.Infrastructure/Contracts/IEnvelopeRepository.cs @@ -5,8 +5,8 @@ namespace EnvelopeGenerator.Infrastructure.Contracts { public interface IEnvelopeRepository : ICRUDRepository { - Task> ReadAllWithAsync(bool documents = false, bool receivers = false, bool history = false, bool documentReceiverElement = true); + Task> ReadAllWithAsync(bool documents = false, bool envelopeReceivers = false, bool history = false, bool documentReceiverElement = true); - Task ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withAll = false); + Task ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withEnvelopeReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withUser = false, bool withAll = false); } } \ No newline at end of file diff --git a/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj b/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj index 208721a6..fdeb1594 100644 --- a/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj +++ b/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj @@ -26,6 +26,9 @@ ..\..\WebCoreModules\DigitalData.Core.Infrastructure\bin\Debug\net7.0\DigitalData.Core.Infrastructure.dll + + ..\..\WebUserManager\DigitalData.UserManager.Domain\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll + diff --git a/EnvelopeGenerator.Infrastructure/Repositories/EnvelopeHistoryRepository.cs b/EnvelopeGenerator.Infrastructure/Repositories/EnvelopeHistoryRepository.cs index 7c19c82a..e27286fa 100644 --- a/EnvelopeGenerator.Infrastructure/Repositories/EnvelopeHistoryRepository.cs +++ b/EnvelopeGenerator.Infrastructure/Repositories/EnvelopeHistoryRepository.cs @@ -2,6 +2,7 @@ using DigitalData.UserManager.Infrastructure.Repositories; using EnvelopeGenerator.Domain.Entities; using EnvelopeGenerator.Infrastructure.Contracts; +using Microsoft.EntityFrameworkCore; namespace EnvelopeGenerator.Infrastructure.Repositories { @@ -10,5 +11,21 @@ namespace EnvelopeGenerator.Infrastructure.Repositories public EnvelopeHistoryRepository(EGDbContext dbContext) : base(dbContext) { } + + public async Task CountAsync(int? envelopeId = null, string? userReference = null, int? status = null) + { + var query = _dbSet.AsQueryable(); + + if (envelopeId is not null) + query = query.Where(eh => eh.EnvelopeId == envelopeId); + + if (userReference is not null) + query = query.Where(eh => eh.UserReference == userReference); + + if (status is not null) + query = query.Where(eh => eh.Status == status); + + return await query.CountAsync(); + } } } \ No newline at end of file diff --git a/EnvelopeGenerator.Infrastructure/Repositories/EnvelopeRepository.cs b/EnvelopeGenerator.Infrastructure/Repositories/EnvelopeRepository.cs index 0c6244eb..0059ac3e 100644 --- a/EnvelopeGenerator.Infrastructure/Repositories/EnvelopeRepository.cs +++ b/EnvelopeGenerator.Infrastructure/Repositories/EnvelopeRepository.cs @@ -23,7 +23,7 @@ namespace EnvelopeGenerator.Infrastructure.Repositories query = query.Include(e => e.Documents); if (receivers) - query = query.Include(e => e.Receivers); + query = query.Include(e => e.EnvelopeReceivers); if (history) query = query.Include(e => e.History); @@ -31,12 +31,12 @@ namespace EnvelopeGenerator.Infrastructure.Repositories return await query.ToListAsync(); } - public async Task ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withAll = false) + public async Task ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withEnvelopeReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withUser = false, bool withAll = false) { var query = _dbSet.Where(e => e.Uuid == uuid); if (signature is not null) - query = query.Where(e => e.Receivers != null && e.Receivers.Any(er => er.Receiver != null && er.Receiver.Signature == signature)); + query = query.Where(e => e.EnvelopeReceivers != null && e.EnvelopeReceivers.Any(er => er.Receiver != null && er.Receiver.Signature == signature)); if (withAll || withDocuments) if (withAll || withDocumentReceiverElement) @@ -44,8 +44,11 @@ namespace EnvelopeGenerator.Infrastructure.Repositories else query = query.Include(e => e.Documents); - if (withAll || withReceivers) - query = query.Include(e => e.Receivers!).ThenInclude(er => er.Receiver); + if (withAll || withEnvelopeReceivers) + query = query.Include(e => e.EnvelopeReceivers!).ThenInclude(er => er.Receiver); + + if (withAll || withUser) + query = query.Include(e => e.User!); if (withAll || withHistory) query = query.Include(e => e.History); diff --git a/EnvelopeGenerator.Infrastructure/Repositories/EnvlopeReceiverRepository.cs b/EnvelopeGenerator.Infrastructure/Repositories/EnvlopeReceiverRepository.cs index 0f528f41..b19ee540 100644 --- a/EnvelopeGenerator.Infrastructure/Repositories/EnvlopeReceiverRepository.cs +++ b/EnvelopeGenerator.Infrastructure/Repositories/EnvlopeReceiverRepository.cs @@ -3,6 +3,7 @@ using DigitalData.UserManager.Infrastructure.Repositories; using EnvelopeGenerator.Domain.Entities; using EnvelopeGenerator.Infrastructure.Contracts; using Microsoft.EntityFrameworkCore; +using System; namespace EnvelopeGenerator.Infrastructure.Repositories { @@ -12,14 +13,40 @@ namespace EnvelopeGenerator.Infrastructure.Repositories { } - public async Task ReadAccessCodeByEnvelopeUuid(string envelopeUuid) + private IQueryable ReadWhere(string? uuid = null, string? signature = null, bool withEnvelope = false, bool withReceiver = false) { - var accessCode = await _dbSet - .Where(er => er.Envelope != null && er.Envelope.Uuid == envelopeUuid) + var query = _dbSet.AsQueryable(); + + if(uuid is not null) + query = query.Where(er => er.Envelope != null && er.Envelope.Uuid == uuid); + + if (signature is not null) + query = query.Where(er => er.Receiver != null && er.Receiver.Signature == signature); + + if (withEnvelope) + 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; + } + + public async Task> ReadByUuidAsync(string uuid, bool withEnvelope = true, bool withReceiver = false) + => await ReadWhere(uuid: uuid, withEnvelope: withEnvelope, withReceiver: withReceiver).ToListAsync(); + + public async Task> ReadBySignatureAsync(string signature, bool withEnvelope = false, bool withReceiver = true) + => await ReadWhere(signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver).ToListAsync(); + + public async Task ReadByUuidSignatureAsync(string uuid, string signature, bool withEnvelope = true, bool withReceiver = true) + => await ReadWhere(uuid: uuid, signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver).FirstOrDefaultAsync(); + + public async Task ReadAccessCodeAsync(string uuid, string signature) + => await ReadWhere(uuid:uuid, signature:signature) .Select(er => er.AccessCode) .FirstOrDefaultAsync(); - return accessCode; - } + 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.Service/App.config b/EnvelopeGenerator.Service/App.config index a99d30ce..8949adf6 100644 --- a/EnvelopeGenerator.Service/App.config +++ b/EnvelopeGenerator.Service/App.config @@ -1,12 +1,12 @@ - -
+ +
- + diff --git a/EnvelopeGenerator.Service/EnvelopeGenerator.Service.vbproj b/EnvelopeGenerator.Service/EnvelopeGenerator.Service.vbproj index 20402aeb..0c6767b9 100644 --- a/EnvelopeGenerator.Service/EnvelopeGenerator.Service.vbproj +++ b/EnvelopeGenerator.Service/EnvelopeGenerator.Service.vbproj @@ -11,7 +11,7 @@ EnvelopeGenerator.Service 512 Console - v4.6.2 + v4.8 true diff --git a/EnvelopeGenerator.Service/My Project/Application.Designer.vb b/EnvelopeGenerator.Service/My Project/Application.Designer.vb index 8ab460ba..88dd01c7 100644 --- a/EnvelopeGenerator.Service/My Project/Application.Designer.vb +++ b/EnvelopeGenerator.Service/My Project/Application.Designer.vb @@ -1,10 +1,10 @@ '------------------------------------------------------------------------------ ' -' Dieser Code wurde von einem Tool generiert. -' Laufzeitversion:4.0.30319.42000 +' This code was generated by a tool. +' Runtime Version:4.0.30319.42000 ' -' Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn -' der Code erneut generiert wird. +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. ' '------------------------------------------------------------------------------ diff --git a/EnvelopeGenerator.Service/My Project/Resources.Designer.vb b/EnvelopeGenerator.Service/My Project/Resources.Designer.vb index b08dad5c..6bdb5758 100644 --- a/EnvelopeGenerator.Service/My Project/Resources.Designer.vb +++ b/EnvelopeGenerator.Service/My Project/Resources.Designer.vb @@ -1,10 +1,10 @@ '------------------------------------------------------------------------------ ' -' Dieser Code wurde von einem Tool generiert. -' Laufzeitversion:4.0.30319.42000 +' This code was generated by a tool. +' Runtime Version:4.0.30319.42000 ' -' Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn -' der Code erneut generiert wird. +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. ' '------------------------------------------------------------------------------ @@ -15,12 +15,12 @@ Imports System Namespace My.Resources - 'Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert - '-Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. - 'Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen - 'mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. + 'This class was auto-generated by the StronglyTypedResourceBuilder + 'class via a tool like ResGen or Visual Studio. + 'To add or remove a member, edit your .ResX file then rerun ResGen + 'with the /str option, or rebuild your VS project. ''' - ''' Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. + ''' A strongly-typed resource class, for looking up localized strings, etc. ''' - ''' Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. + ''' Returns the cached ResourceManager instance used by this class. ''' _ Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager @@ -47,8 +47,8 @@ Namespace My.Resources End Property ''' - ''' Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle - ''' Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. + ''' Overrides the current thread's CurrentUICulture property for all + ''' resource lookups using this strongly typed resource class. ''' _ Friend Property Culture() As Global.System.Globalization.CultureInfo diff --git a/EnvelopeGenerator.Service/My Project/Settings.Designer.vb b/EnvelopeGenerator.Service/My Project/Settings.Designer.vb index 223ade22..2b9420b0 100644 --- a/EnvelopeGenerator.Service/My Project/Settings.Designer.vb +++ b/EnvelopeGenerator.Service/My Project/Settings.Designer.vb @@ -1,10 +1,10 @@ '------------------------------------------------------------------------------ ' -' Dieser Code wurde von einem Tool generiert. -' Laufzeitversion:4.0.30319.42000 +' This code was generated by a tool. +' Runtime Version:4.0.30319.42000 ' -' Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn -' der Code erneut generiert wird. +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. ' '------------------------------------------------------------------------------ @@ -15,14 +15,14 @@ Option Explicit On Namespace My _ Partial Friend NotInheritable Class MySettings Inherits Global.System.Configuration.ApplicationSettingsBase Private Shared defaultInstance As MySettings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New MySettings()),MySettings) -#Region "Automatische My.Settings-Speicherfunktion" +#Region "My.Settings Auto-Save Functionality" #If _MyType = "WindowsForms" Then Private Shared addedHandler As Boolean diff --git a/EnvelopeGenerator.Web/Controllers/BaseController.cs b/EnvelopeGenerator.Web/Controllers/BaseController.cs index 28f90b18..0906c6e2 100644 --- a/EnvelopeGenerator.Web/Controllers/BaseController.cs +++ b/EnvelopeGenerator.Web/Controllers/BaseController.cs @@ -1,8 +1,6 @@ -using DigitalData.Modules.Logging; -using EnvelopeGenerator.Common; +using EnvelopeGenerator.Common; using EnvelopeGenerator.Web.Services; using Microsoft.AspNetCore.Mvc; -using System.Text; namespace EnvelopeGenerator.Web.Controllers { diff --git a/EnvelopeGenerator.Web/Controllers/ControllerBaseExtensions.cs b/EnvelopeGenerator.Web/Controllers/ControllerBaseExtensions.cs index 6616b3fa..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 5c4426bc..bd8d544d 100644 --- a/EnvelopeGenerator.Web/Controllers/HomeController.cs +++ b/EnvelopeGenerator.Web/Controllers/HomeController.cs @@ -1,162 +1,213 @@ 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; using Microsoft.AspNetCore.Mvc; -using System.Diagnostics; 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 { public class HomeController : BaseController { private readonly EnvelopeOldService envelopeOldService; - private readonly IConfiguration _config; private readonly IEnvelopeReceiverService _envRcvService; private readonly IEnvelopeService _envelopeService; + private readonly IEnvelopeHistoryService _historyService; + private readonly IKeyTranslationService _translator; - public HomeController(DatabaseService databaseService, EnvelopeOldService envelopeOldService, ILogger logger, IConfiguration configuration, IEnvelopeReceiverService envelopeReceiverService, IEnvelopeService envelopeService) : base(databaseService, logger) + 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; - _config = configuration; + _historyService = historyService; + _translator = keyTranslationService; } - - [HttpGet("/")] - public IActionResult Index() - { - return View(); - } - - [HttpPost("/")] - public IActionResult DebugEnvelopes([FromForm] string? password) - { - try - { - var passwordFromConfig = _config["Config:AdminPassword"]; - - if (passwordFromConfig == null) - { - ViewData["error"] = "No admin password configured!"; - return View("Index"); - } - - if (password != passwordFromConfig) - { - ViewData["error"] = "Wrong Password!"; - return View("Index"); - } - - List envelopes = envelopeOldService.LoadEnvelopes(); - - return View(envelopes); - } - catch(Exception ex) - { - _logger.LogError(ex, "Unexpected error"); - ViewData["error"] = "Unknown error!"; - return View("Index"); - } - } - + [HttpGet("/EnvelopeKey/{envelopeReceiverId}")] public async Task SendAccessCode([FromRoute] string envelopeReceiverId) { - var envelope = await _envelopeService.ReadByUuidAsync(envelopeReceiverId.GetEnvelopeUuid()); - - EnvelopeResponse response = await envelopeOldService.LoadEnvelope(envelopeReceiverId); - - if (response.Envelope.UseAccessCode) + ViewData["EnvelopeKey"] = envelopeReceiverId; + try { - bool accessCodeAlreadyRequested = database.Models.receiverModel.AccessCodeAlreadyRequested(response.Receiver.Email, response.Envelope.Id); + var erResult = await _envRcvService.ReadByEnvelopeReceiverIdAsync(envelopeReceiverId: envelopeReceiverId); + var er = erResult.Data; + var receiver = er?.Receiver; + var envelope = er?.Envelope; + var mailAddress = receiver?.EmailAddress; - if (!accessCodeAlreadyRequested) + if (erResult is null) { - // Send email with password - bool actionResult = database.Services.actionService.RequestAccessCode(response.Envelope, response.Receiver); - bool result = database.Services.emailService.SendDocumentAccessCodeReceivedEmail(response.Envelope, response.Receiver); + _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); + + bool accessCodeAlreadyRequested = database.Models.receiverModel.AccessCodeAlreadyRequested(response.Receiver.Email, response.Envelope.Id); + accessCodeAlreadyRequested = await _historyService.AccessCodeAlreadyRequested(envelopeId: envelope.Id, userReference: mailAddress); + if (!accessCodeAlreadyRequested) + { + // Send email with password + bool actionResult = database.Services.actionService.RequestAccessCode(response.Envelope, response.Receiver); + bool result = database.Services.emailService.SendDocumentAccessCodeReceivedEmail(response.Envelope, response.Receiver); + } + } + else + { + _logger.LogServiceMessage(erResult); + return this.ViewEnvelopeNotFound(); } } + catch(Exception ex) + { + _logger.LogEnvelopeError(envelopeEeceiverId: envelopeReceiverId, exception:ex, message: MessageKey.UnexpectedError.TranslateWith(_translator)); + return this.ViewInnerServiceError(); + } - return Redirect($"/EnvelopeKey/{envelopeReceiverId}/Locked"); + return Redirect($"{envelopeReceiverId}/Locked"); } - [HttpGet("/EnvelopeKey/{envelopeReceiverId}/Locked")] - public IActionResult EnvelopeLocked([FromRoute] string envelopeReceiverId) + [HttpGet("EnvelopeKey/{envelopeReceiverId}/Locked")] + public async Task EnvelopeLocked([FromRoute] string envelopeReceiverId) { - 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(); + + return View().WithData("EnvelopeKey", envelopeReceiverId); + } + catch(Exception ex) + { + _logger.LogEnvelopeError(envelopeEeceiverId: envelopeReceiverId, exception: ex); + return this.ViewInnerServiceError(); + } } [HttpPost("/EnvelopeKey/{envelopeReceiverId}/Locked")] public async Task LogInEnvelope([FromRoute] string envelopeReceiverId, [FromForm] string access_code) { - var decodedId = envelopeReceiverId.DecodeEnvelopeReceiverId(); - - _logger.LogInformation($"Envelope UUID: [{decodedId.EnvelopeUuid}]"); - _logger.LogInformation($"Receiver Signature: [{decodedId.ReceiverSignature}]"); - - var verification = await _envRcvService.VerifyAccessCode(decodedId.EnvelopeUuid, access_code); - EnvelopeResponse response = await envelopeOldService.LoadEnvelope(envelopeReceiverId); - - if (verification.IsSuccess) + try { - if (envelopeOldService.ReceiverAlreadySigned(response.Envelope, response.Receiver.Id)) + (string? uuid, string? signature) = envelopeReceiverId.DecodeEnvelopeReceiverId(); + + if(uuid is null || signature is null) { - return Redirect("/EnvelopeKey/{envelopeReceiverId}/Success"); + _logger.LogEnvelopeError(uuid: uuid, signature: signature, message: MessageKey.WrongEnvelopeReceiverId.TranslateWith(_translator)); + return BadRequest(_envelopeService.CreateMessage(false, MessageKey.WrongEnvelopeReceiverId.ToString())); } - var envelope = await _envelopeService.ReadByUuidAsync(uuid: decodedId.EnvelopeUuid, signature: decodedId.ReceiverSignature, withAll: true); - database.Services.actionService.EnterCorrectAccessCode(response.Envelope, response.Receiver); //for history - ViewData["EnvelopeKey"] = envelopeReceiverId; - ViewData["EnvelopeResponse"] = response; + _logger.LogInformation($"Envelope UUID: [{uuid}]\nReceiver Signature: [{signature}]"); - if (response.Envelope.Documents.Count() > 0) + var verification = await _envRcvService.VerifyAccessCodeAsync(uuid: uuid, signature: signature, accessCode: access_code); + var isVerified = verification.Data; + + EnvelopeResponse response = await envelopeOldService.LoadEnvelope(envelopeReceiverId); + + if (!verification.IsSuccess) { - var document = await envelopeOldService.GetDocument(response.Envelope.Documents[0].Id, envelopeReceiverId); - byte[] bytes = await envelopeOldService.GetDocumentContents(document); - ViewData["DocumentBytes"] = bytes; + _logger.LogServiceMessage(verification); + + if (verification.HasFlag(Flag.SecurityBreach)) + return Forbid(); + + return StatusCode(StatusCodes.Status500InternalServerError, verification.ClientMessages.Join()); + } + else if (isVerified) + { + if (envelopeOldService.ReceiverAlreadySigned(response.Envelope, response.Receiver.Id)) + { + return View("EnvelopeSigned"); + } + + var envelope = await _envelopeService.ReadByUuidAsync(uuid: uuid, signature: signature, withAll: true); + + database.Services.actionService.EnterCorrectAccessCode(response.Envelope, response.Receiver); //for history + ViewData["EnvelopeKey"] = envelopeReceiverId; + ViewData["EnvelopeResponse"] = response; + + if (response.Envelope.Documents.Count() > 0) + { + var document = await envelopeOldService.GetDocument(response.Envelope.Documents[0].Id, envelopeReceiverId); + byte[] bytes = await envelopeOldService.GetDocumentContents(document); + ViewData["DocumentBytes"] = bytes; + } + else + ViewData["DocumentBytes"] = null; + + var claims = new List + { + new Claim(ClaimTypes.NameIdentifier, uuid), + new Claim(ClaimTypes.Hash, signature), + }; + + var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + var authProperties = new AuthenticationProperties + { + }; + + await HttpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(claimsIdentity), + authProperties); + + return View("ShowEnvelope", envelope); } else - ViewData["DocumentBytes"] = null; - - var claims = new List { - new Claim(ClaimTypes.NameIdentifier, decodedId.EnvelopeUuid), - new Claim(ClaimTypes.Hash, decodedId.ReceiverSignature), - }; + database.Services.actionService.EnterIncorrectAccessCode(response.Envelope, response.Receiver); //for history + _logger.LogWarning(string.Join("\n", verification.Messages)); + return Unauthorized(); - var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); - var authProperties = new AuthenticationProperties - { - }; - - await HttpContext.SignInAsync( - CookieAuthenticationDefaults.AuthenticationScheme, - new ClaimsPrincipal(claimsIdentity), - authProperties); - - return View("ShowEnvelope", envelope); - } - else + } + } + catch(Exception ex) { - database.Services.actionService.EnterIncorrectAccessCode(response.Envelope, response.Receiver); //for history - return Unauthorized(); - + _logger.LogError(ex, MessageKey.UnexpectedError.ToString()); + return this.InnerServiceError(messageKey: MessageKey.UnexpectedError); } } [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] @@ -168,14 +219,6 @@ namespace EnvelopeGenerator.Web.Controllers return Ok(new { EnvelopeUuid = envelopeUuid, ReceiverSignature = receiverSignature }); } - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public IActionResult Error() - { - return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); - } - - [Authorize] - [HttpGet("test")] - public string Test() => "Test"; + public IActionResult Error404() => this.ViewError404(); } } \ No newline at end of file diff --git a/EnvelopeGenerator.Web/Controllers/Test/TestEnvelopeController.cs b/EnvelopeGenerator.Web/Controllers/Test/TestEnvelopeController.cs index e7d84eea..16785f38 100644 --- a/EnvelopeGenerator.Web/Controllers/Test/TestEnvelopeController.cs +++ b/EnvelopeGenerator.Web/Controllers/Test/TestEnvelopeController.cs @@ -14,13 +14,10 @@ namespace EnvelopeGenerator.Web.Controllers.Test } [NonAction] - public override Task GetAll() - { - return base.GetAll(); - } + public override Task GetAll() => base.GetAll(); [HttpGet] - public virtual async Task GetAll([FromQuery] string? envelopeKey = default, [FromQuery] bool withDocuments = false, [FromQuery] bool withReceivers = false, [FromQuery] bool withHistory = false, [FromQuery] bool withDocumentReceiverElement = false, [FromQuery] bool withAll = true) + public async Task GetAll([FromQuery] string? envelopeKey = default, [FromQuery] bool withDocuments = false, [FromQuery] bool withEnvelopeReceivers = false, [FromQuery] bool withHistory = false, [FromQuery] bool withDocumentReceiverElement = false, [FromQuery] bool withUser = false, [FromQuery] bool withAll = true) { if(envelopeKey is not null) { @@ -29,7 +26,7 @@ namespace EnvelopeGenerator.Web.Controllers.Test var envlopeServiceResult = await _service.ReadByUuidAsync( uuid: decoded.EnvelopeUuid, signature: decoded.ReceiverSignature, - withDocuments: withDocuments, withReceivers: withReceivers, withHistory: withHistory, withDocumentReceiverElement:withDocumentReceiverElement, withAll:withAll); + withDocuments: withDocuments, withEnvelopeReceivers: withEnvelopeReceivers, withHistory: withHistory, withDocumentReceiverElement:withDocumentReceiverElement, withUser:withUser, withAll:withAll); if (envlopeServiceResult.IsSuccess) { @@ -38,7 +35,7 @@ namespace EnvelopeGenerator.Web.Controllers.Test return NotFound(); } - var result = await _service.ReadAllWithAsync(documents: withDocuments, receivers: withReceivers, history: withHistory); + var result = await _service.ReadAllWithAsync(documents: withDocuments, envelopeReceivers: withEnvelopeReceivers, history: withHistory); if (result.IsSuccess) { return Ok(result); diff --git a/EnvelopeGenerator.Web/Controllers/Test/TestEnvelopeHistoryController.cs b/EnvelopeGenerator.Web/Controllers/Test/TestEnvelopeHistoryController.cs index 98c990f8..0164c04e 100644 --- a/EnvelopeGenerator.Web/Controllers/Test/TestEnvelopeHistoryController.cs +++ b/EnvelopeGenerator.Web/Controllers/Test/TestEnvelopeHistoryController.cs @@ -3,6 +3,7 @@ using EnvelopeGenerator.Application.Contracts; using EnvelopeGenerator.Application.DTOs; using EnvelopeGenerator.Domain.Entities; using EnvelopeGenerator.Infrastructure.Contracts; +using Microsoft.AspNetCore.Mvc; namespace EnvelopeGenerator.Web.Controllers.Test { @@ -10,7 +11,18 @@ namespace EnvelopeGenerator.Web.Controllers.Test { public TestEnvelopeHistoryController(ILogger logger, IEnvelopeHistoryService service) : base(logger, service) { + } + [HttpGet("Count")] + public async Task Count(int? envelopeId = null, string? userReference = null, int? status = null) + { + return Ok(await _service.CountAsync(envelopeId, userReference, status)); + } + + [HttpGet("is-ac-req")] + public async Task AccessCodeAlreadyRequested(int envelopeId, string userReference) + { + return Ok(await _service.AccessCodeAlreadyRequested(envelopeId, userReference)); } } } \ No newline at end of file diff --git a/EnvelopeGenerator.Web/Controllers/Test/TestEnvelopeReceiverController.cs b/EnvelopeGenerator.Web/Controllers/Test/TestEnvelopeReceiverController.cs index 073720ef..6d70ddca 100644 --- a/EnvelopeGenerator.Web/Controllers/Test/TestEnvelopeReceiverController.cs +++ b/EnvelopeGenerator.Web/Controllers/Test/TestEnvelopeReceiverController.cs @@ -1,8 +1,11 @@ using DigitalData.Core.API; +using DigitalData.Core.Application; using EnvelopeGenerator.Application.Contracts; using EnvelopeGenerator.Application.DTOs; +using EnvelopeGenerator.Application.Services; using EnvelopeGenerator.Domain.Entities; using EnvelopeGenerator.Infrastructure.Contracts; +using Microsoft.AspNetCore.Mvc; namespace EnvelopeGenerator.Web.Controllers.Test { @@ -10,7 +13,38 @@ namespace EnvelopeGenerator.Web.Controllers.Test { public TestEnvelopeReceiverController(ILogger logger, IEnvelopeReceiverService service) : base(logger, service) { + } + [HttpGet("verify-access-code/{envelope_receiver_id}")] + public async Task VerifyAccessCode([FromRoute] string envelope_receiver_id, [FromQuery] string access_code) + { + var verification = await _service.VerifyAccessCodeAsync(envelopeReceiverId:envelope_receiver_id, accessCode: access_code); + + if (verification.IsSuccess) + return Ok(verification); + else if (verification.HasFlag(Flag.SecurityBreach)) + return Forbid(); + else if (verification.HasFlag(Flag.SecurityBreachOrDataIntegrity)) + return Conflict(); + else + return this.InnerServiceError(verification); + } + + [HttpGet("e-r-id/{envelope_receiver_id}")] + public async Task GetByEnvelopeReceiverId([FromRoute] string envelope_receiver_id) + { + var er_result = await _service.ReadByEnvelopeReceiverIdAsync(envelopeReceiverId: envelope_receiver_id); + if (er_result.IsSuccess) + return Ok(er_result); + else + return this.InnerServiceError(er_result); + } + + [HttpGet("decode")] + public IActionResult DecodeEnvelopeReceiverId(string envelopeReceiverId) + { + var decoded = envelopeReceiverId.DecodeEnvelopeReceiverId(); + return Ok(new { uuid = decoded.EnvelopeUuid, signature = decoded.ReceiverSignature }); } } } \ No newline at end of file diff --git a/EnvelopeGenerator.Web/Controllers/Test/TestViewController.cs b/EnvelopeGenerator.Web/Controllers/Test/TestViewController.cs new file mode 100644 index 00000000..46bc0b36 --- /dev/null +++ b/EnvelopeGenerator.Web/Controllers/Test/TestViewController.cs @@ -0,0 +1,60 @@ +using EnvelopeGenerator.Application.Contracts; +using EnvelopeGenerator.Common; +using EnvelopeGenerator.Web.Services; +using Microsoft.AspNetCore.Mvc; + +namespace EnvelopeGenerator.Web.Controllers.Test +{ + public class TestViewController : BaseController + { + private readonly EnvelopeOldService envelopeOldService; + private readonly IConfiguration _config; + private readonly IEnvelopeReceiverService _envRcvService; + private readonly IEnvelopeService _envelopeService; + + public TestViewController(DatabaseService databaseService, EnvelopeOldService envelopeOldService, ILogger logger, IConfiguration configuration, IEnvelopeReceiverService envelopeReceiverService, IEnvelopeService envelopeService) : base(databaseService, logger) + { + this.envelopeOldService = envelopeOldService; + _envRcvService = envelopeReceiverService; + _envelopeService = envelopeService; + _config = configuration; + } + + [HttpGet("/")] + public IActionResult Index() + { + return View("Index"); + } + + [HttpPost("/")] + public IActionResult DebugEnvelopes([FromForm] string? password) + { + try + { + var passwordFromConfig = _config["Config:AdminPassword"]; + + if (passwordFromConfig == null) + { + ViewData["error"] = "No admin password configured!"; + return View("Index"); + } + + if (password != passwordFromConfig) + { + ViewData["error"] = "Wrong Password!"; + return View("Index"); + } + + List envelopes = envelopeOldService.LoadEnvelopes(); + + return View("DebugEnvelopes", envelopes); + } + catch(Exception ex) + { + _logger.LogError(ex, "Unexpected error"); + ViewData["error"] = "Unknown error!"; + return View("Index"); + } + } + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj b/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj index 77d36604..220e8fc6 100644 --- a/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj +++ b/EnvelopeGenerator.Web/EnvelopeGenerator.Web.csproj @@ -6,6 +6,10 @@ enable + + + + @@ -25,6 +29,9 @@ + + + @@ -66,6 +73,15 @@ ..\..\DDModules\Logging\bin\Debug\DigitalData.Modules.Logging.dll + + ..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Application.dll + + + ..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll + + + ..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Infrastructure.dll + D:\ProgramFiles\GdPicture.NET 14\Redist\GdPicture.NET (.NET Framework 4.5)\GdPicture.NET.14.dll @@ -78,6 +94,9 @@ Always + + PreserveNewest + Always diff --git a/EnvelopeGenerator.Web/MessageKey.cs b/EnvelopeGenerator.Web/MessageKey.cs new file mode 100644 index 00000000..a1b2f6de --- /dev/null +++ b/EnvelopeGenerator.Web/MessageKey.cs @@ -0,0 +1,12 @@ +namespace EnvelopeGenerator.Web +{ + public enum MessageKey + { + ServiceOutputNullError, + UnexpectedError, + FailedToSendAccessCode, + WrongEnvelopeReceiverId, //the value should be about URL (like URL is not existing) as a part of security. + DataIntegrityError, + NonDecodableEnvelopeReceiverId + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.Web/Models/ContactLink.cs b/EnvelopeGenerator.Web/Models/ContactLink.cs new file mode 100644 index 00000000..8221d4b4 --- /dev/null +++ b/EnvelopeGenerator.Web/Models/ContactLink.cs @@ -0,0 +1,60 @@ +namespace EnvelopeGenerator.Web.Models +{ + /// + /// Represents a hyperlink for contact purposes with various HTML attributes. + /// + public class ContactLink + { + /// + /// Gets or sets the label of the hyperlink. + /// + public string Label { get; init; } = "Contact"; + + /// + /// Gets or sets the URL that the hyperlink points to. + /// + public string Href { get; set; } = string.Empty; + + /// + /// Gets or sets the target where the hyperlink should open. + /// Commonly used values are "_blank", "_self", "_parent", "_top". + /// + public string Target { get; set; } = "_blank"; + + /// + /// Gets or sets the relationship of the linked URL as space-separated link types. + /// Examples include "nofollow", "noopener", "noreferrer". + /// + public string Rel { get; set; } = string.Empty; + + /// + /// Gets or sets the filename that should be downloaded when clicking the hyperlink. + /// This attribute will only have an effect if the href attribute is set. + /// + public string Download { get; set; } = string.Empty; + + /// + /// Gets or sets the language of the linked resource. Useful when linking to + /// content in another language. + /// + public string HrefLang { get; set; } = "en"; + + /// + /// Gets or sets the MIME type of the linked URL. Helps browsers to handle + /// the type correctly when the link is clicked. + /// + public string Type { get; set; } = string.Empty; + + /// + /// Gets or sets additional information about the hyperlink, typically viewed + /// as a tooltip when the mouse hovers over the link. + /// + public string Title { get; set; } = string.Empty; + + /// + /// Gets or sets an identifier for the hyperlink, unique within the HTML document. + /// + public string Id { get; set; } = string.Empty; + } + +} diff --git a/EnvelopeGenerator.Web/Models/ErrorViewModel.cs b/EnvelopeGenerator.Web/Models/ErrorViewModel.cs index a4489520..2173e705 100644 --- a/EnvelopeGenerator.Web/Models/ErrorViewModel.cs +++ b/EnvelopeGenerator.Web/Models/ErrorViewModel.cs @@ -2,8 +2,10 @@ namespace EnvelopeGenerator.Web.Models { public class ErrorViewModel { - public string? RequestId { get; set; } + public string Title { get; init; } = "404"; - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + public string Subtitle { get; init; } = "Hmmm..."; + + public string Body { get; init; } = "It looks like one of the developers fell asleep"; } } \ No newline at end of file diff --git a/EnvelopeGenerator.Web/Program.cs b/EnvelopeGenerator.Web/Program.cs index c254425b..84d28b61 100644 --- a/EnvelopeGenerator.Web/Program.cs +++ b/EnvelopeGenerator.Web/Program.cs @@ -13,6 +13,8 @@ using NLog.Web; using DigitalData.Core.API; using Microsoft.AspNetCore.Authentication.Cookies; using DigitalData.Core.Application; +using DigitalData.UserManager.Application.MappingProfiles; +using EnvelopeGenerator.Web.Models; var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); logger.Info("Logging initialized!"); @@ -29,9 +31,10 @@ try // Add higher order services builder.Services.AddScoped(); - // Add services to the container. + // Add controllers and razor views builder.Services.AddControllersWithViews(options => { + //remove option for Test*Controller options.Conventions.Add(new RemoveIfControllerConvention() .AndIf(c => c.ControllerName.StartsWith("Test")) .AndIf(c => !builder.Configuration.GetValue("AddTestControllers"))); @@ -86,6 +89,7 @@ try //Auto mapping profiles builder.Services.AddAutoMapper(typeof(BasicDtoMappingProfile).Assembly); + builder.Services.AddAutoMapper(typeof(UserMappingProfile).Assembly); builder.Services.Configure(options => { @@ -102,10 +106,10 @@ try builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { - options.Cookie.HttpOnly = true; // Makes the cookie inaccessible to client-side scripts for security + options.Cookie.HttpOnly = true; // Makes the cookie inaccessible to client-side scripts for security options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; // Ensures cookies are sent over HTTPS only options.Cookie.SameSite = SameSiteMode.Strict; // Protects against CSRF attacks by restricting how cookies are sent with requests from external sites - // Set up event handlers for dynamic login and logout paths + options.Events = new CookieAuthenticationEvents { OnRedirectToLogin = context => @@ -129,6 +133,8 @@ try }; }); + builder.Services.AddSingleton(_ => builder.Configuration.GetSection("ContactLink").Get() ?? new ContactLink()); + builder.Services.AddCookieConsentSettings(); var app = builder.Build(); @@ -154,7 +160,7 @@ try app.UseAuthorization(); app.MapControllers(); - + app.MapFallbackToController("Error404", "Home"); app.Run(); } catch(Exception ex) diff --git a/EnvelopeGenerator.Web/Views/Home/EnvelopeLocked.cshtml b/EnvelopeGenerator.Web/Views/Home/EnvelopeLocked.cshtml index 30861534..c3de39c5 100644 --- a/EnvelopeGenerator.Web/Views/Home/EnvelopeLocked.cshtml +++ b/EnvelopeGenerator.Web/Views/Home/EnvelopeLocked.cshtml @@ -1,7 +1,6 @@ @{ ViewData["Title"] = "Dokument geschützt"; } -
@@ -21,7 +20,7 @@
- +
diff --git a/EnvelopeGenerator.Web/Views/Home/ShowEnvelope.cshtml b/EnvelopeGenerator.Web/Views/Home/ShowEnvelope.cshtml index bc2d46ff..7b339119 100644 --- a/EnvelopeGenerator.Web/Views/Home/ShowEnvelope.cshtml +++ b/EnvelopeGenerator.Web/Views/Home/ShowEnvelope.cshtml @@ -9,7 +9,8 @@ { var envelope = Model.Data; var document = envelope.Documents?.FirstOrDefault(); - var receiver = envelope.Receivers?.FirstOrDefault(); + var receiver = envelope.EnvelopeReceivers?.FirstOrDefault(); + var sender = envelope.User; var receiverName = receiver?.Name ?? string.Empty; var pages = document?.Elements?.Select(e => e.Page) ?? Array.Empty(); var stPageIndexes = string.Join(pages.Count() > 1 ? ", " : "", pages.Take(pages.Count() - 1)) @@ -37,7 +38,7 @@
@($"{envelope.Title}")

@($"Sie haben {(pages.Count())} Briefe zu unterschreiben. Bitte prüfen Sie die Seiten {stPageIndexes}.")

-

Erstellt am @envelope.AddedWhen

+

Erstellt am @envelope.AddedWhen von @sender?.Prename @sender?.Name. Sie können den Absender über @sender?.Email kontaktieren.

@@ -62,9 +63,9 @@ { var envelopeResponse = ViewData["EnvelopeResponse"]; var settings = new Newtonsoft.Json.JsonSerializerSettings - { - ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() - }; + { + ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() + }; var envelopeResponseJson = Newtonsoft.Json.JsonConvert.SerializeObject(envelopeResponse, settings); var documentBase64String = Convert.ToBase64String(documentBytes); diff --git a/EnvelopeGenerator.Web/Views/Shared/Error.cshtml b/EnvelopeGenerator.Web/Views/Shared/Error.cshtml deleted file mode 100644 index a1e04783..00000000 --- a/EnvelopeGenerator.Web/Views/Shared/Error.cshtml +++ /dev/null @@ -1,25 +0,0 @@ -@model ErrorViewModel -@{ - ViewData["Title"] = "Error"; -} - -

Error.

-

An error occurred while processing your request.

- -@if (Model.ShowRequestId) -{ -

- Request ID: @Model.RequestId -

-} - -

Development Mode

-

- Swapping to Development environment will display more detailed information about the error that occurred. -

-

- The Development environment shouldn't be enabled for deployed applications. - It can result in displaying sensitive information from exceptions to end users. - For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development - and restarting the app. -

diff --git a/EnvelopeGenerator.Web/Views/Shared/_Error.cshtml b/EnvelopeGenerator.Web/Views/Shared/_Error.cshtml new file mode 100644 index 00000000..f72914f1 --- /dev/null +++ b/EnvelopeGenerator.Web/Views/Shared/_Error.cshtml @@ -0,0 +1,69 @@ +@{ + Layout = null; +} +@model ErrorViewModel +@{ + var errorModel = Model ?? new ErrorViewModel(); +} +@inject ContactLink _contactLink + + + + + + + Document + + + + +
+
+
+
+ +
+
+
+
+
+ +
+
@errorModel.Title
+
@errorModel.Subtitle
+
@errorModel.Body
+ @_contactLink.Label +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+ + + + + \ No newline at end of file diff --git a/EnvelopeGenerator.Web/Views/Home/DebugEnvelopes.cshtml b/EnvelopeGenerator.Web/Views/TestView/DebugEnvelopes.cshtml similarity index 100% rename from EnvelopeGenerator.Web/Views/Home/DebugEnvelopes.cshtml rename to EnvelopeGenerator.Web/Views/TestView/DebugEnvelopes.cshtml diff --git a/EnvelopeGenerator.Web/Views/Home/Index.cshtml b/EnvelopeGenerator.Web/Views/TestView/Index.cshtml similarity index 100% rename from EnvelopeGenerator.Web/Views/Home/Index.cshtml rename to EnvelopeGenerator.Web/Views/TestView/Index.cshtml diff --git a/EnvelopeGenerator.Web/Views/_ViewStart.cshtml b/EnvelopeGenerator.Web/Views/_ViewStart.cshtml index a5f10045..1af6e494 100644 --- a/EnvelopeGenerator.Web/Views/_ViewStart.cshtml +++ b/EnvelopeGenerator.Web/Views/_ViewStart.cshtml @@ -1,3 +1,3 @@ @{ Layout = "_Layout"; -} +} \ No newline at end of file diff --git a/EnvelopeGenerator.Web/appsettings.json b/EnvelopeGenerator.Web/appsettings.json index e761e269..5dcb02dc 100644 --- a/EnvelopeGenerator.Web/appsettings.json +++ b/EnvelopeGenerator.Web/appsettings.json @@ -21,21 +21,33 @@ "type": "File", "fileName": "E:\\EnvelopeGenerator\\Logs\\${shortdate}-ECM.EnvelopeGenerator.Web-Error.log", "maxArchiveDays": 30 + }, + "criticalLogs": { + "type": "File", + "fileName": "E:\\EnvelopeGenerator\\Logs\\${shortdate}-ECM.EnvelopeGenerator.Web-Critical.log", + "maxArchiveDays": 30 } }, + // Trace, Debug, Info, Warn, Error and *Fatal* "rules": [ { "logger": "*", "minLevel": "Info", + "maxLevel": "Warn", "writeTo": "infoLogs" }, { "logger": "*", - "minLevel": "Error", + "level": "Error", "writeTo": "errorLogs" }, { - "logger": "Namespace.Controllers.*", + "logger": "*", + "level": "Fatal", + "writeTo": "criticalLogs" + }, + { + "logger": "EnvelopeGenerator.Web.Controllers.*", "minLevel": "Error", "writeTo": "errorLogs", "final": true @@ -68,5 +80,12 @@ "ModalId": "bootstrapCookieConsentSettingsModal", "AlsoUseLocalStorage": false, "Categories": [ "necessary" ] + }, + "ContactLink": { + "Label": "Kontakt", + "Href": "https://digitaldata.works/", + "HrefLang": "de", + "Target": "_blank", + "Title": "Digital Data GmbH" } } \ No newline at end of file diff --git a/EnvelopeGenerator.Web/wwwroot/css/error-space.css b/EnvelopeGenerator.Web/wwwroot/css/error-space.css new file mode 100644 index 00000000..9a3391b0 --- /dev/null +++ b/EnvelopeGenerator.Web/wwwroot/css/error-space.css @@ -0,0 +1,391 @@ +html, +body { + height: 100%; + width: 100%; + margin: 0px; + 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: var(--moon-left-position); + width: 900px; + height: 900px; + content: ''; + border-radius: 100%; + box-shadow: 0px 0px 30px -4px rgba(0, 0, 0, 0.5); +} + +.moon__crater { + position: absolute; + content: ''; + border-radius: 100%; + background: linear-gradient(90deg, rgba(122, 122, 122, 1) 38%, rgba(195, 195, 195, 1) 100%); + opacity: 0.6; +} + +.moon__crater1 { + top: 250px; + left: calc(var(--moon-left-position) + 800px); + width: 60px; + height: 180px; +} + +.moon__crater2 { + top: 650px; + left: calc(var(--moon-left-position) + 640px); + width: 40px; + height: 80px; + transform: rotate(55deg); +} + +.moon__crater3 { + top: -20px; + left: calc(var(--moon-left-position) + 340px); + width: 65px; + height: 120px; + transform: rotate(250deg); +} + +.star { + background: grey; + position: absolute; + width: 5px; + height: 5px; + content: ''; + border-radius: 100%; + transform: rotate(250deg); + opacity: 0.4; + animation-name: shimmer; + animation-duration: 1.5s; + animation-iteration-count: infinite; + animation-direction: alternate; +} + +@keyframes shimmer { + from { + opacity: 0; + } + + to { + opacity: 0.7; + } +} + +.star1 { + top: 40%; + left: 50%; + animation-delay: 1s; +} + +.star2 { + top: 60%; + left: 90%; + animation-delay: 3s; +} + +.star3 { + top: 10%; + left: 70%; + animation-delay: 2s; +} + +.star4 { + top: 90%; + left: 40%; +} + +.star5 { + top: 20%; + left: 30%; + animation-delay: 0.5s; +} + +.error { + position: absolute; + left: 100px; + top: 400px; + transform: translateY(-60%); + font-family: 'Montserrat', sans-serif; + color: #363e49; +} + +.error__title { + font-size: 10em; +} + +.error__subtitle { + font-size: 2em; +} + +.error__description { + opacity: 0.5; +} + +.error__button { + display: inline-block; + margin-top: 3em; + margin-right: 0.5em; + padding: 0.5em 2em; + outline: none; + border: 2px solid #2f3640; + background-color: transparent; + border-radius: 8em; + color: #576375; + cursor: pointer; + transition-duration: 0.2s; + font-size: 0.75em; + font-family: 'Montserrat', sans-serif; + text-decoration: none; +} + +.error__button:hover { + color: #21252c; +} + +.error__button--active { + background-color: #e67e22; + border: 2px solid #e67e22; + color: white; +} + +.error__button--active:hover { + box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.5); + color: white; +} + +.astronaut { + position: absolute; + width: 185px; + height: 300px; + left: 70%; + top: 50%; + transform: translate(-50%, -50%) rotate(20deg) scale(1.2); +} + +.astronaut__head { + background-color: white; + position: absolute; + top: 60px; + left: 60px; + width: 60px; + height: 60px; + content: ''; + border-radius: 2em; +} + +.astronaut__head-visor-flare1 { + background-color: #7f8fa6; + position: absolute; + top: 28px; + left: 40px; + width: 10px; + height: 10px; + content: ''; + border-radius: 2em; + opacity: 0.5; +} + +.astronaut__head-visor-flare2 { + background-color: #718093; + position: absolute; + top: 40px; + left: 38px; + width: 5px; + height: 5px; + content: ''; + border-radius: 2em; + opacity: 0.3; +} + +.astronaut__backpack { + background-color: #bfbfbf; + position: absolute; + top: 90px; + left: 47px; + width: 86px; + height: 90px; + content: ''; + border-radius: 8px; +} + +.astronaut__body { + background-color: #e6e6e6; + position: absolute; + top: 115px; + left: 55px; + width: 70px; + height: 80px; + content: ''; + border-radius: 8px; +} + +.astronaut__body__chest { + background-color: #d9d9d9; + position: absolute; + top: 140px; + left: 68px; + width: 45px; + height: 25px; + content: ''; + border-radius: 6px; +} + +.astronaut__arm-left1 { + background-color: #e6e6e6; + position: absolute; + top: 127px; + left: 9px; + width: 65px; + height: 20px; + content: ''; + border-radius: 8px; + transform: rotate(-30deg); +} + +.astronaut__arm-left2 { + background-color: #e6e6e6; + position: absolute; + top: 102px; + left: 7px; + width: 20px; + height: 45px; + content: ''; + border-radius: 8px; + transform: rotate(-12deg); + border-top-left-radius: 8em; + border-top-right-radius: 8em; +} + +.astronaut__arm-right1 { + background-color: #e6e6e6; + position: absolute; + top: 113px; + left: 100px; + width: 65px; + height: 20px; + content: ''; + border-radius: 8px; + transform: rotate(-10deg); +} + +.astronaut__arm-right2 { + background-color: #e6e6e6; + position: absolute; + top: 78px; + left: 141px; + width: 20px; + height: 45px; + content: ''; + border-radius: 8px; + transform: rotate(-10deg); + border-top-left-radius: 8em; + border-top-right-radius: 8em; +} + +.astronaut__arm-thumb-left { + background-color: #e6e6e6; + position: absolute; + top: 110px; + left: 21px; + width: 10px; + height: 6px; + content: ''; + border-radius: 8em; + transform: rotate(-35deg); +} + +.astronaut__arm-thumb-right { + background-color: #e6e6e6; + position: absolute; + top: 90px; + left: 133px; + width: 10px; + height: 6px; + content: ''; + border-radius: 8em; + transform: rotate(20deg); +} + +.astronaut__wrist-left { + background-color: #e67e22; + position: absolute; + top: 122px; + left: 6.5px; + width: 21px; + height: 4px; + content: ''; + border-radius: 8em; + transform: rotate(-15deg); +} + +.astronaut__wrist-right { + background-color: #e67e22; + position: absolute; + top: 98px; + left: 141px; + width: 21px; + height: 4px; + content: ''; + border-radius: 8em; + transform: rotate(-10deg); +} + +.astronaut__leg-left { + background-color: #e6e6e6; + position: absolute; + top: 188px; + left: 50px; + width: 23px; + height: 75px; + content: ''; + transform: rotate(10deg); +} + +.astronaut__leg-right { + background-color: #e6e6e6; + position: absolute; + top: 188px; + left: 108px; + width: 23px; + height: 75px; + content: ''; + transform: rotate(-10deg); +} + +.astronaut__foot-left { + background-color: white; + position: absolute; + top: 240px; + left: 43px; + width: 28px; + height: 20px; + content: ''; + transform: rotate(10deg); + border-radius: 3px; + border-top-left-radius: 8em; + border-top-right-radius: 8em; + border-bottom: 4px solid #e67e22; +} + +.astronaut__foot-right { + background-color: white; + position: absolute; + top: 240px; + left: 111px; + width: 28px; + height: 20px; + content: ''; + transform: rotate(-10deg); + border-radius: 3px; + border-top-left-radius: 8em; + border-top-right-radius: 8em; + border-bottom: 4px solid #e67e22; +} \ No newline at end of file diff --git a/EnvelopeGenerator.Web/wwwroot/error.html b/EnvelopeGenerator.Web/wwwroot/error.html new file mode 100644 index 00000000..41ba16de --- /dev/null +++ b/EnvelopeGenerator.Web/wwwroot/error.html @@ -0,0 +1,61 @@ + + + + + + + Document + + + + +
+
+
+
+ +
+
+
+
+
+ +
+
404
+
Hmmm...
+
It looks like one of the developers fell asleep
+ CONTACT +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+ + + + + \ No newline at end of file diff --git a/EnvelopeGenerator.Web/wwwroot/favicon.ico b/EnvelopeGenerator.Web/wwwroot/favicon.ico index 63e859b4..60c2c4ab 100644 Binary files a/EnvelopeGenerator.Web/wwwroot/favicon.ico and b/EnvelopeGenerator.Web/wwwroot/favicon.ico differ diff --git a/EnvelopeGenerator.Web/wwwroot/js/app.js b/EnvelopeGenerator.Web/wwwroot/js/app.js index bd32fe6b..2756c48f 100644 --- a/EnvelopeGenerator.Web/wwwroot/js/app.js +++ b/EnvelopeGenerator.Web/wwwroot/js/app.js @@ -34,11 +34,7 @@ class App { this.currentReceiver = this.envelopeResponse.receiver // Load the document from the filestore - console.debug('Loading document from filestore') - const documentResponse = await this.Network.getDocument( - this.envelopeKey, - this.currentDocument.id - ) + const documentResponse = this.documentBytes if (documentResponse.fatal || documentResponse.error) { console.error(documentResponse.error) @@ -48,11 +44,9 @@ class App { icon: 'error', }) } - console.log(documentResponse.data) - console.log(this.documentBytes) const arrayBuffer = this.documentBytes - console.log(arrayBuffer) + // Load PSPDFKit this.Instance = await this.UI.loadPSPDFKit(arrayBuffer, this.container) this.UI.configurePSPDFKit(this.Instance, this.handleClick.bind(this)) diff --git a/EnvelopeGenerator.Web/wwwroot/js/error-space.js b/EnvelopeGenerator.Web/wwwroot/js/error-space.js new file mode 100644 index 00000000..80f1cae3 --- /dev/null +++ b/EnvelopeGenerator.Web/wwwroot/js/error-space.js @@ -0,0 +1,77 @@ +function drawVisor() { + const canvas = document.getElementById('visor'); + const ctx = canvas.getContext('2d'); + + ctx.beginPath(); + ctx.moveTo(5, 45); + ctx.bezierCurveTo(15, 64, 45, 64, 55, 45); + + ctx.lineTo(55, 20); + ctx.bezierCurveTo(55, 15, 50, 10, 45, 10); + + ctx.lineTo(15, 10); + + ctx.bezierCurveTo(15, 10, 5, 10, 5, 20); + ctx.lineTo(5, 45); + + ctx.fillStyle = '#2f3640'; + ctx.strokeStyle = '#f5f6fa'; + ctx.fill(); + ctx.stroke(); +} + +const cordCanvas = document.getElementById('cord'); +const ctx = cordCanvas.getContext('2d'); + +let y1 = 160; +let y2 = 100; +let y3 = 100; + +let y1Forward = true; +let y2Forward = false; +let y3Forward = true; + +function animate() { + requestAnimationFrame(animate); + ctx.clearRect(0, 0, innerWidth, innerHeight); + + ctx.beginPath(); + ctx.moveTo(130, 170); + ctx.bezierCurveTo(250, y1, 345, y2, 400, y3); + + ctx.strokeStyle = 'white'; + ctx.lineWidth = 8; + ctx.stroke(); + + + if (y1 === 100) { + y1Forward = true; + } + + if (y1 === 300) { + y1Forward = false; + } + + if (y2 === 100) { + y2Forward = true; + } + + if (y2 === 310) { + y2Forward = false; + } + + if (y3 === 100) { + y3Forward = true; + } + + if (y3 === 317) { + y3Forward = false; + } + + y1Forward ? y1 += 1 : y1 -= 1; + y2Forward ? y2 += 1 : y2 -= 1; + y3Forward ? y3 += 1 : y3 -= 1; +} + +drawVisor(); +animate(); \ No newline at end of file