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