127 lines
4.3 KiB
C#

using EnvelopeGenerator.Application.Configurations;
using EnvelopeGenerator.Application.Contracts.Services;
using Microsoft.Extensions.Options;
using OtpNet;
using QRCoder;
using System.Text;
namespace EnvelopeGenerator.Application.Services;
/// <summary>
///
/// </summary>
public class Authenticator : IAuthenticator
{
/// <summary>
///
/// </summary>
public static Lazy<Authenticator> LazyStatic => new(() => new Authenticator(Options.Create<AuthenticatorParams>(new()), new QRCodeGenerator()));
/// <summary>
///
/// </summary>
public static Authenticator Static => LazyStatic.Value;
private readonly AuthenticatorParams _params;
private readonly QRCodeGenerator _qrCodeGenerator;
/// <summary>
///
/// </summary>
/// <param name="options"></param>
/// <param name="qrCodeGenerator"></param>
public Authenticator(IOptions<AuthenticatorParams> options, QRCodeGenerator qrCodeGenerator)
{
_params = options.Value;
_qrCodeGenerator = qrCodeGenerator;
}
/// <summary>
///
/// </summary>
/// <param name="length"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
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();
}
/// <summary>
///
/// </summary>
/// <param name="length"></param>
/// <returns></returns>
public string GenerateTotpSecretKey(int? length = null)
=> Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(length ?? _params.DefaultTotpSecretKeyLength));
/// <summary>
///
/// </summary>
/// <param name="userEmail"></param>
/// <param name="secretKey"></param>
/// <param name="issuer"></param>
/// <param name="totpUrlFormat"></param>
/// <param name="pixelsPerModule"></param>
/// <returns></returns>
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);
}
/// <summary>
///
/// </summary>
/// <param name="userEmail"></param>
/// <param name="length"></param>
/// <param name="issuer"></param>
/// <param name="totpUrlFormat"></param>
/// <param name="pixelsPerModule"></param>
/// <returns></returns>
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);
}
/// <summary>
///
/// </summary>
/// <param name="secretKey"></param>
/// <param name="step"></param>
/// <returns></returns>
public string GenerateTotp(string secretKey, int step = 30) => new Totp(Base32Encoding.ToBytes(secretKey), step).ComputeTotp();
/// <summary>
///
/// </summary>
/// <param name="totpCode"></param>
/// <param name="secretKey"></param>
/// <param name="step"></param>
/// <param name="window"></param>
/// <returns></returns>
public bool VerifyTotp(string totpCode, string secretKey, int step = 30, VerificationWindow? window = null)
=> new Totp(Base32Encoding.ToBytes(secretKey), step).VerifyTotp(totpCode, out _, window);
}