mirror of
https://github.com/XFox111/SimpleOTP.git
synced 2026-04-22 08:00:45 +03:00
Major 2.0 (#20)
* New 2.0 version + DependencyInjection library * Updated docs and repo settings (devcontainers, vscode, github, etc.) * Added tests * Fixed bugs * Minor test project refactoring * Updated projects - Added symbol packages - Updated package versions to pre-release * Updated SECURITY.md * Added GitHub Actions workflows
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("SimpleOTP.Tests")]
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace SimpleOTP.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods for generating and validating One-Time Passwords.
|
||||
/// </summary>
|
||||
public interface IOtpService
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an OTP URI for specified user and secret.
|
||||
/// </summary>
|
||||
/// <param name="username">The username of the user.</param>
|
||||
/// <param name="secret">The secret to use.</param>
|
||||
/// <param name="counter">(only for HOTP) The counter to use.</param>
|
||||
/// <returns>The generated URI.</returns>
|
||||
public Uri CreateUri(string username, OtpSecret secret, long counter = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an OTP code for specified user and secret.
|
||||
/// </summary>
|
||||
/// <param name="secret">The secret to use.</param>
|
||||
/// <param name="counter">(only for HOTP) The counter to use.</param>
|
||||
public OtpCode GenerateCode(OtpSecret secret, long counter = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Validates an OTP code for specified user and secret.
|
||||
/// </summary>
|
||||
/// <param name="code">The code to validate.</param>
|
||||
/// <param name="secret">The secret to use.</param>
|
||||
/// <param name="resyncValue">The resync value. Shows how much the code is ahead or behind the current counter value.</param>
|
||||
/// <param name="counter">(only for HOTP) The counter to use.</param>
|
||||
/// <returns><c>true</c> if the code is valid; otherwise, <c>false</c>.</returns>
|
||||
public bool ValidateCode(OtpCode code, OtpSecret secret, out int resyncValue, long counter = 0);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace SimpleOTP.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Provides options for the One-Time Password service.
|
||||
/// </summary>
|
||||
public class OtpOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the issuer.
|
||||
/// </summary>
|
||||
public required string Issuer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The issuer domain.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <b>IMPORTANT:</b> Using this property will imply adherence to the Apple specification.
|
||||
/// </remarks>
|
||||
public string? IssuerDomain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The algorithm to use.
|
||||
/// </summary>
|
||||
public OtpAlgorithm Algorithm { get; set; } = OtpAlgorithm.SHA1;
|
||||
|
||||
/// <summary>
|
||||
/// The number of digits in the OTP code.
|
||||
/// </summary>
|
||||
public int Digits { get; set; } = 6;
|
||||
|
||||
/// <summary>
|
||||
/// The number of seconds between each OTP code.
|
||||
/// </summary>
|
||||
public int Period { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// The type of One-Time Password to generate.
|
||||
/// </summary>
|
||||
public OtpType Type { get; set; } = OtpType.Totp;
|
||||
|
||||
/// <summary>
|
||||
/// The format of OTP URIs.
|
||||
/// </summary>
|
||||
public OtpUriFormat UriFormat { get; set; } = OtpUriFormat.Google | OtpUriFormat.Minimal;
|
||||
|
||||
/// <summary>
|
||||
/// The tolerance span for the OTP codes validation.
|
||||
/// </summary>
|
||||
public ToleranceSpan ToleranceSpan { get; set; } = ToleranceSpan.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Custom properties to place in OTP URIs.
|
||||
/// </summary>
|
||||
public NameValueCollection CustomProperties { get; } = [];
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System.Collections.Specialized;
|
||||
using SimpleOTP.Fluent;
|
||||
|
||||
namespace SimpleOTP.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods for generating and validating One-Time Passwords.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration for the One-Time Password service.</param>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an OTP URI for specified user and secret.
|
||||
/// </summary>
|
||||
/// <param name="username">The username of the user.</param>
|
||||
/// <param name="secret">The secret to use.</param>
|
||||
/// <param name="counter">(only for HOTP) The counter to use.</param>
|
||||
/// <returns>The generated URI.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an OTP code for specified user and secret.
|
||||
/// </summary>
|
||||
/// <param name="secret">The secret to use.</param>
|
||||
/// <param name="counter">(only for HOTP) The counter to use.</param>
|
||||
/// <returns>The generated code.</returns>
|
||||
/// <exception cref="NotSupportedException">The service was not configured properly. Check the "Authenticator:Type" configuration.</exception>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates an OTP code for specified user and secret.
|
||||
/// </summary>
|
||||
/// <param name="code">The code to validate.</param>
|
||||
/// <param name="secret">The secret to use.</param>
|
||||
/// <param name="resyncValue">The resync value. Shows how much the code is ahead or behind the current counter value.</param>
|
||||
/// <param name="counter">(only for HOTP) The counter to use.</param>
|
||||
/// <returns><c>true</c> if the code is valid; otherwise, <c>false</c>.</returns>
|
||||
/// <exception cref="NotSupportedException">The service was not configured properly. Check the "Authenticator:Type" configuration.</exception>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
namespace SimpleOTP.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for the One-Time Password service.
|
||||
/// </summary>
|
||||
public class OtpServiceConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the issuer.
|
||||
/// </summary>
|
||||
public string Issuer { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The issuer domain.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <b>IMPORTANT:</b> Using this property will imply adherence to the Apple specification.
|
||||
/// </remarks>
|
||||
public string? IssuerDomain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The algorithm to use.
|
||||
/// </summary>
|
||||
public OtpAlgorithm Algorithm { get; set; } = OtpAlgorithm.SHA1;
|
||||
|
||||
/// <summary>
|
||||
/// The number of digits in the OTP code.
|
||||
/// </summary>
|
||||
public int Digits { get; set; } = 6;
|
||||
|
||||
/// <summary>
|
||||
/// The number of seconds between each OTP code.
|
||||
/// </summary>
|
||||
public int Period { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// The type of One-Time Password to generate.
|
||||
/// </summary>
|
||||
public OtpType Type { get; set; } = OtpType.Totp;
|
||||
|
||||
/// <summary>
|
||||
/// The format of OTP URIs.
|
||||
/// </summary>
|
||||
public OtpUriFormat UriFormat { get; set; } = OtpUriFormat.Google;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use minimal URI formatting (only required, or altered properties are included), or full URI formatting.
|
||||
/// </summary>
|
||||
public bool MinimalUri { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The tolerance span for the OTP codes validation.
|
||||
/// </summary>
|
||||
public ToleranceSpanConfig ToleranceSpan { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Custom properties to place in OTP URIs.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> CustomProperties { get; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for the tolerance span.
|
||||
/// </summary>
|
||||
public class ToleranceSpanConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of periods/counter values behind the current value.
|
||||
/// </summary>
|
||||
public int Behind { get; set; } = ToleranceSpan.Default.Behind;
|
||||
|
||||
/// <summary>
|
||||
/// The number of periods/counter values ahead of the current value.
|
||||
/// </summary>
|
||||
public int Ahead { get; set; } = ToleranceSpan.Default.Ahead;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace SimpleOTP.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for the One-Time Password service.
|
||||
/// </summary>
|
||||
public static class OtpServiceExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the One-Time Password service to the service collection.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <param name="issuerName">The issuer/application/service name.</param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static IServiceCollection AddAuthenticator(this IServiceCollection services, string issuerName) =>
|
||||
AddAuthenticator(services, issuerName);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the One-Time Password service to the service collection.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <param name="issuerName">The issuer/application/service name.</param>
|
||||
/// <param name="configure">The configuration for the One-Time Password service.</param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static IServiceCollection AddAuthenticator(this IServiceCollection services, string issuerName, Action<OtpOptions>? configure = null)
|
||||
{
|
||||
OtpOptions options = new()
|
||||
{
|
||||
Issuer = issuerName
|
||||
};
|
||||
|
||||
configure?.Invoke(options);
|
||||
services.AddTransient<IOtpService, OtpService>(_ => new OtpService(options));
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the One-Time Password service to the service collection.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <param name="configuration">The configuration for the One-Time Password service.</param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static IServiceCollection AddAuthenticator(this IServiceCollection services, IConfiguration configuration) =>
|
||||
AddAuthenticator(services, configuration);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the One-Time Password service to the service collection.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <param name="configuration">The configuration for the One-Time Password service.</param>
|
||||
/// <param name="configure">The configuration for the One-Time Password service.</param>
|
||||
/// <returns>A reference to this instance after the operation has completed.</returns>
|
||||
public static IServiceCollection AddAuthenticator(this IServiceCollection services, IConfiguration configuration, Action<OtpOptions>? configure = null)
|
||||
{
|
||||
OtpOptions? options = GetOptionsFromConfiguration(configuration);
|
||||
|
||||
if (options is null)
|
||||
return services;
|
||||
|
||||
configure?.Invoke(options);
|
||||
services.AddTransient<IOtpService, OtpService>(_ => new OtpService(options));
|
||||
return services;
|
||||
}
|
||||
|
||||
private static OtpOptions? GetOptionsFromConfiguration(IConfiguration configuration)
|
||||
{
|
||||
OtpServiceConfig config = new();
|
||||
IConfigurationSection configSection = configuration.GetSection("Authenticator");
|
||||
|
||||
if (!configSection.Exists() || string.IsNullOrWhiteSpace(configuration["Authenticator:Issuer"]))
|
||||
return null;
|
||||
|
||||
configSection.Bind(config);
|
||||
|
||||
OtpOptions options = new()
|
||||
{
|
||||
Issuer = config.Issuer,
|
||||
Algorithm = config.Algorithm,
|
||||
Type = config.Type,
|
||||
Digits = config.Digits,
|
||||
Period = config.Period,
|
||||
IssuerDomain = config.IssuerDomain,
|
||||
ToleranceSpan = (config.ToleranceSpan.Behind, config.ToleranceSpan.Ahead),
|
||||
UriFormat = config.UriFormat
|
||||
};
|
||||
|
||||
options.UriFormat |= config.MinimalUri ? OtpUriFormat.Minimal : OtpUriFormat.Full;
|
||||
|
||||
foreach (KeyValuePair<string, string> pair in config.CustomProperties)
|
||||
options.CustomProperties.Add(pair.Key, pair.Value);
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>EugeneFox.SimpleOTP.DependencyInjection</PackageId>
|
||||
<Version>8.0.0.0-rc1</Version>
|
||||
<Authors>Eugene Fox</Authors>
|
||||
<Copyright>Copyright © Eugene Fox 2024</Copyright>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageIcon>icon.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/XFox111/SimpleOTP.git</RepositoryUrl>
|
||||
<PackageProjectUrl>https://github.com/XFox111/SimpleOTP</PackageProjectUrl>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageTags>
|
||||
otp;totp;hotp;authenticator;authentication;one-time;2fa;mfa;security;otpauth;services;dependency-injection;di</PackageTags>
|
||||
<Description>
|
||||
Dependency Injection implementation for SimpleOTP library. Allows to use SimpleOTP as DI
|
||||
service in your application.
|
||||
</Description>
|
||||
<PackageReleaseNotes>
|
||||
Initial release. See README.md for details.
|
||||
</PackageReleaseNotes>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\assets\icon.png">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
</None>
|
||||
<None Include="..\..\README.md">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SimpleOTP\SimpleOTP.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions"
|
||||
Version="8.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user