using System.Collections.Specialized;
using SimpleOTP.Fluent;
namespace SimpleOTP.DependencyInjection;
///
/// Provides methods for generating and validating One-Time Passwords.
///
/// The configuration for the One-Time Password service.
internal class OtpService(OtpOptions configuration) : IOtpService
{
private readonly string _issuerName = configuration.Issuer;
private readonly string? _issuerDomain = configuration.IssuerDomain;
private readonly OtpAlgorithm _algorithm = configuration.Algorithm;
private readonly OtpType _type = configuration.Type;
private readonly OtpUriFormat _format = configuration.UriFormat |
(string.IsNullOrWhiteSpace(configuration.IssuerDomain) ? 0 : OtpUriFormat.Apple);
private readonly int _digits = configuration.Digits;
private readonly int _period = configuration.Period;
private readonly NameValueCollection _customProperties = configuration.CustomProperties;
private readonly ToleranceSpan _tolerance = configuration.ToleranceSpan;
///
/// Creates an OTP URI for specified user and secret.
///
/// The username of the user.
/// The secret to use.
/// (only for HOTP) The counter to use.
/// The generated URI.
public Uri CreateUri(string username, OtpSecret secret, long counter = 0)
{
OtpConfig config = new(username)
{
Algorithm = _algorithm,
Type = _type,
Issuer = _issuerName,
Digits = _digits,
Period = _period,
Secret = secret,
Counter = counter
};
if (!string.IsNullOrWhiteSpace(_issuerDomain))
config.WithAppleIssuer(_issuerName, _issuerDomain);
config.CustomProperties.Add(_customProperties);
return config.ToUri(_format);
}
///
/// Creates an OTP code for specified user and secret.
///
/// The secret to use.
/// (only for HOTP) The counter to use.
/// The generated code.
/// The service was not configured properly. Check the "Authenticator:Type" configuration.
public OtpCode GenerateCode(OtpSecret secret, long counter = 0)
{
using OtpSecret secretClone = OtpSecret.CreateCopy(secret);
Otp generator = _type switch
{
OtpType.Hotp => new Hotp(secret, counter, _algorithm, _digits),
OtpType.Totp => new Totp(secret, _period, _algorithm, _digits),
_ => throw new NotSupportedException("The service was not configured properly. Check the \"Authenticator:Type\" configuration.")
};
return generator.Generate();
}
///
/// Validates an OTP code for specified user and secret.
///
/// The code to validate.
/// The secret to use.
/// The resync value. Shows how much the code is ahead or behind the current counter value.
/// (only for HOTP) The counter to use.
/// true if the code is valid; otherwise, false.
/// The service was not configured properly. Check the "Authenticator:Type" configuration.
public bool ValidateCode(OtpCode code, OtpSecret secret, out int resyncValue, long counter = 0)
{
using OtpSecret secretClone = OtpSecret.CreateCopy(secret);
Otp generator = _type switch
{
OtpType.Hotp => new Hotp(secret, counter, _algorithm, _digits),
OtpType.Totp => new Totp(secret, _period, _algorithm, _digits),
_ => throw new NotSupportedException("The service was not configured properly. Check the \"Authenticator:Type\" configuration.")
};
return generator.Validate(code, _tolerance, out resyncValue);
}
}