1
0
mirror of https://github.com/XFox111/SimpleOTP.git synced 2026-07-02 19:52:42 +03:00

5 Commits

Author SHA1 Message Date
xfox111 66eab0a589 Version 1.2.0 (#16)
* Expanded support to .NET Standard 2.1 and .NET Core 3.1
* Fixed documentation typos #14 (#15)

Co-authored-by: Akshay Kumar <72260914+AXE02@users.noreply.github.com>
2021-06-06 12:36:49 +03:00
xfox111 9b2376c326 Update azure-pipelines.yml for Azure Pipelines 2021-05-30 18:22:25 +03:00
xfox111 f866fb2c80 Updated validation methods signatures (#13)
Improved code coverage results
2021-05-30 18:12:45 +03:00
xfox111 f669201554 Updated CI filter, added deployment status badge 2021-05-30 14:06:15 +03:00
xfox111 435b25ffd2 Changelog moved and pipeline updated (#12) 2021-05-30 13:33:08 +03:00
9 changed files with 52 additions and 42 deletions
+6 -3
View File
@@ -27,7 +27,8 @@ See more documentation at [project's wiki](https://github.com/xfox111/SimpleOTP/
```csharp
string sample_config_uri = "otpauth://totp/FoxDev%20Studio:eugene@xfox111.net?secret=ESQVTYRM2CWZC3NX24GRRWIAUUWVHWQH&issuer=FoxDev%20Studio";
OTPConfiguration config = OTPConfiguration.GetConfiguration(sample_config_uri);
// OTPModel { Id = af2358b0-3f69-4dd7-9537-32c07d6663aa, Type = TOTP, IssuerLabel = FoxDev Studio, AccountName = eugene@xfox111.net, Secret = ESQVTYRM2CWZC3NX24GRRWIAUUWVHWQH, Issuer = FoxDev Studio, Algorithm = SHA1, Digits = 6, Counter = 0, Period = 00:00:30 }
// OTPConfiguration { Id = af2358b0-3f69-4dd7-9537-32c07d6663aa, Type = TOTP, IssuerLabel = FoxDev Studio, AccountName = eugene@xfox111.net, Secret = ESQVTYRM2CWZC3NX24GRRWIAUUWVHWQH, Issuer = FoxDev Studio, Algorithm = SHA1, Digits = 6, Counter = 0, Period = 00:00:30 }
OTPCode code = OTPService.GenerateCode(ref config);
// OTPasswordModel { Code = 350386, Expiring = 23-May-21 06:08:30 PM }
@@ -36,7 +37,7 @@ OTPCode code = OTPService.GenerateCode(ref config);
### Validate code
```csharp
int codeToValidate = 350386;
bool isValid = OTPService.ValidateCode(codeToValidate, config, TimeSpan.FromSeconds(30)); // True
bool isValid = OTPService.ValidateTotp(codeToValidate, config, TimeSpan.FromSeconds(30)); // True
```
### Generate setup config
@@ -53,7 +54,7 @@ string qrCode = config.GetQrImage(300); // data:image/png;base64,...
OTPFactory factory = new (config);
factory.CodeUpdated += (newCode) => Console.WriteLine(newCode);
// OTPasswordModel { Code = 350386, Expiring = 23-May-21 06:08:30 PM }
// OTPCode { Code = 350386, Expiring = 23-May-21 06:08:30 PM }
factory.PropertyChanged += (sender, args) =>
{
if (args.PropertyName == nameof(factory.TimeLeft))
@@ -74,6 +75,8 @@ factory.Dispose();
## CI/DC status
[![Build Status](https://dev.azure.com/xfox111/GitHub%20CI/_apis/build/status/XFox111.SimpleOTP?branchName=master)](https://dev.azure.com/xfox111/GitHub%20CI/_build/latest?definitionId=13)
![Deployment status](https://vsrm.dev.azure.com/xfox111/_apis/public/Release/badge/e42c572c-a3cd-4aac-bbb1-f720d9ccb5ea/3/15)
![Azure DevOps tests](https://img.shields.io/azure-devops/tests/xfox111/GitHub%2520CI/13?label=Tests)
![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/xfox111/GitHub%2520CI/13?label=Code+coverage)
@@ -26,7 +26,8 @@ namespace SimpleOTP.Test.Models
public void TestShortLinkGenerator()
{
OTPConfiguration config = OTPConfiguration.GenerateConfiguration("FoxDev Studio", "eugene@xfox111.net");
System.Diagnostics.Debug.WriteLine(config.Id);
var testId = config.Id;
System.Diagnostics.Debug.WriteLine(testId);
Uri uri = config.GetUri();
Assert.AreEqual($"otpauth://totp/FoxDev+Studio:eugene@xfox111.net?secret={config.Secret}&issuer=FoxDev+Studio", uri.AbsoluteUri);
}
+2 -1
View File
@@ -29,7 +29,8 @@ namespace SimpleOTP.Test
OTPConfiguration config = OTPConfiguration.GetConfiguration("ESQVTYRM2CWZC3NX24GRRWIAUUWVHWQH", "FoxDev Studio", "eugene@xfox111.net");
config.Period = TimeSpan.FromSeconds(3);
using OTPFactory factory = new (config, 1500);
System.Diagnostics.Debug.WriteLine(factory.Configuration);
var testGetConfig = factory.Configuration;
System.Diagnostics.Debug.WriteLine(testGetConfig);
var code = factory.CurrentCode;
factory.Configuration = config;
+15 -11
View File
@@ -66,13 +66,15 @@ namespace SimpleOTP.Test
OTPService.GenerateCode(ref totpConfig, DateTime.UtcNow.AddSeconds(15)).Code,
OTPService.GenerateCode(ref totpConfig, DateTime.UtcNow.AddSeconds(45)).Code,
};
Assert.IsFalse(OTPService.ValidateCode(codes[0], totpConfig));
Assert.IsTrue(OTPService.ValidateCode(codes[1], totpConfig));
Assert.IsTrue(OTPService.ValidateCode(codes[2], totpConfig));
Assert.IsTrue(OTPService.ValidateCode(codes[3], totpConfig));
Assert.IsFalse(OTPService.ValidateCode(codes[4], totpConfig));
Assert.IsFalse(OTPService.ValidateTotp(codes[0], totpConfig));
Assert.IsTrue(OTPService.ValidateTotp(codes[1], totpConfig));
Assert.IsTrue(OTPService.ValidateTotp(codes[2], totpConfig));
Assert.IsTrue(OTPService.ValidateTotp(codes[3], totpConfig));
Assert.IsFalse(OTPService.ValidateTotp(codes[4], totpConfig));
Assert.IsTrue(OTPService.ValidateCode(codes[0], totpConfig, TimeSpan.FromSeconds(60)));
Assert.IsTrue(OTPService.ValidateTotp(codes[0], totpConfig, TimeSpan.FromSeconds(60)));
Assert.ThrowsException<ArgumentException>(() => OTPService.ValidateTotp(0, hotpConfig));
}
/// <summary>
@@ -92,19 +94,21 @@ namespace SimpleOTP.Test
};
hotpConfig.Counter = 10002;
Assert.IsFalse(OTPService.ValidateCode(codes[0], ref hotpConfig, 1, true));
Assert.IsFalse(OTPService.ValidateHotp(codes[0], ref hotpConfig, 1, true));
Assert.AreEqual(10002, hotpConfig.Counter);
Assert.IsTrue(OTPService.ValidateCode(codes[1], ref hotpConfig, 1, true));
Assert.IsTrue(OTPService.ValidateHotp(codes[1], ref hotpConfig, 1, true));
Assert.AreEqual(10001, hotpConfig.Counter);
hotpConfig.Counter = 10002;
Assert.IsTrue(OTPService.ValidateCode(codes[2], ref hotpConfig, 1, true));
Assert.IsTrue(OTPService.ValidateHotp(codes[2], ref hotpConfig, 1, true));
Assert.AreEqual(10002, hotpConfig.Counter);
hotpConfig.Counter = 10002;
Assert.IsTrue(OTPService.ValidateCode(codes[3], ref hotpConfig, 1, true));
Assert.IsTrue(OTPService.ValidateHotp(codes[3], ref hotpConfig, 1, true));
Assert.AreEqual(10003, hotpConfig.Counter);
hotpConfig.Counter = 10002;
Assert.IsFalse(OTPService.ValidateCode(codes[4], ref hotpConfig, 1, true));
Assert.IsFalse(OTPService.ValidateHotp(codes[4], ref hotpConfig, 1, true));
Assert.AreEqual(10002, hotpConfig.Counter);
Assert.ThrowsException<ArgumentException>(() => OTPService.ValidateHotp(0, ref totpConfig, 1, true));
}
}
}
+2 -2
View File
@@ -33,7 +33,7 @@ namespace SimpleOTP.Models
/// <summary>
/// Initializes a new instance of the <see cref="OTPCode"/> class.<br/>
/// Use this contructor only for HOTP key. Otherwise, fill out all properties.
/// Use this constructor only for HOTP key. Otherwise, fill out all properties.
/// </summary>
/// <param name="code">OTP code.</param>
public OTPCode(int code) =>
@@ -48,4 +48,4 @@ namespace SimpleOTP.Models
public string GetCode(string formatter = "000000") =>
Code.ToString(formatter);
}
}
}
+3 -3
View File
@@ -26,7 +26,7 @@ namespace SimpleOTP.Models
public Guid Id { get; set; } = Guid.NewGuid();
/// <summary>
/// Gets or sets oTP algorithm type.
/// Gets or sets OTP algorithm type.
/// </summary>
public OTPType Type { get; set; } = OTPType.TOTP;
@@ -217,7 +217,7 @@ namespace SimpleOTP.Models
HttpResponseMessage response = client.GetAsync($"https://chart.googleapis.com/chart?cht=qr&chs={qrCodeSize}x{qrCodeSize}&chl={HttpUtility.UrlEncode(GetUri().AbsoluteUri)}").Result;
if (!response.IsSuccessStatusCode)
throw new HttpRequestException("Response status code indicates that request has failed", null, response.StatusCode);
throw new HttpRequestException($"Response status code indicates that request has failed (Response code: {response.StatusCode})");
byte[] imageBytes = await response.Content.ReadAsByteArrayAsync();
string imageString = @$"data:image/png;base64,{Convert.ToBase64String(imageBytes)}";
@@ -225,4 +225,4 @@ namespace SimpleOTP.Models
return imageString;
}
}
}
}
+16 -2
View File
@@ -96,8 +96,10 @@ namespace SimpleOTP
/// <param name="toleranceSpan">Counter span from which OTP codes remain valid.</param>
/// <param name="resyncCounter">Defines whether method should resync <see cref="OTPConfiguration.Counter"/> of the <paramref name="target"/> or not after successful validation.</param>
/// <returns><c>True</c> if code is valid, <c>False</c> if it isn't.</returns>
public static bool ValidateCode(int otp, ref OTPConfiguration target, int toleranceSpan, bool resyncCounter)
public static bool ValidateHotp(int otp, ref OTPConfiguration target, int toleranceSpan, bool resyncCounter)
{
if (target?.Type != OTPType.HOTP)
throw new ArgumentException("Invalid configuration. This method only validates HOTP codes. For TOTP codes use OTPService.ValidateTotp()");
long currentCounter = target.Counter;
List<(int Code, long Counter)> codes = new ();
for (long i = currentCounter - toleranceSpan; i <= currentCounter + toleranceSpan; i++)
@@ -123,8 +125,10 @@ namespace SimpleOTP
/// <param name="toleranceTime">Time span from which OTP codes remain valid.<br/>
/// Default: 15 seconds.</param>
/// <returns><c>True</c> if code is valid, <c>False</c> if it isn't.</returns>
public static bool ValidateCode(int otp, OTPConfiguration target, TimeSpan? toleranceTime = null)
public static bool ValidateTotp(int otp, OTPConfiguration target, TimeSpan? toleranceTime = null)
{
if (target?.Type != OTPType.TOTP)
throw new ArgumentException("Invalid configuration. This method only validates TOTP codes. For HOTP codes use OTPService.ValidateHotp()");
toleranceTime ??= TimeSpan.FromSeconds(15);
DateTime now = DateTime.UtcNow;
List<int> codes = new ();
@@ -134,6 +138,16 @@ namespace SimpleOTP
return codes.Any(i => i == otp);
}
/// <inheritdoc cref="ValidateHotp(int, ref OTPConfiguration, int, bool)"/>
[Obsolete("Use ValidateHotp() instead.")]
public static bool ValidateCode(int otp, ref OTPConfiguration target, int toleranceSpan, bool resyncCounter) =>
ValidateHotp(otp, ref target, toleranceSpan, resyncCounter);
/// <inheritdoc cref="ValidateTotp(int, OTPConfiguration, TimeSpan?)"/>
[Obsolete("Use ValidateTotp() instead.")]
public static bool ValidateCode(int otp, OTPConfiguration target, TimeSpan? toleranceTime = null) =>
ValidateTotp(otp, target, toleranceTime);
private static long GetCurrentCounter(DateTime date, int period) =>
(long)(date - DateTime.UnixEpoch).TotalSeconds / period;
}
+5 -13
View File
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFrameworks>net5.0;netstandard2.1;netcoreapp3.1</TargetFrameworks>
<LangVersion>9</LangVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
@@ -11,7 +12,7 @@
<PropertyGroup>
<PackageId>SimpleOTP</PackageId>
<AssemblyName>SimpleOTP</AssemblyName>
<Version>1.0.0</Version>
<Version>1.2.0</Version>
<Description>.NET library for TOTP/HOTP implementation on server (ASP.NET) or client (Xamarin) side</Description>
<Authors>Eugene Fox</Authors>
<Company>FoxDev Studio</Company>
@@ -21,17 +22,8 @@
<RepositoryUrl>https://github.com/XFox111/SimpleOTP</RepositoryUrl>
<NeutralLanguage>en-US</NeutralLanguage>
<PackageTags>otp;totp;dotnet;hotp;authenticator;2fa;mfa;security;oath</PackageTags>
<PackageReleaseNotes>.NET library for TOTP/HOTP implementation on server (ASP.NET) or client (Xamarin) side
Features
- Generate and validate OTP codes
- Support of TOTP (RFC 6238) and HOTP (RFC 4226) algorithms
- Support of HMAC-SHA1, HMAC-SHA256 and HMAC-SHA512 hashing algorithms
- Setup URI parser
- Database-ready configuration models
- Configuration generator for server-side implementation
- QR code generator
- No dependencies</PackageReleaseNotes>
<PackageReleaseNotes>- Expanded support to .NET Standard 2.1 and .NET Core 3.1
- Fixed some documentation typos (https://github.com/XFox111/SimpleOTP/issues/14)</PackageReleaseNotes>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+1 -6
View File
@@ -1,10 +1,5 @@
trigger:
branches:
include:
- master
paths:
include:
- SimpleOTP/**
- master
pool:
vmImage: 'windows-latest'