This commit is contained in:
SchreiberM 2024-04-26 13:17:02 +02:00
commit a7cf500858
56 changed files with 1418 additions and 362 deletions

View File

@ -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<IEnvelopeHistoryRepository, EnvelopeHistoryDto, EnvelopeHistory, long>
{
Task<int> CountAsync(int? envelopeId = null, string? userReference = null, int? status = null);
Task<bool> AccessCodeAlreadyRequested(int envelopeId, string userReference);
}
}

View File

@ -7,6 +7,19 @@ namespace EnvelopeGenerator.Application.Contracts
{
public interface IEnvelopeReceiverService : IBasicCRUDService<IEnvelopeReceiverRepository, EnvelopeReceiverDto, EnvelopeReceiver, int>
{
Task<IServiceMessage> VerifyAccessCode(string envelopeUuid, string accessCode);
Task<IServiceResult<IEnumerable<EnvelopeReceiverDto>>> ReadByUuidAsync(string uuid, bool withEnvelope = true, bool withReceiver = false);
Task<IServiceResult<IEnumerable<EnvelopeReceiverDto>>> ReadBySignatureAsync(string signature, bool withEnvelope = false, bool withReceiver = true);
Task<IServiceResult<EnvelopeReceiverDto>> ReadByUuidSignatureAsync(string uuid, string signature, bool withEnvelope = true, bool withReceiver = true);
Task<IServiceResult<EnvelopeReceiverDto>> ReadByEnvelopeReceiverIdAsync(string envelopeReceiverId, bool withEnvelope = true, bool withReceiver = true);
Task<IServiceResult<bool>> VerifyAccessCodeAsync(string uuid, string signature, string accessCode);
Task<IServiceResult<bool>> VerifyAccessCodeAsync(string envelopeReceiverId, string accessCode);
Task<IServiceResult<bool>> IsExisting(string envelopeReceiverId);
}
}

View File

@ -7,8 +7,8 @@ namespace EnvelopeGenerator.Application.Contracts
{
public interface IEnvelopeService : IBasicCRUDService<IEnvelopeRepository, EnvelopeDto, Envelope, int>
{
Task<IServiceResult<IEnumerable<EnvelopeDto>>> ReadAllWithAsync(bool documents = false, bool receivers = false, bool history = false, bool documentReceiverElement = false);
Task<IServiceResult<IEnumerable<EnvelopeDto>>> ReadAllWithAsync(bool documents = false, bool envelopeReceivers = false, bool history = false, bool documentReceiverElement = false);
Task<IServiceResult<EnvelopeDto>> ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withAll = false);
Task<IServiceResult<EnvelopeDto>> ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withEnvelopeReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withUser = false, bool withAll = false);
}
}

View File

@ -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<EnvelopeDocumentDto>? Documents,
IEnumerable<EnvelopeReceiverDto>? Receivers,
IEnumerable<EnvelopeReceiverDto>? EnvelopeReceivers,
IEnumerable<EnvelopeHistoryDto>? History);
}

View File

@ -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,

View File

@ -4,6 +4,6 @@
int Id,
string EmailAddress,
string Signature,
DateTime AddedWhen,
IEnumerable<EnvelopeReceiverDto>? EnvelopeReceivers);
DateTime AddedWhen
);
}

View File

@ -0,0 +1,8 @@
namespace EnvelopeGenerator.Application
{
public enum EnvelopeFlag
{
EnvelopeOrReceiverNonexists,
NonDecodableEnvelopeReceiverId
}
}

View File

@ -22,6 +22,18 @@
<Reference Include="DigitalData.Core.Contracts">
<HintPath>..\..\WebCoreModules\DigitalData.Core.Application\bin\Debug\net7.0\DigitalData.Core.Contracts.dll</HintPath>
</Reference>
<Reference Include="DigitalData.Core.Infrastructure">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.Core.Infrastructure.dll</HintPath>
</Reference>
<Reference Include="DigitalData.UserManager.Application">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Application.dll</HintPath>
</Reference>
<Reference Include="DigitalData.UserManager.Domain">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Domain\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll</HintPath>
</Reference>
<Reference Include="DigitalData.UserManager.Infrastructure">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Infrastructure.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -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}";
}
}
}

View File

@ -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
}
}

View File

@ -5,13 +5,76 @@
/// </summary>
public static class EnvelopeGeneratorExtensions
{
/// <summary>
/// Validates whether a given string is a correctly formatted Base-64 encoded string.
/// </summary>
/// <remarks>
/// 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).
/// </remarks>
/// <param name="input">The Base-64 encoded string to validate.</param>
/// <returns>
/// <c>true</c> if the string is a valid Base-64 encoded string; otherwise, <c>false</c>.
/// </returns>
/// <example>
/// <code>
/// string testString = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnk=";
/// bool isValid = IsValidBase64String(testString);
/// Console.WriteLine(isValid); // Output: true
/// </code>
/// </example>
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
}
/// <summary>
/// Decodes the envelope receiver ID and extracts the envelope UUID and receiver signature.
/// </summary>
/// <param name="envelopeReceiverId">The base64 encoded string containing the envelope UUID and receiver signature.</param>
/// <returns>A tuple containing the envelope UUID and receiver signature.</returns>
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);

View File

@ -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<int> CountAsync(int? envelopeId = null, string? userReference = null, int? status = null) => await _repository.CountAsync(envelopeId: envelopeId, userReference: userReference, status: status);
public async Task<bool> AccessCodeAlreadyRequested(int envelopeId, string userReference) => await _repository.CountAsync(envelopeId: envelopeId, userReference:userReference, status: (int) EnvelopeStatus.AccessCodeRequested) > 0;
}
}

View File

@ -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<IServiceMessage> VerifyAccessCode(string envelopeUuid, string accessCode)
public async Task<IServiceResult<IEnumerable<EnvelopeReceiverDto>>> 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<IEnumerable<EnvelopeReceiverDto>>(env_rcvs));
}
public async Task<IServiceResult<IEnumerable<EnvelopeReceiverDto>>> 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<IEnumerable<EnvelopeReceiverDto>>(env_rcvs));
}
public async Task<IServiceResult<EnvelopeReceiverDto>> 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<EnvelopeReceiverDto>()
.WithClientMessageKey(MessageKey.EnvelopeReceiverNotFound);
return Successful(_mapper.MapOrThrow<EnvelopeReceiverDto>(env_rcv));
}
public async Task<IServiceResult<EnvelopeReceiverDto>> ReadByEnvelopeReceiverIdAsync(string envelopeReceiverId, bool withEnvelope = true, bool withReceiver = true)
{
(string? uuid, string? signature) = envelopeReceiverId.DecodeEnvelopeReceiverId();
if (uuid is null || signature is null)
return Failed<EnvelopeReceiverDto>()
.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<IServiceResult<bool>> VerifyAccessCodeAsync(string uuid, string signature, string accessCode)
{
var er = await _repository.ReadByUuidSignatureAsync(uuid: uuid, signature: signature);
if (er is null)
return Failed<bool>()
.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<bool>()
.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<IServiceResult<bool>> VerifyAccessCodeAsync(string envelopeReceiverId, string accessCode)
{
(string? uuid, string? signature) = envelopeReceiverId.DecodeEnvelopeReceiverId();
if (uuid is null || signature is null)
return Failed<bool>()
.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<IServiceResult<bool>> 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);
}
}
}

View File

@ -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<IEnvelopeRepository, EnvelopeDto, Envelope, int>, IEnvelopeService
{
public EnvelopeService(IEnvelopeRepository repository, IKeyTranslationService translationService, IMapper mapper)
private readonly ILogger _logger;
public EnvelopeService(IEnvelopeRepository repository, IKeyTranslationService translationService, IMapper mapper, ILogger<EnvelopeService> logger)
: base(repository, translationService, mapper)
{
_logger = logger;
}
public async Task<IServiceResult<IEnumerable<EnvelopeDto>>> ReadAllWithAsync(bool documents = false, bool receivers = false, bool history = false, bool documentReceiverElement = false)
public async Task<IServiceResult<IEnumerable<EnvelopeDto>>> 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<IEnumerable<EnvelopeDto>>(envelopes);
return Successful(readDto);
}
public async Task<IServiceResult<EnvelopeDto>> ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withAll = false)
public async Task<IServiceResult<EnvelopeDto>> 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<EnvelopeDto>();

View File

@ -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
}
}

View File

@ -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; }
/// <summary>
/// The sender of envelope
/// </summary>
[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<EnvelopeDocument>? Documents { get; set; }
public IEnumerable<EnvelopeReceiver>? Receivers { get; set; }
public IEnumerable<EnvelopeReceiver>? EnvelopeReceivers { get; set; }
public IEnumerable<EnvelopeHistory>? History { get; set; }
}

View File

@ -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")]

View File

@ -22,7 +22,5 @@ namespace EnvelopeGenerator.Domain.Entities
[Required]
[Column("ADDED_WHEN", TypeName = "datetime")]
public DateTime AddedWhen { get; set; }
public IEnumerable<EnvelopeReceiver>? EnvelopeReceivers { get; set; }
}
}

View File

@ -10,4 +10,10 @@
<ProjectReference Include="..\EnvelopeGenerator.Common\EnvelopeGenerator.Common.vbproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="DigitalData.UserManager.Domain">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Domain\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -5,5 +5,6 @@ namespace EnvelopeGenerator.Infrastructure.Contracts
{
public interface IEnvelopeHistoryRepository : ICRUDRepository<EnvelopeHistory, long>
{
Task<int> CountAsync(int? envelopeId = null, string? userReference = null, int? status = null);
}
}

View File

@ -5,6 +5,14 @@ namespace EnvelopeGenerator.Infrastructure.Contracts
{
public interface IEnvelopeReceiverRepository : ICRUDRepository<EnvelopeReceiver, int>
{
Task<string?> ReadAccessCodeByEnvelopeUuid(string envelopeUuid);
Task<IEnumerable<EnvelopeReceiver>> ReadByUuidAsync(string uuid, bool withEnvelope = true, bool withReceiver = false);
Task<IEnumerable<EnvelopeReceiver>> ReadBySignatureAsync(string signature, bool withEnvelope = false, bool withReceiver = true);
Task<EnvelopeReceiver?> ReadByUuidSignatureAsync(string uuid, string signature, bool withEnvelope = true, bool withReceiver = true);
Task<string?> ReadAccessCodeAsync(string uuid, string signature);
Task<int> CountAsync(string uuid, string signature);
}
}

View File

@ -5,8 +5,8 @@ namespace EnvelopeGenerator.Infrastructure.Contracts
{
public interface IEnvelopeRepository : ICRUDRepository<Envelope, int>
{
Task<IEnumerable<Envelope>> ReadAllWithAsync(bool documents = false, bool receivers = false, bool history = false, bool documentReceiverElement = true);
Task<IEnumerable<Envelope>> ReadAllWithAsync(bool documents = false, bool envelopeReceivers = false, bool history = false, bool documentReceiverElement = true);
Task<Envelope?> ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withAll = false);
Task<Envelope?> ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withEnvelopeReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withUser = false, bool withAll = false);
}
}

View File

@ -26,6 +26,9 @@
<Reference Include="DigitalData.Core.Infrastructure">
<HintPath>..\..\WebCoreModules\DigitalData.Core.Infrastructure\bin\Debug\net7.0\DigitalData.Core.Infrastructure.dll</HintPath>
</Reference>
<Reference Include="DigitalData.UserManager.Domain">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Domain\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -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<int> 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();
}
}
}

View File

@ -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<Envelope?> ReadByUuidAsync(string uuid, string? signature = null, bool withDocuments = false, bool withReceivers = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withAll = false)
public async Task<Envelope?> 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);

View File

@ -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<string?> ReadAccessCodeByEnvelopeUuid(string envelopeUuid)
private IQueryable<EnvelopeReceiver> 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<IEnumerable<EnvelopeReceiver>> ReadByUuidAsync(string uuid, bool withEnvelope = true, bool withReceiver = false)
=> await ReadWhere(uuid: uuid, withEnvelope: withEnvelope, withReceiver: withReceiver).ToListAsync();
public async Task<IEnumerable<EnvelopeReceiver>> ReadBySignatureAsync(string signature, bool withEnvelope = false, bool withReceiver = true)
=> await ReadWhere(signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver).ToListAsync();
public async Task<EnvelopeReceiver?> 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<string?> ReadAccessCodeAsync(string uuid, string signature)
=> await ReadWhere(uuid:uuid, signature:signature)
.Select(er => er.AccessCode)
.FirstOrDefaultAsync();
return accessCode;
}
public async Task<int> CountAsync(string uuid, string signature) => await ReadWhere(uuid: uuid, signature: signature).CountAsync();
}
}

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="EnvelopeGenerator.Service.My.MySettings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="EnvelopeGenerator.Service.My.MySettings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/>
</sectionGroup>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

View File

@ -11,7 +11,7 @@
<AssemblyName>EnvelopeGenerator.Service</AssemblyName>
<FileAlignment>512</FileAlignment>
<MyType>Console</MyType>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>

View File

@ -1,10 +1,10 @@
'------------------------------------------------------------------------------
' <auto-generated>
' 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.
' </auto-generated>
'------------------------------------------------------------------------------

View File

@ -1,10 +1,10 @@
'------------------------------------------------------------------------------
' <auto-generated>
' 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.
' </auto-generated>
'------------------------------------------------------------------------------
@ -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.
'''<summary>
''' Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw.
''' A strongly-typed resource class, for looking up localized strings, etc.
'''</summary>
<Global.System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0"), _
Global.System.Diagnostics.DebuggerNonUserCodeAttribute(), _
@ -33,7 +33,7 @@ Namespace My.Resources
Private resourceCulture As Global.System.Globalization.CultureInfo
'''<summary>
''' Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird.
''' Returns the cached ResourceManager instance used by this class.
'''</summary>
<Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _
Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager
@ -47,8 +47,8 @@ Namespace My.Resources
End Property
'''<summary>
''' Ü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.
'''</summary>
<Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _
Friend Property Culture() As Global.System.Globalization.CultureInfo

View File

@ -1,10 +1,10 @@
'------------------------------------------------------------------------------
' <auto-generated>
' 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.
' </auto-generated>
'------------------------------------------------------------------------------
@ -15,14 +15,14 @@ Option Explicit On
Namespace My
<Global.System.Runtime.CompilerServices.CompilerGeneratedAttribute(), _
Global.System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.3.0.0"), _
Global.System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.9.0.0"), _
Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _
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

View File

@ -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
{

View File

@ -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."
});
}
}
}

View File

@ -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<HomeController> logger, IConfiguration configuration, IEnvelopeReceiverService envelopeReceiverService, IEnvelopeService envelopeService) : base(databaseService, logger)
public HomeController(DatabaseService databaseService, EnvelopeOldService envelopeOldService, ILogger<HomeController> 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<Envelope> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<Claim>
{
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<Claim>
{
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<IActionResult> 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();
}
}

View File

@ -14,13 +14,10 @@ namespace EnvelopeGenerator.Web.Controllers.Test
}
[NonAction]
public override Task<IActionResult> GetAll()
{
return base.GetAll();
}
public override Task<IActionResult> GetAll() => base.GetAll();
[HttpGet]
public virtual async Task<IActionResult> 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<IActionResult> 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);

View File

@ -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<TestEnvelopeHistoryController> logger, IEnvelopeHistoryService service) : base(logger, service)
{
}
[HttpGet("Count")]
public async Task<IActionResult> 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<IActionResult> AccessCodeAlreadyRequested(int envelopeId, string userReference)
{
return Ok(await _service.AccessCodeAlreadyRequested(envelopeId, userReference));
}
}
}

View File

@ -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<TestEnvelopeReceiverController> logger, IEnvelopeReceiverService service) : base(logger, service)
{
}
[HttpGet("verify-access-code/{envelope_receiver_id}")]
public async Task<IActionResult> 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<IActionResult> 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 });
}
}
}

View File

@ -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<TestViewController> 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<Envelope> envelopes = envelopeOldService.LoadEnvelopes();
return View("DebugEnvelopes", envelopes);
}
catch(Exception ex)
{
_logger.LogError(ex, "Unexpected error");
ViewData["error"] = "Unknown error!";
return View("Index");
}
}
}
}

View File

@ -6,6 +6,10 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<None Remove="Views\Shared\Error.html" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4" />
@ -25,6 +29,9 @@
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="7.0.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.5" />
<PackageReference Include="System.DirectoryServices" Version="7.0.1" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="7.0.1" />
<PackageReference Include="System.DirectoryServices.Protocols" Version="7.0.1" />
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
</ItemGroup>
@ -66,6 +73,15 @@
<Reference Include="DigitalData.Modules.Logging">
<HintPath>..\..\DDModules\Logging\bin\Debug\DigitalData.Modules.Logging.dll</HintPath>
</Reference>
<Reference Include="DigitalData.UserManager.Application">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Application.dll</HintPath>
</Reference>
<Reference Include="DigitalData.UserManager.Domain">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Domain.dll</HintPath>
</Reference>
<Reference Include="DigitalData.UserManager.Infrastructure">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Infrastructure.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14">
<HintPath>D:\ProgramFiles\GdPicture.NET 14\Redist\GdPicture.NET (.NET Framework 4.5)\GdPicture.NET.14.dll</HintPath>
</Reference>
@ -78,6 +94,9 @@
<Content Update="wwwroot\cookies-policy.en.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\favicon.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\privacy-policy.en.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>

View File

@ -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
}
}

View File

@ -0,0 +1,60 @@
namespace EnvelopeGenerator.Web.Models
{
/// <summary>
/// Represents a hyperlink for contact purposes with various HTML attributes.
/// </summary>
public class ContactLink
{
/// <summary>
/// Gets or sets the label of the hyperlink.
/// </summary>
public string Label { get; init; } = "Contact";
/// <summary>
/// Gets or sets the URL that the hyperlink points to.
/// </summary>
public string Href { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the target where the hyperlink should open.
/// Commonly used values are "_blank", "_self", "_parent", "_top".
/// </summary>
public string Target { get; set; } = "_blank";
/// <summary>
/// Gets or sets the relationship of the linked URL as space-separated link types.
/// Examples include "nofollow", "noopener", "noreferrer".
/// </summary>
public string Rel { get; set; } = string.Empty;
/// <summary>
/// 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.
/// </summary>
public string Download { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the language of the linked resource. Useful when linking to
/// content in another language.
/// </summary>
public string HrefLang { get; set; } = "en";
/// <summary>
/// Gets or sets the MIME type of the linked URL. Helps browsers to handle
/// the type correctly when the link is clicked.
/// </summary>
public string Type { get; set; } = string.Empty;
/// <summary>
/// Gets or sets additional information about the hyperlink, typically viewed
/// as a tooltip when the mouse hovers over the link.
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// Gets or sets an identifier for the hyperlink, unique within the HTML document.
/// </summary>
public string Id { get; set; } = string.Empty;
}
}

View File

@ -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";
}
}

View File

@ -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<EnvelopeOldService>();
// 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<bool>("AddTestControllers")));
@ -86,6 +89,7 @@ try
//Auto mapping profiles
builder.Services.AddAutoMapper(typeof(BasicDtoMappingProfile).Assembly);
builder.Services.AddAutoMapper(typeof(UserMappingProfile).Assembly);
builder.Services.Configure<CookiePolicyOptions>(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<ContactLink>() ?? 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)

View File

@ -1,7 +1,6 @@
@{
ViewData["Title"] = "Dokument geschützt";
}
<div class="page container p-5">
<header class="text-center">
<div class="icon locked">
@ -21,7 +20,7 @@
<form id="form-access-code" class="form" method="post">
<div class="input">
<label class="visually-hidden" for="access_code">Zugriffscode</label>
<input type="password" class="form-control" name="access_code" placeholder="Zugriffscode" required="required">
<input type="password" id="access_code" class="form-control" name="access_code" placeholder="Zugriffscode" required="required">
</div>
<div class="button">

View File

@ -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<int>();
var stPageIndexes = string.Join(pages.Count() > 1 ? ", " : "", pages.Take(pages.Count() - 1))
@ -37,7 +38,7 @@
<div class="card-body p-0 m-0">
<h5 class="card-title p-0 m-0">@($"{envelope.Title}")</h5>
<p class="card-text p-0 m-0">@($"Sie haben {(pages.Count())} Briefe zu unterschreiben. Bitte prüfen Sie die Seiten {stPageIndexes}.")</p>
<p class="card-text p-0 m-0"><small class="text-body-secondary">Erstellt am @envelope.AddedWhen</small></p>
<p class="card-text p-0 m-0"><small class="text-body-secondary">Erstellt am @envelope.AddedWhen von @sender?.Prename @sender?.Name. Sie können den Absender über <a href="mailto:@(sender?.Email)?subject=@(envelope.Title)&body=Sehr%20geehrter%20@(sender?.Prename)%20@(sender?.Name),%0A%0A%0A">@sender?.Email</a> kontaktieren.</small></p>
</div>
</div>
</div>
@ -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);

View File

@ -1,25 +0,0 @@
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

View File

@ -0,0 +1,69 @@
@{
Layout = null;
}
@model ErrorViewModel
@{
var errorModel = Model ?? new ErrorViewModel();
}
@inject ContactLink _contactLink
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="~/css/error-space.css">
</head>
<body>
<div class="moon"></div>
<div class="moon__crater moon__crater1"></div>
<div class="moon__crater moon__crater2"></div>
<div class="moon__crater moon__crater3"></div>
<div class="star star1"></div>
<div class="star star2"></div>
<div class="star star3"></div>
<div class="star star4"></div>
<div class="star star5"></div>
<div class="error">
<div class="error__title">@errorModel.Title</div>
<div class="error__subtitle">@errorModel.Subtitle</div>
<div class="error__description">@errorModel.Body</div>
<a href="@_contactLink.Href" class="error__button" target="@_contactLink.Target" hreflang="@_contactLink.HrefLang" title="@_contactLink.Title">@_contactLink.Label</a>
</div>
<div class="astronaut">
<div class="astronaut__backpack"></div>
<div class="astronaut__body"></div>
<div class="astronaut__body__chest"></div>
<div class="astronaut__arm-left1"></div>
<div class="astronaut__arm-left2"></div>
<div class="astronaut__arm-right1"></div>
<div class="astronaut__arm-right2"></div>
<div class="astronaut__arm-thumb-left"></div>
<div class="astronaut__arm-thumb-right"></div>
<div class="astronaut__leg-left"></div>
<div class="astronaut__leg-right"></div>
<div class="astronaut__foot-left"></div>
<div class="astronaut__foot-right"></div>
<div class="astronaut__wrist-left"></div>
<div class="astronaut__wrist-right"></div>
<div class="astronaut__cord">
<canvas id="cord" height="500px" width="500px"></canvas>
</div>
<div class="astronaut__head">
<canvas id="visor" width="60px" height="60px"></canvas>
<div class="astronaut__head-visor-flare1"></div>
<div class="astronaut__head-visor-flare2"></div>
</div>
</div>
<script src="~/js/error-space.js"></script>
</body>
</html>

View File

@ -1,3 +1,3 @@
@{
Layout = "_Layout";
}
}

View File

@ -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"
}
}

View File

@ -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;
}

View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="css/error-space.css">
</head>
<body>
<div class="moon"></div>
<div class="moon__crater moon__crater1"></div>
<div class="moon__crater moon__crater2"></div>
<div class="moon__crater moon__crater3"></div>
<div class="star star1"></div>
<div class="star star2"></div>
<div class="star star3"></div>
<div class="star star4"></div>
<div class="star star5"></div>
<div class="error">
<div class="error__title">404</div>
<div class="error__subtitle">Hmmm...</div>
<div class="error__description">It looks like one of the developers fell asleep</div>
<a class="error__button">CONTACT</a>
</div>
<div class="astronaut">
<div class="astronaut__backpack"></div>
<div class="astronaut__body"></div>
<div class="astronaut__body__chest"></div>
<div class="astronaut__arm-left1"></div>
<div class="astronaut__arm-left2"></div>
<div class="astronaut__arm-right1"></div>
<div class="astronaut__arm-right2"></div>
<div class="astronaut__arm-thumb-left"></div>
<div class="astronaut__arm-thumb-right"></div>
<div class="astronaut__leg-left"></div>
<div class="astronaut__leg-right"></div>
<div class="astronaut__foot-left"></div>
<div class="astronaut__foot-right"></div>
<div class="astronaut__wrist-left"></div>
<div class="astronaut__wrist-right"></div>
<div class="astronaut__cord">
<canvas id="cord" height="500px" width="500px"></canvas>
</div>
<div class="astronaut__head">
<canvas id="visor" width="60px" height="60px"></canvas>
<div class="astronaut__head-visor-flare1"></div>
<div class="astronaut__head-visor-flare2"></div>
</div>
</div>
<script src="js/error-space.js"></script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 264 KiB

View File

@ -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))

View File

@ -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();