refactor(HomeController): LogInEnvelope aktualisiert, um SMS-Code als TOTP zu verifizieren
This commit is contained in:
parent
3267acbeb3
commit
af5d7c289d
@ -13,7 +13,5 @@
|
|||||||
/// The placeholder {0} represents the envelopeReceiverId.
|
/// The placeholder {0} represents the envelopeReceiverId.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string CodeExpirationCacheKeyFormat { get; init; } = "sms-code-expiration-{0}";
|
public string CodeExpirationCacheKeyFormat { get; init; } = "sms-code-expiration-{0}";
|
||||||
|
|
||||||
public TimeSpan CodeCacheValidityPeriod { get; init; } = new(0, 5, 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
using DigitalData.Core.Abstractions.Client;
|
using DigitalData.Core.Abstractions.Client;
|
||||||
using Microsoft.Extensions.Caching.Distributed;
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
|
using OtpNet;
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Application.Configurations.GtxMessaging
|
namespace EnvelopeGenerator.Application.Configurations.GtxMessaging
|
||||||
{
|
{
|
||||||
@ -21,5 +22,9 @@ namespace EnvelopeGenerator.Application.Configurations.GtxMessaging
|
|||||||
public string MessageQueryParamName { get; init; } = "text";
|
public string MessageQueryParamName { get; init; } = "text";
|
||||||
|
|
||||||
public int CodeLength { get; init; } = 5;
|
public int CodeLength { get; init; } = 5;
|
||||||
|
|
||||||
|
public int SmsTotpStep { get; init; } = 300;
|
||||||
|
|
||||||
|
public string DefaultTotpMessageFormat { get; init; } = "{0}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -15,5 +15,7 @@ namespace EnvelopeGenerator.Application.Contracts
|
|||||||
string GenerateTotp(string secretKey, int step = 30);
|
string GenerateTotp(string secretKey, int step = 30);
|
||||||
|
|
||||||
bool VerifyTotp(string totpCode, string secretKey, int step = 30, VerificationWindow? window = null);
|
bool VerifyTotp(string totpCode, string secretKey, int step = 30, VerificationWindow? window = null);
|
||||||
|
|
||||||
|
bool GetTotpExpirationTime(int step = 30);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,17 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.Application.Contracts
|
|
||||||
{
|
|
||||||
public interface IEnvelopeReceiverCache
|
|
||||||
{
|
|
||||||
Task<string?> GetSmsCodeAsync(string envelopeReceiverId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Asynchronously stores an SMS verification code in the cache and returns the expiration date of the code.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="envelopeReceiverId">The unique identifier for the recipient of the envelope to associate with the SMS code.</param>
|
|
||||||
/// <param name="code">The SMS verification code to be stored.</param>
|
|
||||||
/// <returns>A task that represents the asynchronous operation. The task result contains the expiration date and time of the stored SMS code.</returns>
|
|
||||||
Task<DateTime> SetSmsCodeAsync(string envelopeReceiverId, string code);
|
|
||||||
|
|
||||||
Task<DateTime?> GetSmsCodeExpirationAsync(string envelopeReceiverId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +1,12 @@
|
|||||||
using EnvelopeGenerator.Application.DTOs.Messaging;
|
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<SmsResponse> SendSmsAsync(string recipient, string message);
|
Task<SmsResponse> SendSmsAsync(string recipient, string message);
|
||||||
|
|
||||||
Task<SmsResponse> SendSmsCodeAsync(string recipient, string envelopeReceiverId);
|
Task<SmsResponse> SendSmsCodeAsync(string recipient, string secretKey, string messageFormat = "{0}");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -4,16 +4,6 @@
|
|||||||
{
|
{
|
||||||
public required bool Ok { get; init; }
|
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; }
|
public dynamic? Errors { get; init; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,5 +67,10 @@ namespace EnvelopeGenerator.Application.Services
|
|||||||
|
|
||||||
public bool VerifyTotp(string totpCode, string secretKey, int step = 30, VerificationWindow? window = null)
|
public bool VerifyTotp(string totpCode, string secretKey, int step = 30, VerificationWindow? window = null)
|
||||||
=> new Totp(Base32Encoding.ToBytes(secretKey), step).VerifyTotp(totpCode, out _, window);
|
=> new Totp(Base32Encoding.ToBytes(secretKey), step).VerifyTotp(totpCode, out _, window);
|
||||||
|
|
||||||
|
public bool GetTotpExpirationTime(int step = 30)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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<EnvelopeReceiverCacheParams> cacheParamOptions, IDistributedCache cache)
|
|
||||||
{
|
|
||||||
_cacheParams = cacheParamOptions.Value;
|
|
||||||
_codeCacheOptions = new() { AbsoluteExpirationRelativeToNow = cacheParamOptions.Value.CodeCacheValidityPeriod };
|
|
||||||
_cache = cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string?> GetSmsCodeAsync(string envelopeReceiverId)
|
|
||||||
{
|
|
||||||
var code_key = string.Format(_cacheParams.CodeCacheKeyFormat, envelopeReceiverId);
|
|
||||||
return await _cache.GetStringAsync(code_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<DateTime> 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<DateTime?> GetSmsCodeExpirationAsync(string envelopeReceiverId)
|
|
||||||
{
|
|
||||||
var code_expiration_key = string.Format(_cacheParams.CodeExpirationCacheKeyFormat, envelopeReceiverId);
|
|
||||||
return await _cache.GetDateTimeAsync(code_expiration_key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -6,62 +6,44 @@ using EnvelopeGenerator.Application.Contracts;
|
|||||||
using EnvelopeGenerator.Application.DTOs.Messaging;
|
using EnvelopeGenerator.Application.DTOs.Messaging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Application.Services
|
namespace EnvelopeGenerator.Application.Services;
|
||||||
|
|
||||||
|
public class GtxMessagingService : IMessagingService
|
||||||
{
|
{
|
||||||
public class GtxMessagingService : IMessagingService
|
private readonly IHttpClientService<SmsParams> _smsClient;
|
||||||
|
|
||||||
|
private readonly SmsParams _smsParams;
|
||||||
|
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
private readonly ICodeGenerator _codeGen;
|
||||||
|
|
||||||
|
public string ServiceProvider { get; }
|
||||||
|
|
||||||
|
public GtxMessagingService(IHttpClientService<SmsParams> smsClient, IOptions<SmsParams> smsParamsOptions, IMapper mapper, ICodeGenerator codeGenerator)
|
||||||
{
|
{
|
||||||
private readonly IHttpClientService<SmsParams> _smsClient;
|
_smsClient = smsClient;
|
||||||
|
_smsParams = smsParamsOptions.Value;
|
||||||
|
_mapper = mapper;
|
||||||
|
ServiceProvider = GetType().Name.Replace("Service", string.Empty);
|
||||||
|
_codeGen = codeGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly SmsParams _smsParams;
|
public async Task<SmsResponse> SendSmsAsync(string recipient, string message)
|
||||||
|
{
|
||||||
private readonly IMapper _mapper;
|
return await _smsClient.FetchAsync(queryParams: new Dictionary<string, object?>()
|
||||||
|
|
||||||
private readonly ICodeGenerator _codeGen;
|
|
||||||
|
|
||||||
private readonly IEnvelopeReceiverCache _erCache;
|
|
||||||
|
|
||||||
public string ServiceProvider { get; }
|
|
||||||
|
|
||||||
public GtxMessagingService(IHttpClientService<SmsParams> smsClient, IOptions<SmsParams> smsParamsOptions, IMapper mapper, ICodeGenerator codeGenerator, IEnvelopeReceiverCache envelopeReceiverCache)
|
|
||||||
{
|
{
|
||||||
_smsClient = smsClient;
|
{ _smsParams.RecipientQueryParamName, recipient },
|
||||||
_smsParams = smsParamsOptions.Value;
|
{ _smsParams.MessageQueryParamName, message }
|
||||||
_mapper = mapper;
|
})
|
||||||
ServiceProvider = GetType().Name.Replace("Service", string.Empty);
|
.ThenAsync(res => res.Json<GtxMessagingResponse>())
|
||||||
_codeGen = codeGenerator;
|
.ThenAsync(_mapper.Map<SmsResponse>);
|
||||||
_erCache = envelopeReceiverCache;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<SmsResponse> SendSmsAsync(string recipient, string message)
|
public async Task<SmsResponse> SendSmsCodeAsync(string recipient, string secretKey, string? messageFormat = null)
|
||||||
{
|
{
|
||||||
return await _smsClient.FetchAsync(queryParams: new Dictionary<string, object?>()
|
var code = _codeGen.GenerateTotp(secretKey, _smsParams.SmsTotpStep);
|
||||||
{
|
var message = string.Format(messageFormat ?? _smsParams.DefaultTotpMessageFormat, code);
|
||||||
{ _smsParams.RecipientQueryParamName, recipient },
|
return await SendSmsAsync(recipient: recipient, message: message);
|
||||||
{ _smsParams.MessageQueryParamName, message }
|
|
||||||
})
|
|
||||||
.ThenAsync(res => res.Json<GtxMessagingResponse>())
|
|
||||||
.ThenAsync(_mapper.Map<SmsResponse>);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<SmsResponse> 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 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
EnvelopeGenerator.Extensions/CacheExtensions.cs
Normal file
12
EnvelopeGenerator.Extensions/CacheExtensions.cs
Normal file
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -36,11 +36,10 @@ namespace EnvelopeGenerator.Web.Controllers
|
|||||||
private readonly IEnvelopeMailService _mailService;
|
private readonly IEnvelopeMailService _mailService;
|
||||||
private readonly IEnvelopeReceiverReadOnlyService _readOnlyService;
|
private readonly IEnvelopeReceiverReadOnlyService _readOnlyService;
|
||||||
private readonly IMessagingService _msgService;
|
private readonly IMessagingService _msgService;
|
||||||
private readonly IEnvelopeReceiverCache _erCache;
|
|
||||||
private readonly ICodeGenerator _codeGenerator;
|
private readonly ICodeGenerator _codeGenerator;
|
||||||
private readonly IReceiverService _rcvService;
|
private readonly IReceiverService _rcvService;
|
||||||
|
|
||||||
public HomeController(EnvelopeOldService envelopeOldService, ILogger<HomeController> logger, IEnvelopeReceiverService envelopeReceiverService, IEnvelopeHistoryService historyService, IStringLocalizer<Resource> localizer, IConfiguration configuration, HtmlSanitizer sanitizer, Cultures cultures, IEnvelopeMailService envelopeMailService, IEnvelopeReceiverReadOnlyService readOnlyService, IMessagingService messagingService, IEnvelopeReceiverCache envelopeReceiverCache, ICodeGenerator codeGenerator, IReceiverService receiverService)
|
public HomeController(EnvelopeOldService envelopeOldService, ILogger<HomeController> logger, IEnvelopeReceiverService envelopeReceiverService, IEnvelopeHistoryService historyService, IStringLocalizer<Resource> localizer, IConfiguration configuration, HtmlSanitizer sanitizer, Cultures cultures, IEnvelopeMailService envelopeMailService, IEnvelopeReceiverReadOnlyService readOnlyService, IMessagingService messagingService, ICodeGenerator codeGenerator, IReceiverService receiverService)
|
||||||
{
|
{
|
||||||
this.envelopeOldService = envelopeOldService;
|
this.envelopeOldService = envelopeOldService;
|
||||||
_envRcvService = envelopeReceiverService;
|
_envRcvService = envelopeReceiverService;
|
||||||
@ -53,7 +52,6 @@ namespace EnvelopeGenerator.Web.Controllers
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
_readOnlyService = readOnlyService;
|
_readOnlyService = readOnlyService;
|
||||||
_msgService = messagingService;
|
_msgService = messagingService;
|
||||||
_erCache = envelopeReceiverCache;
|
|
||||||
_codeGenerator = codeGenerator;
|
_codeGenerator = codeGenerator;
|
||||||
_rcvService = receiverService;
|
_rcvService = receiverService;
|
||||||
}
|
}
|
||||||
@ -183,153 +181,152 @@ namespace EnvelopeGenerator.Web.Controllers
|
|||||||
//check access code
|
//check access code
|
||||||
EnvelopeResponse response = await envelopeOldService.LoadEnvelope(envelopeReceiverId);
|
EnvelopeResponse response = await envelopeOldService.LoadEnvelope(envelopeReceiverId);
|
||||||
|
|
||||||
return await _envRcvService.ReadWithSecretByUuidSignatureAsync(uuid: uuid, signature: signature).ThenAsync(
|
var er_secret_res = await _envRcvService.ReadWithSecretByUuidSignatureAsync(uuid: uuid, signature: signature);
|
||||||
SuccessAsync: async er_secret =>
|
|
||||||
|
if (er_secret_res.IsFailed)
|
||||||
|
{
|
||||||
|
_logger.LogNotice(er_secret_res.Notices);
|
||||||
|
return this.ViewEnvelopeNotFound();
|
||||||
|
}
|
||||||
|
var er_secret = er_secret_res.Data;
|
||||||
|
|
||||||
|
async Task<IActionResult> 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<IActionResult> TFAView(bool viaSms)
|
var res_json = JsonConvert.SerializeObject(res);
|
||||||
{
|
_logger.LogEnvelopeError(envelopeReceiverId: envelopeReceiverId, message: $"An unexpected error occurred while sending an SMS code. Response: ${res_json}");
|
||||||
if (viaSms)
|
return this.ViewInnerServiceError();
|
||||||
{
|
|
||||||
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<Claim> {
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
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<Claim> {
|
||||||
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user