-
-
Notifications
You must be signed in to change notification settings - Fork 7
feat(Authenticator): add ITotpService project #417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
e2a9b39
0c9eb9a
54cf24f
93f75b0
bd9496c
945597b
6390be7
0faa48a
cdcf295
fd63868
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -101,3 +101,4 @@ vimeo | |
| scrlang | ||
| Validata | ||
| Validatable | ||
| Totp | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk.Razor"> | ||
|
|
||
| <PropertyGroup> | ||
| <Version>9.0.0</Version> | ||
| </PropertyGroup> | ||
|
|
||
| <PropertyGroup> | ||
| <PackageTags>Bootstrap Blazor WebAssembly wasm Authenticator 2FA MFA OTP TOTP HOTP</PackageTags> | ||
| <Description>Bootstrap UI components extensions of Authenticator</Description> | ||
| <RootNamespace>BootstrapBlazor.Components</RootNamespace> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="BootstrapBlazor" Version="9.5.12" /> | ||
| <PackageReference Include="Otp.NET" Version="1.4.0" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\..\..\BootstrapBlazor\src\BootstrapBlazor\BootstrapBlazor.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // Copyright (c) Argo Zhang (argo@163.com). All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
| // Website: https://www.blazor.zone or https://argozhang.github.io/ | ||
|
|
||
| namespace BootstrapBlazor.Components; | ||
|
|
||
| internal static class OtpExtensions | ||
| { | ||
| public static OtpNet.OtpHashMode ToMode(this OtpHashMode mode) => mode switch | ||
| { | ||
| OtpHashMode.Sha256 => OtpNet.OtpHashMode.Sha256, | ||
| OtpHashMode.Sha512 => OtpNet.OtpHashMode.Sha512, | ||
| _ => OtpNet.OtpHashMode.Sha1 | ||
| }; | ||
|
|
||
| public static OtpNet.OtpType ToType(this OtpType type) => type switch | ||
| { | ||
| OtpType.Hotp => OtpNet.OtpType.Hotp, | ||
| _ => OtpNet.OtpType.Totp | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| // Copyright (c) Argo Zhang (argo@163.com). All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
| // Website: https://www.blazor.zone or https://argozhang.github.io/ | ||
|
|
||
| using Microsoft.Extensions.DependencyInjection; | ||
|
|
||
| namespace BootstrapBlazor.Components; | ||
|
|
||
| /// <summary> | ||
| /// BootstrapBlazor service extensions | ||
| /// </summary> | ||
| public static class ServiceCollectionExtension | ||
| { | ||
| /// <summary> | ||
| /// Inject <see cref="ITotpService"/> service extension method. | ||
| /// </summary> | ||
| /// <param name="services"></param> | ||
| /// <param name="configOptions"></param> | ||
| /// <returns></returns> | ||
| public static IServiceCollection AddBootstrapBlazorTotpService(this IServiceCollection services, Action< | ||
| OtpOptions>? configOptions = null) | ||
| { | ||
| services.AddSingleton<ITotpService, DefaultTotpService>(); | ||
| services.Configure<OtpOptions>(options => | ||
| { | ||
| configOptions?.Invoke(options); | ||
|
|
||
| options.AccountName ??= "BootstrapBlazor"; | ||
| options.UserName ??= "Simulator"; | ||
| options.IssuerName ??= options.AccountName; | ||
| options.SecretKey ??= "OMM2LVLFX6QJHMYI"; | ||
| }); | ||
| return services; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| // Copyright (c) Argo Zhang (argo@163.com). All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
| // Website: https://www.blazor.zone or https://argozhang.github.io/ | ||
|
|
||
| using Microsoft.Extensions.Options; | ||
| using OtpNet; | ||
|
|
||
| namespace BootstrapBlazor.Components; | ||
|
|
||
| class DefaultTotpService(IOptionsMonitor<OtpOptions> optionsMonitor) : ITotpService | ||
| { | ||
| [NotNull] | ||
| public TotpInstanceBase? Instance { get; private set; } | ||
|
|
||
| public string GenerateOtpUri(OtpOptions? options = null) | ||
| { | ||
| options ??= optionsMonitor.CurrentValue; | ||
| var type = options.Type.ToType(); | ||
| var mode = options.Algorithm.ToMode(); | ||
| var uri = new OtpUri(type, options.SecretKey, options.UserName, options.IssuerName, mode, options.Digits, options.Period, options.Counter); | ||
| return uri.ToString(); | ||
| } | ||
|
|
||
| public string Compute(string secretKey, int period = 30, OtpHashMode mode = OtpHashMode.Sha1, int digits = 6, DateTime? timestamp = null) | ||
| { | ||
| var instance = new Totp(Base32Encoding.ToBytes(secretKey), period, mode.ToMode(), digits, timeCorrection: null); | ||
| Instance = new DefaultTotpInstance(instance); | ||
| return timestamp == null ? instance.ComputeTotp() : instance.ComputeTotp(timestamp.Value); | ||
| } | ||
|
|
||
| public int GetRemainingSeconds(DateTime? timestamp = null) | ||
| { | ||
| if (Instance != null) | ||
| { | ||
| return timestamp == null ? Instance.GetRemainingSeconds() : Instance.GetRemainingSeconds(timestamp.Value); | ||
| } | ||
| var instance = new Totp(Base32Encoding.ToBytes("OMM2LVLFX6QJHMYI")); | ||
| return timestamp == null ? instance.RemainingSeconds() : instance.RemainingSeconds(timestamp.Value); | ||
| } | ||
|
|
||
| public string GenerateSecretKey(int length = 20) | ||
| { | ||
| var secretKey = KeyGeneration.GenerateRandomKey(length); | ||
| return Base32Encoding.ToString(secretKey); | ||
| } | ||
|
|
||
| public byte[] GetSecretKeyBytes(string input) | ||
| { | ||
| return Base32Encoding.ToBytes(input); | ||
| } | ||
|
|
||
| public bool Verify(string code, DateTime? timestamp = null) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Consistent use of configured secret key in Verify method. Similar to GetRemainingSeconds, if Instance is null, Verify creates a Totp using a hard-coded secret. It might be beneficial to ensure that verification uses the same configuration from OtpOptions to avoid unexpected results. Suggested implementation: var instance = new Totp(GetSecretKeyBytes(OtpOptions.Secret));
return timestamp == null ? instance.RemainingSeconds() : instance.RemainingSeconds(timestamp.Value);Ensure that the DefaultTotpService class has the OtpOptions (or similarly named options object) properly injected and that the property containing the secret key is named "Secret". If your property name is different, please update the field accordingly. |
||
| { | ||
| if (Instance != null) | ||
| { | ||
| return timestamp == null ? Instance.Verify(code) : Instance.Verify(code, timestamp.Value); | ||
| } | ||
| var instance = new Totp(Base32Encoding.ToBytes("OMM2LVLFX6QJHMYI")); | ||
| return timestamp == null ? instance.VerifyTotp(code, out _) : instance.VerifyTotp(timestamp.Value, code, out _); | ||
| } | ||
| } | ||
|
|
||
| class DefaultTotpInstance(Totp instance) : TotpInstanceBase | ||
| { | ||
| public override int GetRemainingSeconds(DateTime? timestamp = null) | ||
| { | ||
| return timestamp == null ? instance.RemainingSeconds() : instance.RemainingSeconds(timestamp.Value); | ||
| } | ||
|
|
||
| public override bool Verify(string code, DateTime? timestamp = null) | ||
| { | ||
| return timestamp == null ? instance.VerifyTotp(code, out _) : instance.VerifyTotp(timestamp.Value, code, out _); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question (bug_risk): Consider thread-safety concerns with the mutable Instance property.
The Instance property is updated in the Compute method and then used in GetRemainingSeconds and Verify. In concurrent scenarios, this mutable field could lead to race conditions or unexpected behavior. It might be worthwhile to review thread-safety or consider a stateless approach.