This commit is contained in:
Developer01 2025-02-04 15:08:07 +01:00
commit c63f369bd6
39 changed files with 592 additions and 607 deletions

View File

@ -1,6 +1,6 @@
namespace EnvelopeGenerator.Application.Configurations
{
public class CodeGeneratorParams
public class AuthenticatorParams
{
public string CharPool { get; init; } = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789012345678901234567890123456789";

View File

@ -1,13 +0,0 @@
namespace EnvelopeGenerator.Application.Configurations
{
public class DispatcherConfig
{
public int SendingProfile { get; init; } = 1;
public string AddedWho { get; init; } = "DDEnvelopGenerator";
public int ReminderTypeId { get; init; } = 202377;
public string EmailAttmt1 { get; init; } = string.Empty;
}
}

View File

@ -0,0 +1,12 @@
namespace EnvelopeGenerator.Application.Configurations;
public class DispatcherParams
{
public int SendingProfile { get; init; } = 1;
public string AddedWho { get; init; } = "DDEnvelopGenerator";
public int ReminderTypeId { get; init; } = 202377;
public string EmailAttmt1 { get; init; } = string.Empty;
}

View File

@ -1,19 +0,0 @@
namespace EnvelopeGenerator.Application.Configurations
{
public class EnvelopeReceiverCacheParams
{
/// <summary>
/// Gets the cache key format for SMS codes.
/// The placeholder {0} represents the envelopeReceiverId.
/// </summary>
public string CodeCacheKeyFormat { get; init; } = "sms-code-{0}";
/// <summary>
/// Gets the cache expiration key format for SMS codes.
/// The placeholder {0} represents the envelopeReceiverId.
/// </summary>
public string CodeExpirationCacheKeyFormat { get; init; } = "sms-code-expiration-{0}";
public TimeSpan CodeCacheValidityPeriod { get; init; } = new(0, 5, 0);
}
}

View File

@ -1,25 +0,0 @@
using DigitalData.Core.Abstractions.Client;
using Microsoft.Extensions.Caching.Distributed;
namespace EnvelopeGenerator.Application.Configurations.GtxMessaging
{
/// <summary>
/// https://www.gtx-messaging.com/en/api-docs/sms-rest-api/
/// </summary>
public class SmsParams : IHttpClientOptions
{
public required string Uri { get; init; }
public string? Path { get; init; }
public Dictionary<string, object>? Headers { get; init; }
public Dictionary<string, object?>? QueryParams { get; init; }
public string RecipientQueryParamName { get; init; } = "to";
public string MessageQueryParamName { get; init; } = "text";
public int CodeLength { get; init; } = 5;
}
}

View File

@ -0,0 +1,20 @@
using DigitalData.Core.Abstractions.Client;
namespace EnvelopeGenerator.Application.Configurations;
/// <summary>
/// https://www.gtx-messaging.com/en/api-docs/sms-rest-api/
/// </summary>
public class GtxMessagingParams : IHttpClientOptions
{
public required string Uri { get; init; }
public string? Path { get; init; }
public Dictionary<string, object>? Headers { get; init; }
public Dictionary<string, object?>? QueryParams { get; init; }
public string RecipientQueryParamName { get; init; } = "to";
public string MessageQueryParamName { get; init; } = "text";
}

View File

@ -1,7 +0,0 @@
namespace EnvelopeGenerator.Application.Configurations
{
public class MailConfig
{
public required Dictionary<string, string> Placeholders { get; init; }
}
}

View File

@ -0,0 +1,6 @@
namespace EnvelopeGenerator.Application.Configurations;
public class MailParams
{
public required Dictionary<string, string> Placeholders { get; init; }
}

View File

@ -0,0 +1,49 @@
using OtpNet;
using System.Globalization;
namespace EnvelopeGenerator.Application.Configurations
{
public class TotpSmsParams
{
/// <summary>
/// The unit is second.
/// </summary>
public int TotpStep { get; init; } = 90;
public string Format { get; init; } = "Ihr 2FA-Passwort lautet {0}. Gültig bis {1}";
public ExpirationHandler Expiration { get; init; } = new();
public VerificationWindow? TotpVerificationWindow { get; private init; } = VerificationWindow.RfcSpecifiedNetworkDelay;
private IEnumerable<int>? _tvwParams;
public IEnumerable<int>? TotpVerificationWindowParams
{
get => _tvwParams;
init
{
_tvwParams = value;
if(_tvwParams is not null)
TotpVerificationWindow = new(previous: _tvwParams.ElementAtOrDefault(0), future: _tvwParams.ElementAtOrDefault(0));
}
}
public class ExpirationHandler
{
public string CacheKeyFormat { get; init; } = "e{0}_r{1}_sms_code_expiration";
public string Format { get; init; } = "HH:mm:ss";
public string CultureName
{
get => _cultureInfo.Name;
init => _cultureInfo = new(value);
}
private CultureInfo _cultureInfo = new("de-DE");
public CultureInfo CultureInfo => _cultureInfo;
}
}
}

View File

@ -0,0 +1,19 @@
using OtpNet;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IAuthenticator
{
string GenerateCode(int length);
string GenerateTotpSecretKey(int? length = null);
byte[] GenerateTotpQrCode(string userEmail, string secretKey, string? issuer = null, string? totpUrlFormat = null, int? pixelsPerModule = null);
byte[] GenerateTotpQrCode(string userEmail, int? length = null, string? issuer = null, string? totpUrlFormat = null, int? pixelsPerModule = null);
string GenerateTotp(string secretKey, int step = 30);
bool VerifyTotp(string totpCode, string secretKey, int step = 30, VerificationWindow? window = null);
}
}

View File

@ -1,13 +0,0 @@
namespace EnvelopeGenerator.Application.Contracts
{
public interface ICodeGenerator
{
string GenerateCode(int length);
public string GenerateTotpSecretKey(int? length = null);
public byte[] GenerateTotpQrCode(string userEmail, string secretKey, string? issuer = null, string? totpUrlFormat = null, int? pixelsPerModule = null);
public byte[] GenerateTotpQrCode(string userEmail, int? length = null, string? issuer = null, string? totpUrlFormat = null, int? pixelsPerModule = null);
}
}

View File

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

View File

@ -0,0 +1,17 @@
using EnvelopeGenerator.Application.DTOs.EnvelopeReceiver;
using EnvelopeGenerator.Application.DTOs.Messaging;
namespace EnvelopeGenerator.Application.Contracts;
public interface IEnvelopeSmsHandler
{
/// <summary>
/// If expiration is passed then, sends sms and returns smsResponse and up-to-date expiration; otherwise send expiration.
/// </summary>
/// <param name="er_secret"></param>
/// <param name="cToken"></param>
/// <returns></returns>
Task<(SmsResponse? SmsResponse, DateTime Expiration)> SendTotpAsync(EnvelopeReceiverSecretDto er_secret, CancellationToken cToken = default);
bool VerifyTotp(string totpCode, string secretKey);
}

View File

@ -1,24 +0,0 @@
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IJWTService<TClaimValue>
{
public static SymmetricSecurityKey GenerateSecurityKey(int byteSize = 32)
{
using var rng = RandomNumberGenerator.Create();
var randomBytes = new byte[byteSize];
rng.GetBytes(randomBytes);
var securityKey = new SymmetricSecurityKey(randomBytes);
return securityKey;
}
string GenerateToken(TClaimValue claimValue);
JwtSecurityToken? ReadSecurityToken(string token);
}
}

View File

@ -1,13 +0,0 @@
using EnvelopeGenerator.Application.DTOs.Messaging;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IMessagingService
{
string ServiceProvider { get; }
Task<SmsResponse> SendSmsAsync(string recipient, string message);
Task<SmsResponse> SendSmsCodeAsync(string recipient, string envelopeReceiverId);
}
}

View File

@ -0,0 +1,11 @@
using EnvelopeGenerator.Application.DTOs.Messaging;
namespace EnvelopeGenerator.Application.Contracts;
//TODO: move to DigitalData.Core
public interface ISmsSender
{
string ServiceProvider { get; }
Task<SmsResponse> SendSmsAsync(string recipient, string message);
}

View File

@ -51,6 +51,8 @@ namespace EnvelopeGenerator.Application.DTOs
public int? ExpiresWarningWhenDays { get; set; }
public bool TFAEnabled { get; init; }
public bool DmzMoved { get; set; }
public UserReadDto? User { get; set; }
public EnvelopeType? EnvelopeType { get; set; }

View File

@ -27,7 +27,5 @@ namespace EnvelopeGenerator.Application.DTOs.EnvelopeReceiver
public DateTime? ChangedWhen { get; init; }
public bool HasPhoneNumber { get; init; }
public bool TFAEnabled { get; init; }
}
}

View File

@ -1,4 +1,4 @@
namespace EnvelopeGenerator.Domain.HttpResponse
namespace EnvelopeGenerator.Application.DTOs.Messaging
{
public class GtxMessagingResponse : Dictionary<string, object?> { }
}

View File

@ -4,15 +4,7 @@
{
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 bool Failed => !Ok;
public dynamic? Errors { get; init; }
}

View File

@ -18,6 +18,7 @@
<PackageReference Include="DigitalData.Core.DTO" Version="2.0.0" />
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.18" />
<PackageReference Include="Otp.NET" Version="1.4.0" />
<PackageReference Include="QRCoder" Version="1.6.0" />
<PackageReference Include="QRCoder-ImageSharp" Version="0.10.0" />
<PackageReference Include="UserManager.Application" Version="2.0.0" />

View File

@ -4,79 +4,128 @@ namespace EnvelopeGenerator.Application.Extensions
{
public static class CacheExtensions
{
public static Task SetLongAsync(this IDistributedCache cache, string key, long value, DistributedCacheEntryOptions? options = null)
public static Task SetLongAsync(this IDistributedCache cache, string key, long value, DistributedCacheEntryOptions? options = null, CancellationToken cToken = default)
=> options is null
? cache.SetAsync(key, BitConverter.GetBytes(value))
: cache.SetAsync(key, BitConverter.GetBytes(value), options: options);
? cache.SetAsync(key, BitConverter.GetBytes(value), token: cToken)
: cache.SetAsync(key, BitConverter.GetBytes(value), options: options, token: cToken);
public static async Task<long?> GetLongAsync(this IDistributedCache cache, string key)
public static async Task<long?> GetLongAsync(this IDistributedCache cache, string key, CancellationToken cToken = default)
{
var value = await cache.GetAsync(key);
var value = await cache.GetAsync(key, cToken);
return value is null ? null : BitConverter.ToInt64(value, 0);
}
public static Task SetDateTimeAsync(this IDistributedCache cache, string key, DateTime value, DistributedCacheEntryOptions? options = null)
=> cache.SetLongAsync(key: key, value: value.Ticks, options: options);
public static Task SetDateTimeAsync(this IDistributedCache cache, string key, DateTime value, DistributedCacheEntryOptions? options = null, CancellationToken cToken = default)
=> cache.SetLongAsync(key: key, value: value.Ticks, options: options, cToken: cToken);
public static async Task<DateTime?> GetDateTimeAsync(this IDistributedCache cache, string key)
public static async Task<DateTime?> GetDateTimeAsync(this IDistributedCache cache, string key, CancellationToken cToken = default)
{
var value = await cache.GetAsync(key);
var value = await cache.GetAsync(key, cToken);
return value is null ? null : new(BitConverter.ToInt64(value, 0));
}
public static Task SetTimeSpanAsync(this IDistributedCache cache, string key, TimeSpan value, DistributedCacheEntryOptions? options = null)
=> cache.SetLongAsync(key: key, value: value.Ticks, options: options);
public static Task SetTimeSpanAsync(this IDistributedCache cache, string key, TimeSpan value, DistributedCacheEntryOptions? options = null, CancellationToken cToken = default)
=> cache.SetLongAsync(key: key, value: value.Ticks, options: options, cToken);
public static async Task<TimeSpan?> GetTimeSpanAsync(this IDistributedCache cache, string key)
public static async Task<TimeSpan?> GetTimeSpanAsync(this IDistributedCache cache, string key, CancellationToken cToken = default)
{
var value = await cache.GetAsync(key);
var value = await cache.GetAsync(key, cToken);
return value is null ? null : new(BitConverter.ToInt64(value, 0));
}
public static string GetOrSet(this IDistributedCache cache, string key, Func<string> factory, DistributedCacheEntryOptions? options = null, bool cacheInBackground = false, CancellationToken token = default)
//TODO: use code generator
#region GetOrSetAsync
#region string
public static async Task<string> GetOrSetAsync(this IDistributedCache cache, string key, Func<string> factory, DistributedCacheEntryOptions? options = null, bool cacheInBackground = false, CancellationToken cToken = default)
{
var value = cache.GetString(key);
var value = await cache.GetStringAsync(key, cToken);
if (value is null)
{
// create new and save
value = factory();
void Cache()
{
if (options is null)
cache.SetString(key: key, value: value);
else
cache.SetString(key: key, value: value, options: options);
}
if (cacheInBackground)
_ = Task.Run(() => Cache(), token);
else
Cache();
}
return value;
}
public static async Task<string> GetOrSetAsync(this IDistributedCache cache, string key, Func<Task<string>> factory, DistributedCacheEntryOptions? options = null, bool cacheInBackground = false, CancellationToken token = default)
{
var value = await cache.GetStringAsync(key, token: token);
if(value is null)
{
// create new and save
value = await factory();
Task CacheAsync() => options is null
? cache.SetStringAsync(key: key, value: value, token: token)
: cache.SetStringAsync(key: key, value: value, options: options, token: token);
? cache.SetStringAsync(key, value, cToken)
: cache.SetStringAsync(key, value, options, cToken);
if (cacheInBackground)
_ = Task.Run(async () => await CacheAsync(), token);
_ = Task.Run(async () => await CacheAsync(), cToken);
else
await CacheAsync();
}
return value;
}
public static async Task<string> GetOrSetAsync(this IDistributedCache cache, string key, Func<Task<string>> factoryAsync, DistributedCacheEntryOptions? options = null, bool cacheInBackground = false, CancellationToken cToken = default)
{
var value = await cache.GetStringAsync(key, cToken);
if(value is null)
{
// create new and save
value = await factoryAsync();
Task CacheAsync() => options is null
? cache.SetStringAsync(key: key, value: value, token: cToken)
: cache.SetStringAsync(key: key, value: value, options: options, token: cToken);
if (cacheInBackground)
_ = Task.Run(async () => await CacheAsync(), cToken);
else
await CacheAsync();
}
return value;
}
#endregion
#region DateTime
public static async Task<DateTime> GetOrSetAsync(this IDistributedCache cache, string key, Func<DateTime> factory, DistributedCacheEntryOptions? options = null, bool cacheInBackground = false, CancellationToken cToken = default)
{
if (await cache.GetDateTimeAsync(key, cToken) is DateTime dateTimeValue)
return dateTimeValue;
else
{
// create new and save
var newValue = factory();
Task CacheAsync() => options is null
? cache.SetDateTimeAsync(key, newValue, cToken: cToken)
: cache.SetDateTimeAsync(key, newValue, options, cToken);
if (cacheInBackground)
_ = Task.Run(async () => await CacheAsync(), cToken);
else
await CacheAsync();
return newValue;
}
}
public static async Task<DateTime> GetOrSetAsync(this IDistributedCache cache, string key, Func<Task<DateTime>> factory, DistributedCacheEntryOptions? options = null, bool cacheInBackground = false, CancellationToken cToken = default)
{
if (await cache.GetDateTimeAsync(key, cToken) is DateTime dateTimeValue)
return dateTimeValue;
else
{
// create new and save
var newValue = await factory();
Task CacheAsync() => options is null
? cache.SetDateTimeAsync(key, newValue, cToken: cToken)
: cache.SetDateTimeAsync(key, newValue, options, cToken);
if (cacheInBackground)
_ = Task.Run(async () => await CacheAsync(), cToken);
else
await CacheAsync();
return newValue;
}
}
#endregion
#endregion
}
}

View File

@ -9,14 +9,13 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using DigitalData.Core.Client;
using EnvelopeGenerator.Application.Configurations.GtxMessaging;
using QRCoder;
namespace EnvelopeGenerator.Application.Extensions
{
public static class DIExtensions
{
public static IServiceCollection AddEnvelopeGenerator(this IServiceCollection services, IConfigurationSection dispatcherConfigSection, IConfigurationSection mailConfigSection, IConfigurationSection smsConfigSection, IConfigurationSection codeGeneratorConfigSection, IConfigurationSection envelopeReceiverCacheParamsSection)
public static IServiceCollection AddEnvelopeGenerator(this IServiceCollection services, IConfiguration config)
{
//Inject CRUD Service and repositoriesad
services.TryAddScoped<IConfigRepository, ConfigRepository>();
@ -54,25 +53,22 @@ namespace EnvelopeGenerator.Application.Extensions
services.AddAutoMapper(typeof(BasicDtoMappingProfile).Assembly);
services.AddAutoMapper(typeof(UserMappingProfile).Assembly);
services.Configure<DispatcherConfig>(dispatcherConfigSection);
services.Configure<MailConfig>(mailConfigSection);
services.Configure<CodeGeneratorParams>(codeGeneratorConfigSection);
services.Configure<EnvelopeReceiverCacheParams>(envelopeReceiverCacheParamsSection);
services.ConfigureByTypeName<DispatcherParams>(config);
services.ConfigureByTypeName<MailParams>(config);
services.ConfigureByTypeName<AuthenticatorParams>(config);
services.ConfigureByTypeName<TotpSmsParams>(config);
services.AddHttpClientService<SmsParams>(smsConfigSection);
services.TryAddSingleton<IMessagingService, GtxMessagingService>();
services.TryAddSingleton<ICodeGenerator, CodeGenerator>();
services.TryAddSingleton<IEnvelopeReceiverCache, EnvelopeReceiverCache>();
services.AddHttpClientService<GtxMessagingParams>(config.GetSection(nameof(GtxMessagingParams)));
services.TryAddSingleton<ISmsSender, GTXSmsSender>();
services.TryAddSingleton<IEnvelopeSmsHandler, EnvelopeSmsHandler>();
services.TryAddSingleton<IAuthenticator, Authenticator>();
services.TryAddSingleton<QRCodeGenerator>();
return services;
}
public static IServiceCollection AddEnvelopeGenerator(this IServiceCollection services, IConfiguration config) => services.AddEnvelopeGenerator(
dispatcherConfigSection: config.GetSection("DispatcherConfig"),
mailConfigSection: config.GetSection("MailConfig"),
smsConfigSection: config.GetSection("SmsConfig"),
codeGeneratorConfigSection: config.GetSection("CodeGeneratorParams"),
envelopeReceiverCacheParamsSection: config.GetSection("EnvelopeReceiverCacheParams"));
//TODO: move to DigitalData.Core
private static IServiceCollection ConfigureByTypeName<TOptions>(this IServiceCollection services, IConfiguration configuration) where TOptions : class
=> services.Configure<TOptions>(configuration.GetSection(nameof(TOptions)));
}
}

View File

@ -1,4 +1,4 @@
using EnvelopeGenerator.Domain.HttpResponse;
using EnvelopeGenerator.Application.DTOs.Messaging;
namespace EnvelopeGenerator.Application.Extensions
{

View File

@ -7,7 +7,6 @@ using EnvelopeGenerator.Application.DTOs.Messaging;
using EnvelopeGenerator.Application.DTOs.Receiver;
using EnvelopeGenerator.Application.Extensions;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Domain.HttpResponse;
namespace EnvelopeGenerator.Application.MappingProfiles
{

View File

@ -7,17 +7,17 @@ using System.Text;
namespace EnvelopeGenerator.Application.Services
{
public class CodeGenerator : ICodeGenerator
public class Authenticator : IAuthenticator
{
public static Lazy<CodeGenerator> LazyStatic => new(() => new CodeGenerator(Options.Create<CodeGeneratorParams>(new()), new QRCodeGenerator()));
public static Lazy<Authenticator> LazyStatic => new(() => new Authenticator(Options.Create<AuthenticatorParams>(new()), new QRCodeGenerator()));
public static CodeGenerator Static => LazyStatic.Value;
public static Authenticator Static => LazyStatic.Value;
private readonly CodeGeneratorParams _params;
private readonly AuthenticatorParams _params;
private readonly QRCodeGenerator _qrCodeGenerator;
public CodeGenerator(IOptions<CodeGeneratorParams> options, QRCodeGenerator qrCodeGenerator)
public Authenticator(IOptions<AuthenticatorParams> options, QRCodeGenerator qrCodeGenerator)
{
_params = options.Value;
_qrCodeGenerator = qrCodeGenerator;
@ -62,5 +62,10 @@ namespace EnvelopeGenerator.Application.Services
totpUrlFormat: totpUrlFormat,
pixelsPerModule: pixelsPerModule);
}
public string GenerateTotp(string secretKey, int step = 30) => new Totp(Base32Encoding.ToBytes(secretKey), step).ComputeTotp();
public bool VerifyTotp(string totpCode, string secretKey, int step = 30, VerificationWindow? window = null)
=> new Totp(Base32Encoding.ToBytes(secretKey), step).VerifyTotp(totpCode, out _, window);
}
}

View File

@ -21,19 +21,19 @@ namespace EnvelopeGenerator.Application.Services
{
private readonly IEmailTemplateService _tempService;
private readonly IEnvelopeReceiverService _envRcvService;
private readonly DispatcherConfig _dConfig;
private readonly DispatcherParams _dConfig;
private readonly IConfigService _configService;
private readonly Dictionary<string, string> _placeholders;
private readonly ICodeGenerator _codeGenerator;
private readonly IAuthenticator _authenticator;
public EnvelopeMailService(IEmailOutRepository repository, IMapper mapper, IEmailTemplateService tempService, IEnvelopeReceiverService envelopeReceiverService, IOptions<DispatcherConfig> dispatcherConfigOptions, IConfigService configService, IOptions<MailConfig> mailConfig, ICodeGenerator codeGenerator) : base(repository, mapper)
public EnvelopeMailService(IEmailOutRepository repository, IMapper mapper, IEmailTemplateService tempService, IEnvelopeReceiverService envelopeReceiverService, IOptions<DispatcherParams> dispatcherConfigOptions, IConfigService configService, IOptions<MailParams> mailConfig, IAuthenticator authenticator) : base(repository, mapper)
{
_tempService = tempService;
_envRcvService = envelopeReceiverService;
_dConfig = dispatcherConfigOptions.Value;
_configService = configService;
_placeholders = mailConfig.Value.Placeholders;
_codeGenerator = codeGenerator;
_authenticator = authenticator;
}
private async Task<Dictionary<string, string>> CreatePlaceholders(string? accessCode = null, EnvelopeReceiverDto? envelopeReceiverDto = null)
@ -167,7 +167,7 @@ namespace EnvelopeGenerator.Application.Services
if (dto.Receiver.TotpExpiration is null)
throw new ArgumentNullException(nameof(dto), $"TFA Qr Code cannot sent. Receiver.TotpExpiration is null. Envelope receiver dto is {JsonConvert.SerializeObject(dto)}");
var totp_qr_64 = _codeGenerator.GenerateTotpQrCode(userEmail: dto.Receiver.EmailAddress, secretKey: dto.Receiver.TotpSecretkey).ToBase64String();
var totp_qr_64 = _authenticator.GenerateTotpQrCode(userEmail: dto.Receiver.EmailAddress, secretKey: dto.Receiver.TotpSecretkey).ToBase64String();
return SendAsync(dto, EmailTemplateType.TotpSecret, new()
{
{"[TFA_QR_CODE]", totp_qr_64 },

View File

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

View File

@ -17,13 +17,13 @@ namespace EnvelopeGenerator.Application.Services
{
private readonly IStringLocalizer<Resource> _localizer;
private readonly IMessagingService _messagingService;
private readonly ISmsSender _smsSender;
public EnvelopeReceiverService(IEnvelopeReceiverRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper, IMessagingService messagingService)
public EnvelopeReceiverService(IEnvelopeReceiverRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper, ISmsSender smsSender)
: base(repository, mapper)
{
_localizer = localizer;
_messagingService = messagingService;
_smsSender = smsSender;
}
public async Task<DataResult<IEnumerable<EnvelopeReceiverDto>>> ReadBySignatureAsync(string signature, bool withEnvelope = false, bool withReceiver = true, bool readOnly = true)
@ -171,7 +171,7 @@ namespace EnvelopeGenerator.Application.Services
.Message(Key.PhoneNumberNonexists)
.Notice(LogLevel.Error, Flag.NotFound, $"An attempt was made to send sms to the user whose phone number is null. Envelope recipient ID is {envelopeReceiverId}, UUID is {uuid} and signature is {signature}.");
var res = await _messagingService.SendSmsAsync(recipient: env_rcv.PhoneNumber, message: message);
var res = await _smsSender.SendSmsAsync(recipient: env_rcv.PhoneNumber, message: message);
return Result.Success(res);
}

View File

@ -0,0 +1,53 @@
using EnvelopeGenerator.Application.Configurations;
using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.DTOs.EnvelopeReceiver;
using EnvelopeGenerator.Application.DTOs.Messaging;
using EnvelopeGenerator.Application.Extensions;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
namespace EnvelopeGenerator.Application.Services;
public class EnvelopeSmsHandler : IEnvelopeSmsHandler
{
private readonly ISmsSender _sender;
private readonly TotpSmsParams _totpSmsParams;
private readonly IDistributedCache _dCache;
private readonly IAuthenticator _authenticator;
public EnvelopeSmsHandler(ISmsSender sender, IOptions<TotpSmsParams> totpSmsParamsOptions, IDistributedCache distributedCache, IAuthenticator authenticator)
{
_sender = sender;
_totpSmsParams = totpSmsParamsOptions.Value;
_dCache = distributedCache;
_authenticator = authenticator;
}
/// <summary>
/// If expiration is passed then, sends sms and returns smsResponse and up-to-date expiration; otherwise send expiration.
/// </summary>
/// <param name="er_secret"></param>
/// <param name="cToken"></param>
/// <returns></returns>
public async Task<(SmsResponse? SmsResponse, DateTime Expiration)> SendTotpAsync(EnvelopeReceiverSecretDto er_secret, CancellationToken cToken = default)
{
var key = string.Format(_totpSmsParams.Expiration.CacheKeyFormat, er_secret.EnvelopeId, er_secret.ReceiverId);
var expiration = await _dCache.GetDateTimeAsync(key, cToken);
if(expiration is DateTime expirationDateTime && expirationDateTime < DateTime.Now)
return (null, expirationDateTime);
else
{
var new_expiration = DateTime.Now.AddSeconds(_totpSmsParams.TotpStep);
var totp = _authenticator.GenerateTotp(er_secret.Receiver!.TotpSecretkey!, _totpSmsParams.TotpStep);
var msg = string.Format(_totpSmsParams.Format, totp, new_expiration.ToString(_totpSmsParams.Expiration.Format, _totpSmsParams.Expiration.CultureInfo));
return (await _sender.SendSmsAsync(er_secret.PhoneNumber!, msg), new_expiration);
}
}
public bool VerifyTotp(string totpCode, string secretKey) => _authenticator
.VerifyTotp(totpCode, secretKey, _totpSmsParams.TotpStep, _totpSmsParams.TotpVerificationWindow);
}

View File

@ -1,70 +0,0 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Client;
using DigitalData.Core.Client;
using EnvelopeGenerator.Application.Configurations.GtxMessaging;
using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.DTOs.Messaging;
using EnvelopeGenerator.Application.Extensions;
using EnvelopeGenerator.Domain.HttpResponse;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
namespace EnvelopeGenerator.Application.Services
{
public class GtxMessagingService : IMessagingService
{
private readonly IHttpClientService<SmsParams> _smsClient;
private readonly SmsParams _smsParams;
private readonly IMapper _mapper;
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 = smsParamsOptions.Value;
_mapper = mapper;
ServiceProvider = GetType().Name.Replace("Service", string.Empty);
_codeGen = codeGenerator;
_erCache = envelopeReceiverCache;
}
public async Task<SmsResponse> SendSmsAsync(string recipient, string message)
{
return await _smsClient.FetchAsync(queryParams: new Dictionary<string, object?>()
{
{ _smsParams.RecipientQueryParamName, recipient },
{ _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 };
}
}
}
}

View File

@ -0,0 +1,40 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Client;
using DigitalData.Core.Client;
using EnvelopeGenerator.Application.Configurations;
using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.DTOs.Messaging;
using Microsoft.Extensions.Options;
namespace EnvelopeGenerator.Application.Services;
//TODO: move to DigitalData.Core
public class GTXSmsSender : ISmsSender
{
private readonly IHttpClientService<GtxMessagingParams> _smsClient;
private readonly GtxMessagingParams _smsParams;
private readonly IMapper _mapper;
public string ServiceProvider { get; }
public GTXSmsSender(IHttpClientService<GtxMessagingParams> smsClient, IOptions<GtxMessagingParams> smsParamsOptions, IMapper mapper)
{
_smsClient = smsClient;
_smsParams = smsParamsOptions.Value;
_mapper = mapper;
ServiceProvider = GetType().Name.Replace("Service", string.Empty);
}
public async Task<SmsResponse> SendSmsAsync(string recipient, string message)
{
return await _smsClient.FetchAsync(queryParams: new Dictionary<string, object?>()
{
{ _smsParams.RecipientQueryParamName, recipient },
{ _smsParams.MessageQueryParamName, message }
})
.ThenAsync(res => res.Json<GtxMessagingResponse>())
.ThenAsync(_mapper.Map<SmsResponse>);
}
}

View File

@ -84,6 +84,9 @@ namespace EnvelopeGenerator.Domain.Entities
[Column("EXPIRES_WARNING_WHEN_DAYS")]
public int? ExpiresWarningWhenDays { get; set; }
[Column("TFA_ENABLED", TypeName = "bit")]
public bool TFAEnabled { get; set; }
/// <summary>
/// The sender of envelope
/// </summary>

View File

@ -46,9 +46,6 @@ namespace EnvelopeGenerator.Domain.Entities
[RegularExpression(@"^\+[0-9]+$", ErrorMessage = "Phone number must start with '+' followed by digits.")]
public string? PhoneNumber { get; set; }
[Column("TFA_ENABLED", TypeName = "bit")]
public bool TFAEnabled { get; set; }
[NotMapped]
public (int Envelope, int Receiver) Id => (Envelope: EnvelopeId, Receiver: ReceiverId);

View File

@ -35,12 +35,11 @@ namespace EnvelopeGenerator.Web.Controllers
private readonly Cultures _cultures;
private readonly IEnvelopeMailService _mailService;
private readonly IEnvelopeReceiverReadOnlyService _readOnlyService;
private readonly IMessagingService _msgService;
private readonly IEnvelopeReceiverCache _erCache;
private readonly ICodeGenerator _codeGenerator;
private readonly IAuthenticator _authenticator;
private readonly IReceiverService _rcvService;
private readonly IEnvelopeSmsHandler _envSmsHandler;
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, IAuthenticator authenticator, IReceiverService receiverService, IEnvelopeSmsHandler envelopeSmsService)
{
this.envelopeOldService = envelopeOldService;
_envRcvService = envelopeReceiverService;
@ -52,10 +51,9 @@ namespace EnvelopeGenerator.Web.Controllers
_mailService = envelopeMailService;
_logger = logger;
_readOnlyService = readOnlyService;
_msgService = messagingService;
_erCache = envelopeReceiverCache;
_codeGenerator = codeGenerator;
_authenticator = authenticator;
_rcvService = receiverService;
_envSmsHandler = envelopeSmsService;
}
[HttpGet("/")]
@ -146,7 +144,7 @@ namespace EnvelopeGenerator.Web.Controllers
return await _envRcvService.ReadByEnvelopeReceiverIdAsync(envelopeReceiverId: envelopeReceiverId).ThenAsync(
Success: er => View()
.WithData("EnvelopeKey", envelopeReceiverId)
.WithData("TFAEnabled", er.TFAEnabled)
.WithData("TFAEnabled", er.Envelope!.TFAEnabled)
.WithData("HasPhoneNumber", er.HasPhoneNumber),
Fail: IActionResult (messages, notices) =>
{
@ -162,6 +160,91 @@ namespace EnvelopeGenerator.Web.Controllers
}
}
#region TFA Views
[NonAction]
private async Task<IActionResult> TFAViewAsync(bool viaSms, EnvelopeReceiverSecretDto er_secret, string envelopeReceiverId)
{
if (viaSms)
{
var (smsRes, expiration) = await _envSmsHandler.SendTotpAsync(er_secret);
if (smsRes is not null && smsRes.Failed)
{
var res_json = JsonConvert.SerializeObject(smsRes);
_logger.LogEnvelopeError(envelopeReceiverId: envelopeReceiverId, message: $"An unexpected error occurred while sending an SMS code. Response: ${res_json}");
return this.ViewInnerServiceError();
}
return View("EnvelopeLocked").WithData("CodeType", "smsCode").WithData("SmsExpiration", expiration);
}
else
{
return View("EnvelopeLocked").WithData("CodeType", "authenticatorCode").WithData("QRCodeExpiration", er_secret.Receiver?.TotpExpiration);
}
}
[NonAction]
private async Task<IActionResult?> HandleAccessCodeAsync(Auth auth, EnvelopeReceiverSecretDto er_secret, string envelopeReceiverId)
{
//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 = _authenticator.GenerateTotpSecretKey();
rcv.TotpExpiration = DateTime.Now.AddMonths(1);
await _rcvService.UpdateAsync(rcv);
await _mailService.SendTFAQrCodeAsync(er_secret);
}
return await TFAViewAsync(auth.UserSelectSMS, er_secret, envelopeReceiverId);
}
return null;
}
[NonAction]
private async Task<IActionResult?> HandleSmsAsync(Auth auth, EnvelopeReceiverSecretDto er_secret, string envelopeReceiverId)
{
if (er_secret.Receiver!.TotpSecretkey is null)
throw new InvalidOperationException($"TotpSecretkey of DTO cannot validate without TotpSecretkey. Dto: {JsonConvert.SerializeObject(er_secret)}");
if (_envSmsHandler.VerifyTotp(auth.SmsCode!, er_secret.Receiver.TotpSecretkey))
{
Response.StatusCode = StatusCodes.Status401Unauthorized;
ViewData["ErrorMessage"] = _localizer[WebKey.WrongAccessCode].Value;
return await TFAViewAsync(viaSms: true, er_secret, envelopeReceiverId);
}
return null;
}
[NonAction]
private async Task<IActionResult?> HandleAuthenticatorAsync(Auth auth, EnvelopeReceiverSecretDto er_secret, string envelopeReceiverId)
{
if (er_secret.Receiver!.IsTotpInvalid(totp: auth.AuthenticatorCode!))
{
Response.StatusCode = StatusCodes.Status401Unauthorized;
ViewData["ErrorMessage"] = _localizer[WebKey.WrongAccessCode].Value;
return await TFAViewAsync(viaSms: false, er_secret, envelopeReceiverId);
}
return null;
}
#endregion
[HttpPost("EnvelopeKey/{envelopeReceiverId}/Locked")]
public async Task<IActionResult> LogInEnvelope([FromRoute] string envelopeReceiverId, [FromForm] Auth auth)
{
@ -183,30 +266,14 @@ 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)
{
async Task<IActionResult> 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);
}
_logger.LogNotice(er_secret_res.Notices);
return this.ViewEnvelopeNotFound();
}
var er_secret = er_secret_res.Data;
if (auth.HasMulti)
{
@ -215,56 +282,14 @@ namespace EnvelopeGenerator.Web.Controllers
.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.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);
}
}
if(await HandleAccessCodeAsync(auth, er_secret, envelopeReceiverId) is IActionResult acView)
return acView;
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);
}
}
if(await HandleSmsAsync(auth, er_secret, envelopeReceiverId) is IActionResult smsView)
return smsView;
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);
}
}
if(await HandleAuthenticatorAsync(auth, er_secret, envelopeReceiverId) is IActionResult aView)
return aView;
else
{
Response.StatusCode = StatusCodes.Status401Unauthorized;
@ -323,13 +348,6 @@ namespace EnvelopeGenerator.Web.Controllers
ViewData["PSPDFKitLicenseKey"] = _configuration["PSPDFKitLicenseKey"];
return View("ShowEnvelope", er);
},
Fail: (messages, notices) =>
{
_logger.LogNotice(notices);
return this.ViewEnvelopeNotFound();
}
);
}
catch (Exception ex)
{
@ -384,7 +402,8 @@ namespace EnvelopeGenerator.Web.Controllers
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return await _envRcvService.ReadByEnvelopeReceiverIdAsync(envelopeReceiverId).ThenAsync(
SuccessAsync: async (er) =>
{ViewData["UserCulture"] = _cultures[UserLanguage];
{
ViewData["UserCulture"] = _cultures[UserLanguage];
ViewData["UserCulture"] = _cultures[UserLanguage];
return await _historyService.IsRejected(envelopeId: er.EnvelopeId)
? View(er)

View File

@ -1,42 +0,0 @@
using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace EnvelopeGenerator.Web.Controllers.Test
{
[ApiController]
[Route("api/test/[controller]")]
public class TestAuthController : ControllerBase
{
private readonly IJWTService<string> _authService;
public TestAuthController(IJWTService<string> authService)
{
_authService = authService;
}
[HttpPost]
public IActionResult ProvideToken([FromQuery] string value)
{
var token = _authService.GenerateToken(value);
return Ok(token);
}
[HttpGet]
public IActionResult GetSecurityToken([FromQuery] string token)
{
var sToken = _authService.ReadSecurityToken(token);
return Ok(sToken);
}
[HttpGet("Username")]
[Authorize]
public IActionResult Getname()
{
var username = User.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
return Ok(username);
}
}
}

View File

@ -7,9 +7,9 @@ namespace EnvelopeGenerator.Web.Controllers.Test
[ApiController]
public class TestMessagingController : ControllerBase
{
private readonly IMessagingService _service;
private readonly ISmsSender _service;
public TestMessagingController(IMessagingService service)
public TestMessagingController(ISmsSender service)
{
_service = service;
}

View File

@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PackageId>EnvelopeGenerator.Web</PackageId>
<Version>2.8.1</Version>
<Version>2.9.0</Version>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>EnvelopeGenerator.Web</Product>
@ -13,8 +13,8 @@
<PackageTags>digital data envelope generator web</PackageTags>
<Description>EnvelopeGenerator.Web is an ASP.NET MVC application developed to manage signing processes. It uses Entity Framework Core (EF Core) for database operations. The user interface for signing processes is developed with Razor View Engine (.cshtml files) and JavaScript under wwwroot, integrated with PSPDFKit. This integration allows users to view and sign documents seamlessly.</Description>
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
<AssemblyVersion>2.8.1</AssemblyVersion>
<FileVersion>2.8.1</FileVersion>
<AssemblyVersion>2.9.0</AssemblyVersion>
<FileVersion>2.9.0</FileVersion>
<Copyright>Copyright © 2024 Digital Data GmbH. All rights reserved.</Copyright>
</PropertyGroup>

View File

@ -70,11 +70,6 @@
}
]
},
"Jwt": {
"Issuer": null,
"Audience": null,
"Key": "8RGnd7x0G2TYLOIW4m_qlIls7MfbAIGNrpQJzMAUIvULHOLiG723znRa_MG-Z4yw3SErusOU4hTui2rVBMcCaQ"
},
"ContactLink": {
"Label": "Kontakt",
"Href": "https://digitaldata.works/",
@ -113,30 +108,25 @@
"ShowPageClass": "dd-show-logo",
"LockedPageClass": "dd-locked-logo"
},
"DispatcherConfig": {
"DispatcherParams": {
"SendingProfile": 1,
"AddedWho": "DDEnvelopGenerator",
"ReminderTypeId": 202377,
"EmailAttmt1": ""
},
"MailConfig": {
"MailParams": {
"Placeholders": {
"[NAME_PORTAL]": "signFlow",
"[SIGNATURE_TYPE]": "signieren",
"[REASON]": ""
}
},
"GTXMessagingConfig": {
"AuthKey": "ep$?A!Gs"
},
"SmsConfig": {
"GtxMessagingParams": {
"Uri": "https://rest.gtx-messaging.net",
"Path": "smsc/sendsms/f566f7e5-bdf2-4a9a-bf52-ed88215a432e/json",
"Headers": {},
"QueryParams": {
"from": "signFlow"
},
"CodeCacheValidityPeriod": "00:10:00"
},
"EnvelopeReceiverCacheParams": {}
}
}
}