diff --git a/EnvelopeGenerator.Application/Configurations/CodeGeneratorConfig.cs b/EnvelopeGenerator.Application/Configurations/CodeGeneratorConfig.cs
deleted file mode 100644
index b44c989a..00000000
--- a/EnvelopeGenerator.Application/Configurations/CodeGeneratorConfig.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace EnvelopeGenerator.Application.Configurations
-{
- public class CodeGeneratorConfig
- {
- public string CharPool { get; init; } = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789012345678901234567890123456789";
- }
-}
\ No newline at end of file
diff --git a/EnvelopeGenerator.Application/Configurations/CodeGeneratorParams.cs b/EnvelopeGenerator.Application/Configurations/CodeGeneratorParams.cs
new file mode 100644
index 00000000..f4c8e1b1
--- /dev/null
+++ b/EnvelopeGenerator.Application/Configurations/CodeGeneratorParams.cs
@@ -0,0 +1,18 @@
+namespace EnvelopeGenerator.Application.Configurations
+{
+ public class CodeGeneratorParams
+ {
+ public string CharPool { get; init; } = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789012345678901234567890123456789";
+
+ public int DefaultTotpSecretKeyLength { get; init; } = 32;
+
+ public string TotpIssuer { get; init; } = "signFlow";
+
+ ///
+ /// 0 is user email, 1 is secret key and 2 is issuer.
+ ///
+ public string TotpUrlFormat { get; init; } = "otpauth://totp/{0}?secret={1}&issuer={2}";
+
+ public int TotpQRPixelsPerModule { get; init; } = 20;
+ }
+}
\ No newline at end of file
diff --git a/EnvelopeGenerator.Application/Contracts/ICodeGenerator.cs b/EnvelopeGenerator.Application/Contracts/ICodeGenerator.cs
index 4a282c66..38a27b85 100644
--- a/EnvelopeGenerator.Application/Contracts/ICodeGenerator.cs
+++ b/EnvelopeGenerator.Application/Contracts/ICodeGenerator.cs
@@ -3,5 +3,11 @@
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);
}
}
\ No newline at end of file
diff --git a/EnvelopeGenerator.Application/Extensions/DIExtensions.cs b/EnvelopeGenerator.Application/Extensions/DIExtensions.cs
index 3c1c62d5..ae8fedf9 100644
--- a/EnvelopeGenerator.Application/Extensions/DIExtensions.cs
+++ b/EnvelopeGenerator.Application/Extensions/DIExtensions.cs
@@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using DigitalData.Core.Client;
using EnvelopeGenerator.Application.Configurations.GtxMessaging;
+using QRCoder;
namespace EnvelopeGenerator.Application.Extensions
{
@@ -55,13 +56,14 @@ namespace EnvelopeGenerator.Application.Extensions
services.Configure(dispatcherConfigSection);
services.Configure(mailConfigSection);
- services.Configure(codeGeneratorConfigSection);
+ services.Configure(codeGeneratorConfigSection);
services.Configure(envelopeReceiverCacheParamsSection);
services.AddHttpClientService(smsConfigSection);
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
+ services.TryAddSingleton();
return services;
}
@@ -70,7 +72,7 @@ namespace EnvelopeGenerator.Application.Extensions
dispatcherConfigSection: config.GetSection("DispatcherConfig"),
mailConfigSection: config.GetSection("MailConfig"),
smsConfigSection: config.GetSection("SmsConfig"),
- codeGeneratorConfigSection: config.GetSection("CodeGeneratorConfig"),
+ codeGeneratorConfigSection: config.GetSection("CodeGeneratorParams"),
envelopeReceiverCacheParamsSection: config.GetSection("EnvelopeReceiverCacheParams"));
}
}
\ No newline at end of file
diff --git a/EnvelopeGenerator.Application/Extensions/MappingExtensions.cs b/EnvelopeGenerator.Application/Extensions/MappingExtensions.cs
index 0d7f19c8..bd996b58 100644
--- a/EnvelopeGenerator.Application/Extensions/MappingExtensions.cs
+++ b/EnvelopeGenerator.Application/Extensions/MappingExtensions.cs
@@ -7,5 +7,8 @@ namespace EnvelopeGenerator.Application.Extensions
public static bool Ok(this GtxMessagingResponse gtxMessagingResponse)
=> gtxMessagingResponse.TryGetValue("message-status", out var status)
&& status?.ToString()?.ToLower() == "ok";
+
+ public static string ToBase64String(this byte[] bytes)
+ => Convert.ToBase64String(bytes);
}
}
\ No newline at end of file
diff --git a/EnvelopeGenerator.Application/Services/CodeGenerator.cs b/EnvelopeGenerator.Application/Services/CodeGenerator.cs
index 7b3f4b3c..89d22522 100644
--- a/EnvelopeGenerator.Application/Services/CodeGenerator.cs
+++ b/EnvelopeGenerator.Application/Services/CodeGenerator.cs
@@ -1,21 +1,26 @@
using EnvelopeGenerator.Application.Configurations;
using EnvelopeGenerator.Application.Contracts;
using Microsoft.Extensions.Options;
+using OtpNet;
+using QRCoder;
using System.Text;
namespace EnvelopeGenerator.Application.Services
{
public class CodeGenerator : ICodeGenerator
{
- public static Lazy LazyStatic => new(() => new CodeGenerator(Options.Create(new())));
+ public static Lazy LazyStatic => new(() => new CodeGenerator(Options.Create(new()), new QRCodeGenerator()));
public static CodeGenerator Static => LazyStatic.Value;
- private readonly string _charPool;
+ private readonly CodeGeneratorParams _params;
- public CodeGenerator(IOptions options)
+ private readonly QRCodeGenerator _qrCodeGenerator;
+
+ public CodeGenerator(IOptions options, QRCodeGenerator qrCodeGenerator)
{
- _charPool = options.Value.CharPool;
+ _params = options.Value;
+ _qrCodeGenerator = qrCodeGenerator;
}
public string GenerateCode(int length)
@@ -29,9 +34,33 @@ namespace EnvelopeGenerator.Application.Services
var passwordBuilder = new StringBuilder(length);
for (int i = 0; i < length; i++)
- passwordBuilder.Append(_charPool[random.Next(_charPool.Length)]);
+ passwordBuilder.Append(_params.CharPool[random.Next(_params.CharPool.Length)]);
return passwordBuilder.ToString();
}
+
+ public string GenerateTotpSecretKey(int? length = null)
+ => Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(length ?? _params.DefaultTotpSecretKeyLength));
+
+ public byte[] GenerateTotpQrCode(string userEmail, string secretKey, string? issuer = null, string? totpUrlFormat = null, int? pixelsPerModule = null)
+ {
+ var url = string.Format(totpUrlFormat ?? _params.TotpUrlFormat,
+ Uri.EscapeDataString(userEmail),
+ Uri.EscapeDataString(secretKey),
+ Uri.EscapeDataString(issuer ?? _params.TotpIssuer));
+ using var qrCodeData = _qrCodeGenerator.CreateQrCode(url, QRCodeGenerator.ECCLevel.Q);
+ using var qrCode = new BitmapByteQRCode(qrCodeData);
+ return qrCode.GetGraphic(pixelsPerModule ?? _params.TotpQRPixelsPerModule);
+ }
+
+ public byte[] GenerateTotpQrCode(string userEmail, int? length = null, string? issuer = null, string? totpUrlFormat = null, int? pixelsPerModule = null)
+ {
+ return GenerateTotpQrCode(
+ userEmail: userEmail,
+ secretKey: GenerateTotpSecretKey(length: length),
+ issuer: issuer,
+ totpUrlFormat: totpUrlFormat,
+ pixelsPerModule: pixelsPerModule);
+ }
}
}
\ No newline at end of file