using EnvelopeGenerator.Application.Configurations; using EnvelopeGenerator.Application.Contracts.Services; using Microsoft.Extensions.Options; using OtpNet; using QRCoder; using System.Text; namespace EnvelopeGenerator.Application.Services { public class Authenticator : IAuthenticator { public static Lazy LazyStatic => new(() => new Authenticator(Options.Create(new()), new QRCodeGenerator())); public static Authenticator Static => LazyStatic.Value; private readonly AuthenticatorParams _params; private readonly QRCodeGenerator _qrCodeGenerator; public Authenticator(IOptions options, QRCodeGenerator qrCodeGenerator) { _params = options.Value; _qrCodeGenerator = qrCodeGenerator; } public string GenerateCode(int length) { //TODO: Inject Random as a singleton to support multithreading to improve performance. Random random = new(); if (length <= 0) throw new ArgumentException("Password length must be greater than 0."); var passwordBuilder = new StringBuilder(length); for (int i = 0; i < length; i++) 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); } 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); } }