diff --git a/EnvelopeGenerator.Application/Configurations/EnvelopeReceiverCacheParams.cs b/EnvelopeGenerator.Application/Configurations/EnvelopeReceiverCacheParams.cs index b8ab323e..5d51941a 100644 --- a/EnvelopeGenerator.Application/Configurations/EnvelopeReceiverCacheParams.cs +++ b/EnvelopeGenerator.Application/Configurations/EnvelopeReceiverCacheParams.cs @@ -13,7 +13,5 @@ /// The placeholder {0} represents the envelopeReceiverId. /// public string CodeExpirationCacheKeyFormat { get; init; } = "sms-code-expiration-{0}"; - - public TimeSpan CodeCacheValidityPeriod { get; init; } = new(0, 5, 0); } } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/Configurations/GtxMessaging/SmsParams.cs b/EnvelopeGenerator.Application/Configurations/GtxMessaging/SmsParams.cs index 7cea8047..9db3c8c5 100644 --- a/EnvelopeGenerator.Application/Configurations/GtxMessaging/SmsParams.cs +++ b/EnvelopeGenerator.Application/Configurations/GtxMessaging/SmsParams.cs @@ -1,5 +1,6 @@ using DigitalData.Core.Abstractions.Client; using Microsoft.Extensions.Caching.Distributed; +using OtpNet; namespace EnvelopeGenerator.Application.Configurations.GtxMessaging { @@ -21,5 +22,9 @@ namespace EnvelopeGenerator.Application.Configurations.GtxMessaging public string MessageQueryParamName { get; init; } = "text"; public int CodeLength { get; init; } = 5; + + public int SmsTotpStep { get; init; } = 300; + + public string DefaultTotpMessageFormat { get; init; } = "{0}"; } } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/Contracts/ICodeGenerator.cs b/EnvelopeGenerator.Application/Contracts/ICodeGenerator.cs index d4e93c6b..c8d47976 100644 --- a/EnvelopeGenerator.Application/Contracts/ICodeGenerator.cs +++ b/EnvelopeGenerator.Application/Contracts/ICodeGenerator.cs @@ -15,5 +15,7 @@ namespace EnvelopeGenerator.Application.Contracts string GenerateTotp(string secretKey, int step = 30); bool VerifyTotp(string totpCode, string secretKey, int step = 30, VerificationWindow? window = null); + + bool GetTotpExpirationTime(int step = 30); } } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/Contracts/IEnvelopeReceiverCache.cs b/EnvelopeGenerator.Application/Contracts/IEnvelopeReceiverCache.cs deleted file mode 100644 index e2ac2a7c..00000000 --- a/EnvelopeGenerator.Application/Contracts/IEnvelopeReceiverCache.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace EnvelopeGenerator.Application.Contracts -{ - public interface IEnvelopeReceiverCache - { - Task GetSmsCodeAsync(string envelopeReceiverId); - - /// - /// Asynchronously stores an SMS verification code in the cache and returns the expiration date of the code. - /// - /// The unique identifier for the recipient of the envelope to associate with the SMS code. - /// The SMS verification code to be stored. - /// A task that represents the asynchronous operation. The task result contains the expiration date and time of the stored SMS code. - Task SetSmsCodeAsync(string envelopeReceiverId, string code); - - Task GetSmsCodeExpirationAsync(string envelopeReceiverId); - } -} \ No newline at end of file diff --git a/EnvelopeGenerator.Application/Contracts/IMessagingService.cs b/EnvelopeGenerator.Application/Contracts/IMessagingService.cs index 5940e50d..9a0c050a 100644 --- a/EnvelopeGenerator.Application/Contracts/IMessagingService.cs +++ b/EnvelopeGenerator.Application/Contracts/IMessagingService.cs @@ -1,13 +1,12 @@ using EnvelopeGenerator.Application.DTOs.Messaging; -namespace EnvelopeGenerator.Application.Contracts +namespace EnvelopeGenerator.Application.Contracts; + +public interface IMessagingService { - public interface IMessagingService - { - string ServiceProvider { get; } + string ServiceProvider { get; } - Task SendSmsAsync(string recipient, string message); + Task SendSmsAsync(string recipient, string message); - Task SendSmsCodeAsync(string recipient, string envelopeReceiverId); - } + Task SendSmsCodeAsync(string recipient, string secretKey, string messageFormat = "{0}"); } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/DTOs/Messaging/SmsResponse.cs b/EnvelopeGenerator.Application/DTOs/Messaging/SmsResponse.cs index 96b67515..a546d0c7 100644 --- a/EnvelopeGenerator.Application/DTOs/Messaging/SmsResponse.cs +++ b/EnvelopeGenerator.Application/DTOs/Messaging/SmsResponse.cs @@ -4,16 +4,6 @@ { public required bool Ok { get; init; } - public DateTime? Expiration { get; set; } - - public DateTime? AllowedAt { get; set; } - - public TimeSpan AllowedAfter => Allowed ? TimeSpan.Zero : AllowedAt!.Value - DateTime.Now; - - public bool Allowed => AllowedAt is null || DateTime.Now >= AllowedAt; - - public bool Error => !Ok && Allowed; - public dynamic? Errors { get; init; } } } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/Services/CodeGenerator.cs b/EnvelopeGenerator.Application/Services/CodeGenerator.cs index ca84bfc9..94ad6720 100644 --- a/EnvelopeGenerator.Application/Services/CodeGenerator.cs +++ b/EnvelopeGenerator.Application/Services/CodeGenerator.cs @@ -67,5 +67,10 @@ namespace EnvelopeGenerator.Application.Services public bool VerifyTotp(string totpCode, string secretKey, int step = 30, VerificationWindow? window = null) => new Totp(Base32Encoding.ToBytes(secretKey), step).VerifyTotp(totpCode, out _, window); + + public bool GetTotpExpirationTime(int step = 30) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/Services/EnvelopeReceiverCache.cs b/EnvelopeGenerator.Application/Services/EnvelopeReceiverCache.cs deleted file mode 100644 index 525b5468..00000000 --- a/EnvelopeGenerator.Application/Services/EnvelopeReceiverCache.cs +++ /dev/null @@ -1,50 +0,0 @@ -using AngleSharp.Dom; -using EnvelopeGenerator.Application.Configurations; -using EnvelopeGenerator.Application.Contracts; -using EnvelopeGenerator.Application.Extensions; -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Options; - -namespace EnvelopeGenerator.Application.Services -{ - public class EnvelopeReceiverCache : IEnvelopeReceiverCache - { - private readonly EnvelopeReceiverCacheParams _cacheParams; - - private readonly DistributedCacheEntryOptions _codeCacheOptions; - - private readonly IDistributedCache _cache; - - public EnvelopeReceiverCache(IOptions cacheParamOptions, IDistributedCache cache) - { - _cacheParams = cacheParamOptions.Value; - _codeCacheOptions = new() { AbsoluteExpirationRelativeToNow = cacheParamOptions.Value.CodeCacheValidityPeriod }; - _cache = cache; - } - - public async Task GetSmsCodeAsync(string envelopeReceiverId) - { - var code_key = string.Format(_cacheParams.CodeCacheKeyFormat, envelopeReceiverId); - return await _cache.GetStringAsync(code_key); - } - - public async Task SetSmsCodeAsync(string envelopeReceiverId, string code) - { - // set key - var code_key = string.Format(_cacheParams.CodeCacheKeyFormat, envelopeReceiverId); - await _cache.SetStringAsync(code_key, code, _codeCacheOptions); - - // set expiration - var code_expiration_key = string.Format(_cacheParams.CodeExpirationCacheKeyFormat, envelopeReceiverId); - var expiration = DateTime.Now + _cacheParams.CodeCacheValidityPeriod; - await _cache.SetDateTimeAsync(code_expiration_key, expiration, _codeCacheOptions); - return expiration; - } - - public async Task GetSmsCodeExpirationAsync(string envelopeReceiverId) - { - var code_expiration_key = string.Format(_cacheParams.CodeExpirationCacheKeyFormat, envelopeReceiverId); - return await _cache.GetDateTimeAsync(code_expiration_key); - } - } -} \ No newline at end of file diff --git a/EnvelopeGenerator.Application/Services/GTXMessagingService.cs b/EnvelopeGenerator.Application/Services/GTXMessagingService.cs index 8b2482fc..78269522 100644 --- a/EnvelopeGenerator.Application/Services/GTXMessagingService.cs +++ b/EnvelopeGenerator.Application/Services/GTXMessagingService.cs @@ -6,62 +6,44 @@ using EnvelopeGenerator.Application.Contracts; using EnvelopeGenerator.Application.DTOs.Messaging; using Microsoft.Extensions.Options; -namespace EnvelopeGenerator.Application.Services +namespace EnvelopeGenerator.Application.Services; + +public class GtxMessagingService : IMessagingService { - public class GtxMessagingService : IMessagingService + private readonly IHttpClientService _smsClient; + + private readonly SmsParams _smsParams; + + private readonly IMapper _mapper; + + private readonly ICodeGenerator _codeGen; + + public string ServiceProvider { get; } + + public GtxMessagingService(IHttpClientService smsClient, IOptions smsParamsOptions, IMapper mapper, ICodeGenerator codeGenerator) { - private readonly IHttpClientService _smsClient; + _smsClient = smsClient; + _smsParams = smsParamsOptions.Value; + _mapper = mapper; + ServiceProvider = GetType().Name.Replace("Service", string.Empty); + _codeGen = codeGenerator; + } - private readonly SmsParams _smsParams; - - private readonly IMapper _mapper; - - private readonly ICodeGenerator _codeGen; - - private readonly IEnvelopeReceiverCache _erCache; - - public string ServiceProvider { get; } - - public GtxMessagingService(IHttpClientService smsClient, IOptions smsParamsOptions, IMapper mapper, ICodeGenerator codeGenerator, IEnvelopeReceiverCache envelopeReceiverCache) + public async Task SendSmsAsync(string recipient, string message) + { + return await _smsClient.FetchAsync(queryParams: new Dictionary() { - _smsClient = smsClient; - _smsParams = smsParamsOptions.Value; - _mapper = mapper; - ServiceProvider = GetType().Name.Replace("Service", string.Empty); - _codeGen = codeGenerator; - _erCache = envelopeReceiverCache; - } + { _smsParams.RecipientQueryParamName, recipient }, + { _smsParams.MessageQueryParamName, message } + }) + .ThenAsync(res => res.Json()) + .ThenAsync(_mapper.Map); + } - public async Task SendSmsAsync(string recipient, string message) - { - return await _smsClient.FetchAsync(queryParams: new Dictionary() - { - { _smsParams.RecipientQueryParamName, recipient }, - { _smsParams.MessageQueryParamName, message } - }) - .ThenAsync(res => res.Json()) - .ThenAsync(_mapper.Map); - } - - public async Task SendSmsCodeAsync(string recipient, string envelopeReceiverId) - { - var code = await _erCache.GetSmsCodeAsync(envelopeReceiverId); - - if (code is null) - { - code = _codeGen.GenerateCode(_smsParams.CodeLength); - var expiration = await _erCache.SetSmsCodeAsync(envelopeReceiverId, code); - var res = await SendSmsAsync(recipient: recipient, message: code); - res.Expiration = expiration; - return res; - } - else - { - var code_expiration = await _erCache.GetSmsCodeExpirationAsync(envelopeReceiverId); - return code_expiration is null - ? new() { Ok = false } - : new() { Ok = false, AllowedAt = code_expiration }; - } - } + public async Task SendSmsCodeAsync(string recipient, string secretKey, string? messageFormat = null) + { + var code = _codeGen.GenerateTotp(secretKey, _smsParams.SmsTotpStep); + var message = string.Format(messageFormat ?? _smsParams.DefaultTotpMessageFormat, code); + return await SendSmsAsync(recipient: recipient, message: message); } } \ No newline at end of file diff --git a/EnvelopeGenerator.Extensions/CacheExtensions.cs b/EnvelopeGenerator.Extensions/CacheExtensions.cs new file mode 100644 index 00000000..94e54af5 --- /dev/null +++ b/EnvelopeGenerator.Extensions/CacheExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Caching.Distributed; + +namespace EnvelopeGenerator.Extensions +{ + public static class CacheExtensions + { + public static IDistributedCache Cache(this IDistributedCache cache) + { + cache.SetStringAsync() + } + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.Web/Controllers/HomeController.cs b/EnvelopeGenerator.Web/Controllers/HomeController.cs index 51af9827..79824bd6 100644 --- a/EnvelopeGenerator.Web/Controllers/HomeController.cs +++ b/EnvelopeGenerator.Web/Controllers/HomeController.cs @@ -36,11 +36,10 @@ namespace EnvelopeGenerator.Web.Controllers private readonly IEnvelopeMailService _mailService; private readonly IEnvelopeReceiverReadOnlyService _readOnlyService; private readonly IMessagingService _msgService; - private readonly IEnvelopeReceiverCache _erCache; private readonly ICodeGenerator _codeGenerator; private readonly IReceiverService _rcvService; - public HomeController(EnvelopeOldService envelopeOldService, ILogger logger, IEnvelopeReceiverService envelopeReceiverService, IEnvelopeHistoryService historyService, IStringLocalizer localizer, IConfiguration configuration, HtmlSanitizer sanitizer, Cultures cultures, IEnvelopeMailService envelopeMailService, IEnvelopeReceiverReadOnlyService readOnlyService, IMessagingService messagingService, IEnvelopeReceiverCache envelopeReceiverCache, ICodeGenerator codeGenerator, IReceiverService receiverService) + public HomeController(EnvelopeOldService envelopeOldService, ILogger logger, IEnvelopeReceiverService envelopeReceiverService, IEnvelopeHistoryService historyService, IStringLocalizer localizer, IConfiguration configuration, HtmlSanitizer sanitizer, Cultures cultures, IEnvelopeMailService envelopeMailService, IEnvelopeReceiverReadOnlyService readOnlyService, IMessagingService messagingService, ICodeGenerator codeGenerator, IReceiverService receiverService) { this.envelopeOldService = envelopeOldService; _envRcvService = envelopeReceiverService; @@ -53,7 +52,6 @@ namespace EnvelopeGenerator.Web.Controllers _logger = logger; _readOnlyService = readOnlyService; _msgService = messagingService; - _erCache = envelopeReceiverCache; _codeGenerator = codeGenerator; _rcvService = receiverService; } @@ -183,153 +181,152 @@ namespace EnvelopeGenerator.Web.Controllers //check access code EnvelopeResponse response = await envelopeOldService.LoadEnvelope(envelopeReceiverId); - return await _envRcvService.ReadWithSecretByUuidSignatureAsync(uuid: uuid, signature: signature).ThenAsync( - SuccessAsync: async er_secret => + var er_secret_res = await _envRcvService.ReadWithSecretByUuidSignatureAsync(uuid: uuid, signature: signature); + + if (er_secret_res.IsFailed) + { + _logger.LogNotice(er_secret_res.Notices); + return this.ViewEnvelopeNotFound(); + } + var er_secret = er_secret_res.Data; + + async Task TFAView(bool viaSms) + { + if (viaSms) + { + //add date time cache + var res = await _msgService.SendSmsCodeAsync(er_secret.PhoneNumber!, er_secret.Receiver.TotpSecretkey); + if (res.Ok) + return View("EnvelopeLocked").WithData("CodeType", "smsCode").WithData("SmsExpiration", res.Expiration); + else if (!res.Allowed) + return View("EnvelopeLocked").WithData("CodeType", "smsCode").WithData("SmsExpiration", res.AllowedAt); + else { - async Task TFAView(bool viaSms) - { - if (viaSms) - { - var res = await _msgService.SendSmsCodeAsync(er_secret.PhoneNumber!, envelopeReceiverId: envelopeReceiverId); - if (res.Ok) - return View("EnvelopeLocked").WithData("CodeType", "smsCode").WithData("SmsExpiration", res.Expiration); - else if (!res.Allowed) - return View("EnvelopeLocked").WithData("CodeType", "smsCode").WithData("SmsExpiration", res.AllowedAt); - else - { - var res_json = JsonConvert.SerializeObject(res); - _logger.LogEnvelopeError(envelopeReceiverId: envelopeReceiverId, message: $"An unexpected error occurred while sending an SMS code. Response: ${res_json}"); - return this.ViewInnerServiceError(); - } - } - else - { - return View("EnvelopeLocked").WithData("CodeType", "authenticatorCode").WithData("QRCodeExpiration", er_secret.Receiver?.TotpExpiration); - } - } - - if (auth.HasMulti) - { - Response.StatusCode = StatusCodes.Status401Unauthorized; - return View("EnvelopeLocked") - .WithData("ErrorMessage", _localizer[WebKey.WrongAccessCode].Value); - } - else if (auth.HasAccessCode) - { - //check the access code verification - if (er_secret.AccessCode != auth.AccessCode) - { - //Constants.EnvelopeStatus.AccessCodeIncorrect - await _historyService.RecordAsync(er_secret.EnvelopeId, er_secret.Receiver!.EmailAddress, EnvelopeStatus.AccessCodeIncorrect); - Response.StatusCode = StatusCodes.Status401Unauthorized; - return View("EnvelopeLocked") - .WithData("ErrorMessage", _localizer[WebKey.WrongAccessCode].Value); - } - - await _historyService.RecordAsync(er_secret.EnvelopeId, er_secret.Receiver!.EmailAddress, EnvelopeStatus.AccessCodeCorrect); - - //check if the user has phone is added - if (er_secret.Envelope!.TFAEnabled) - { - var rcv = er_secret.Receiver; - if (rcv.IsTotpSecretInvalid()) - { - rcv.TotpSecretkey = _codeGenerator.GenerateTotpSecretKey(); - rcv.TotpExpiration = DateTime.Now.AddMonths(1); - await _rcvService.UpdateAsync(rcv); - await _mailService.SendTFAQrCodeAsync(er_secret); - } - return await TFAView(auth.UserSelectSMS); - } - - } - else if (auth.HasSmsCode) - { - var smsCode = await _erCache.GetSmsCodeAsync(envelopeReceiverId); - if (smsCode is null) - return RedirectToAction("EnvelopeLocked", new { envelopeReceiverId }); - - if(auth.SmsCode != smsCode) - { - Response.StatusCode = StatusCodes.Status401Unauthorized; - ViewData["ErrorMessage"] = _localizer[WebKey.WrongAccessCode].Value; - return await TFAView(viaSms: true); - } - } - else if (auth.HasAuthenticatorCode) - { - if (er_secret.Receiver!.IsTotpInvalid(totp: auth.AuthenticatorCode!)) - { - Response.StatusCode = StatusCodes.Status401Unauthorized; - ViewData["ErrorMessage"] = _localizer[WebKey.WrongAccessCode].Value; - return await TFAView(viaSms: false); - } - } - else - { - Response.StatusCode = StatusCodes.Status401Unauthorized; - return View("EnvelopeLocked") - .WithData("ErrorMessage", _localizer[WebKey.WrongAccessCode].Value); - } - - //continue the process without important data to minimize security errors. - EnvelopeReceiverDto er = er_secret; - - ViewData["EnvelopeKey"] = envelopeReceiverId; - //check rejection - var rejRcvrs = await _historyService.ReadRejectingReceivers(er.Envelope!.Id); - if(rejRcvrs.Any()) - { - ViewBag.IsExt = !rejRcvrs.Contains(er.Receiver); //external if the current user is not rejected - return View("EnvelopeRejected", er); - } - - //check if it has already signed - if (await _historyService.IsSigned(envelopeId: er.Envelope!.Id, userReference: er.Receiver!.EmailAddress)) - return View("EnvelopeSigned"); - - if (er.Envelope.Documents?.FirstOrDefault() is EnvelopeDocumentDto doc && doc.ByteData is not null) - { - ViewData["DocumentBytes"] = doc.ByteData; - } - else - { - _logger.LogEnvelopeError(envelopeReceiverId: envelopeReceiverId, message: "No document byte-data was found in ENVELOPE_DOCUMENT table."); - return this.ViewDocumentNotFound(); - } - - var claims = new List { - new(ClaimTypes.NameIdentifier, uuid), - new(ClaimTypes.Hash, signature), - new(ClaimTypes.Name, er.Name ?? string.Empty), - new(ClaimTypes.Email, er.Receiver.EmailAddress), - new(EnvelopeClaimTypes.Title, er.Envelope.Title), - new(EnvelopeClaimTypes.Id, er.Envelope.Id.ToString()) - }; - - var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); - var authProperties = new AuthenticationProperties - { - AllowRefresh = false, - IsPersistent = false - }; - - await HttpContext.SignInAsync( - CookieAuthenticationDefaults.AuthenticationScheme, - new ClaimsPrincipal(claimsIdentity), - authProperties); - - //add PSPDFKit licence key - ViewData["PSPDFKitLicenseKey"] = _configuration["PSPDFKitLicenseKey"]; - - return View("ShowEnvelope", er); - }, - Fail: (messages, notices) => - { - _logger.LogNotice(notices); - return this.ViewEnvelopeNotFound(); + var res_json = JsonConvert.SerializeObject(res); + _logger.LogEnvelopeError(envelopeReceiverId: envelopeReceiverId, message: $"An unexpected error occurred while sending an SMS code. Response: ${res_json}"); + return this.ViewInnerServiceError(); } - ); + } + else + { + return View("EnvelopeLocked").WithData("CodeType", "authenticatorCode").WithData("QRCodeExpiration", er_secret.Receiver?.TotpExpiration); + } + } + + if (auth.HasMulti) + { + Response.StatusCode = StatusCodes.Status401Unauthorized; + return View("EnvelopeLocked") + .WithData("ErrorMessage", _localizer[WebKey.WrongAccessCode].Value); + } + else if (auth.HasAccessCode) + { + //check the access code verification + if (er_secret.AccessCode != auth.AccessCode) + { + //Constants.EnvelopeStatus.AccessCodeIncorrect + await _historyService.RecordAsync(er_secret.EnvelopeId, er_secret.Receiver!.EmailAddress, EnvelopeStatus.AccessCodeIncorrect); + Response.StatusCode = StatusCodes.Status401Unauthorized; + return View("EnvelopeLocked") + .WithData("ErrorMessage", _localizer[WebKey.WrongAccessCode].Value); + } + + await _historyService.RecordAsync(er_secret.EnvelopeId, er_secret.Receiver!.EmailAddress, EnvelopeStatus.AccessCodeCorrect); + + //check if the user has phone is added + if (er_secret.Envelope!.TFAEnabled) + { + var rcv = er_secret.Receiver; + if (rcv.IsTotpSecretInvalid()) + { + rcv.TotpSecretkey = _codeGenerator.GenerateTotpSecretKey(); + rcv.TotpExpiration = DateTime.Now.AddMonths(1); + await _rcvService.UpdateAsync(rcv); + await _mailService.SendTFAQrCodeAsync(er_secret); + } + return await TFAView(auth.UserSelectSMS); + } + + } + else if (auth.HasSmsCode) + { + if (er_secret.Receiver!.TotpSecretkey is null) + throw new InvalidOperationException($"TotpSecretkey of DTO cannot validate without TotpSecretkey. Dto: {JsonConvert.SerializeObject(er_secret)}"); + + if (_codeGenerator.VerifyTotp(auth.SmsCode!, er_secret.Receiver.TotpSecretkey, step: 60 * 5)) + { + Response.StatusCode = StatusCodes.Status401Unauthorized; + ViewData["ErrorMessage"] = _localizer[WebKey.WrongAccessCode].Value; + return await TFAView(viaSms: true); + } + } + else if (auth.HasAuthenticatorCode) + { + if (er_secret.Receiver!.IsTotpInvalid(totp: auth.AuthenticatorCode!)) + { + Response.StatusCode = StatusCodes.Status401Unauthorized; + ViewData["ErrorMessage"] = _localizer[WebKey.WrongAccessCode].Value; + return await TFAView(viaSms: false); + } + } + else + { + Response.StatusCode = StatusCodes.Status401Unauthorized; + return View("EnvelopeLocked") + .WithData("ErrorMessage", _localizer[WebKey.WrongAccessCode].Value); + } + + //continue the process without important data to minimize security errors. + EnvelopeReceiverDto er = er_secret; + + ViewData["EnvelopeKey"] = envelopeReceiverId; + //check rejection + var rejRcvrs = await _historyService.ReadRejectingReceivers(er.Envelope!.Id); + if(rejRcvrs.Any()) + { + ViewBag.IsExt = !rejRcvrs.Contains(er.Receiver); //external if the current user is not rejected + return View("EnvelopeRejected", er); + } + + //check if it has already signed + if (await _historyService.IsSigned(envelopeId: er.Envelope!.Id, userReference: er.Receiver!.EmailAddress)) + return View("EnvelopeSigned"); + + if (er.Envelope.Documents?.FirstOrDefault() is EnvelopeDocumentDto doc && doc.ByteData is not null) + { + ViewData["DocumentBytes"] = doc.ByteData; + } + else + { + _logger.LogEnvelopeError(envelopeReceiverId: envelopeReceiverId, message: "No document byte-data was found in ENVELOPE_DOCUMENT table."); + return this.ViewDocumentNotFound(); + } + + var claims = new List { + new(ClaimTypes.NameIdentifier, uuid), + new(ClaimTypes.Hash, signature), + new(ClaimTypes.Name, er.Name ?? string.Empty), + new(ClaimTypes.Email, er.Receiver.EmailAddress), + new(EnvelopeClaimTypes.Title, er.Envelope.Title), + new(EnvelopeClaimTypes.Id, er.Envelope.Id.ToString()) + }; + + var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + var authProperties = new AuthenticationProperties + { + AllowRefresh = false, + IsPersistent = false + }; + + await HttpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(claimsIdentity), + authProperties); + + //add PSPDFKit licence key + ViewData["PSPDFKitLicenseKey"] = _configuration["PSPDFKitLicenseKey"]; + + return View("ShowEnvelope", er); } catch (Exception ex) {