diff --git a/.gitignore b/.gitignore index 6e993ec..3315362 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,28 @@ # Build Files JwtAuthenticationApi/bin/* JwtAuthenticationApi/obj/* -JwtAuthenticationApi.UnitTests/bin/* -JwtAuthenticationApi.UnitTests/obj/* +JwtAuthenticationApi.Services/bin/* +JwtAuthenticationApi.Services/obj/* +JwtAuthenticationApi.Common/bin/* +JwtAuthenticationApi.Common/obj/* +JwtAuthenticationApi.Controllers/bin/* +JwtAuthenticationApi.Controllers/obj/* +JwtAuthenticationApi.Security/bin/* +JwtAuthenticationApi.Security/obj/* +JwtAuthenticationApi.Infrastructure/bin/* +JwtAuthenticationApi.Infrastructure/obj/* + +# Build Files - Tests Projects +JwtAuthenticationApi.Services.Tests/bin/* +JwtAuthenticationApi.Services.Tests/obj/* +JwtAuthenticationApi.Common.Tests/bin/* +JwtAuthenticationApi.Common.Tests/obj/* +JwtAuthenticationApi.Controllers.Tests/bin/* +JwtAuthenticationApi.Controllers.Tests/obj/* +JwtAuthenticationApi.Security.Tests/bin/* +JwtAuthenticationApi.Security.Tests/obj/* +JwtAuthenticationApi.Infrastructure.Tests/bin/* +JwtAuthenticationApi.Infrastructure.Tests/obj/* # Migrations JwtAuthenticationApi/Migrations/* diff --git a/JwtAuthenticationApi.UnitTests/Factories/Polly/PollySleepingIntervalsFactoryTests.cs b/JwtAuthenticationApi.Common.Tests/Factories/Polly/PollySleepingIntervalsFactoryTests.cs similarity index 71% rename from JwtAuthenticationApi.UnitTests/Factories/Polly/PollySleepingIntervalsFactoryTests.cs rename to JwtAuthenticationApi.Common.Tests/Factories/Polly/PollySleepingIntervalsFactoryTests.cs index 2976ae1..8a76c83 100644 --- a/JwtAuthenticationApi.UnitTests/Factories/Polly/PollySleepingIntervalsFactoryTests.cs +++ b/JwtAuthenticationApi.Common.Tests/Factories/Polly/PollySleepingIntervalsFactoryTests.cs @@ -1,7 +1,8 @@ -namespace JwtAuthenticationApi.UnitTests.Factories.Polly +namespace JwtAuthenticationApi.Common.Tests.Factories.Polly { - using JwtAuthenticationApi.Factories.Polly; - + using Common.Factories.Polly; + using FluentAssertions; + using NUnit.Framework; [TestFixture, Parallelizable] public class PollySleepingIntervalsFactoryTests @@ -16,7 +17,7 @@ public void SetUp() [TestCase(1u,6u)] [TestCase(6u,9u)] - public void ShouldCreateConstantSleepingInterval(uint sleepTime, uint totalRetries) + public void CreateConstantInterval_CreatesConstantSleepingInterval(uint sleepTime, uint totalRetries) { // Act var actual = _uut.CreateConstantInterval(sleepTime, totalRetries).ToList(); @@ -32,7 +33,7 @@ public void ShouldCreateConstantSleepingInterval(uint sleepTime, uint totalRetri [TestCase(2u,1u, 6u)] [TestCase(3u,4u, 9u)] - public void ShouldCreateLinearSleepingInterval(uint baseSleepTime, uint sleepIncreaseTime, uint totalRetries) + public void CreateLinearInterval_CreateLinearSleepingInterval(uint baseSleepTime, uint sleepIncreaseTime, uint totalRetries) { // Act var actual = _uut.CreateLinearInterval(baseSleepTime,sleepIncreaseTime, totalRetries).ToList(); diff --git a/JwtAuthenticationApi.UnitTests/Factories/Wrappers/MutexWrapperFactoryTests.cs b/JwtAuthenticationApi.Common.Tests/Factories/Wrappers/MutexWrapperFactoryTests.cs similarity index 61% rename from JwtAuthenticationApi.UnitTests/Factories/Wrappers/MutexWrapperFactoryTests.cs rename to JwtAuthenticationApi.Common.Tests/Factories/Wrappers/MutexWrapperFactoryTests.cs index 7a4de8c..042553e 100644 --- a/JwtAuthenticationApi.UnitTests/Factories/Wrappers/MutexWrapperFactoryTests.cs +++ b/JwtAuthenticationApi.Common.Tests/Factories/Wrappers/MutexWrapperFactoryTests.cs @@ -1,9 +1,12 @@ -namespace JwtAuthenticationApi.UnitTests.Factories.Wrappers +namespace JwtAuthenticationApi.Common.Tests.Factories.Wrappers { - using JwtAuthenticationApi.Factories.Wrappers; - using JwtAuthenticationApi.Wrappers.Threading; + using Abstraction.Wrappers.Threading; + using Common.Factories.Wrappers; + using Common.Wrappers.Threading; + using FluentAssertions; + using NUnit.Framework; - [TestFixture, Parallelizable] + [TestFixture, Parallelizable] public class MutexWrapperFactoryTests { private MutexWrapperFactory _uut; @@ -16,7 +19,7 @@ public void SetUp() [TestCase(true, "Name")] [TestCase(false, "")] - public void ShouldCreateMutexWrapper(bool initiallyOwned, string name) + public void Create_CreatesMutexWrapper(bool initiallyOwned, string name) { // Act using IMutexWrapper actual = _uut.Create(initiallyOwned, name); diff --git a/JwtAuthenticationApi.UnitTests/Factories/Wrappers/SemaphoreWrapperFactoryTests.cs b/JwtAuthenticationApi.Common.Tests/Factories/Wrappers/SemaphoreWrapperFactoryTests.cs similarity index 57% rename from JwtAuthenticationApi.UnitTests/Factories/Wrappers/SemaphoreWrapperFactoryTests.cs rename to JwtAuthenticationApi.Common.Tests/Factories/Wrappers/SemaphoreWrapperFactoryTests.cs index f92aec8..fd3dca9 100644 --- a/JwtAuthenticationApi.UnitTests/Factories/Wrappers/SemaphoreWrapperFactoryTests.cs +++ b/JwtAuthenticationApi.Common.Tests/Factories/Wrappers/SemaphoreWrapperFactoryTests.cs @@ -1,8 +1,11 @@ -using JwtAuthenticationApi.Factories.Wrappers; -using JwtAuthenticationApi.Wrappers.Threading; - -namespace JwtAuthenticationApi.UnitTests.Factories.Wrappers +namespace JwtAuthenticationApi.Common.Tests.Factories.Wrappers { + using Abstraction.Wrappers.Threading; + using Common.Factories.Wrappers; + using Common.Wrappers.Threading; + using FluentAssertions; + using NUnit.Framework; + [TestFixture, Parallelizable] public class SemaphoreWrapperFactoryTests { @@ -17,7 +20,7 @@ public void SetUp() [TestCase(0,2,"yyy")] [TestCase(1, 9, "zzz")] - public void ShouldCreateSemaphoreWrapper(int initial, int max, string name) + public void Create_CreatesSemaphoreWrapper(int initial, int max, string name) { // Act var actual = _uut.Create(initial, max, name); @@ -25,6 +28,7 @@ public void ShouldCreateSemaphoreWrapper(int initial, int max, string name) // Assert actual.Should().BeAssignableTo(); actual.Should().BeOfType(); + actual.Dispose(); } } } \ No newline at end of file diff --git a/JwtAuthenticationApi.UnitTests/Handlers/CommandHandlerTests.cs b/JwtAuthenticationApi.Common.Tests/Handlers/CommandHandlerTests.cs similarity index 69% rename from JwtAuthenticationApi.UnitTests/Handlers/CommandHandlerTests.cs rename to JwtAuthenticationApi.Common.Tests/Handlers/CommandHandlerTests.cs index 6fb8551..330f51d 100644 --- a/JwtAuthenticationApi.UnitTests/Handlers/CommandHandlerTests.cs +++ b/JwtAuthenticationApi.Common.Tests/Handlers/CommandHandlerTests.cs @@ -1,8 +1,11 @@ -namespace JwtAuthenticationApi.UnitTests.Handlers +namespace JwtAuthenticationApi.Common.Tests.Handlers { - using JwtAuthenticationApi.Abstraction.Commands; - using JwtAuthenticationApi.Commands.Models; - using JwtAuthenticationApi.Handlers; + using Abstraction.Commands; + using Common.Handlers; + using FluentAssertions; + using Models; + using NSubstitute; + using NUnit.Framework; using TddXt.AnyRoot.Strings; using static TddXt.AnyRoot.Root; @@ -18,7 +21,7 @@ public void SetUp() } [Test] - public async Task ShouldHandleCommandExecutionAndReturnResult() + public async Task HandleAsync_HandlesCommandExecutionAndReturnResult() { // Arrange string stringResult = Any.String(); diff --git a/JwtAuthenticationApi.Common.Tests/JwtAuthenticationApi.Common.Tests.csproj b/JwtAuthenticationApi.Common.Tests/JwtAuthenticationApi.Common.Tests.csproj new file mode 100644 index 0000000..44735e7 --- /dev/null +++ b/JwtAuthenticationApi.Common.Tests/JwtAuthenticationApi.Common.Tests.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + enable + disable + + false + true + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/JwtAuthenticationApi/Abstraction/Commands/ICommand.cs b/JwtAuthenticationApi.Common/Abstraction/Commands/ICommand.cs similarity index 85% rename from JwtAuthenticationApi/Abstraction/Commands/ICommand.cs rename to JwtAuthenticationApi.Common/Abstraction/Commands/ICommand.cs index ce6c107..293e7f0 100644 --- a/JwtAuthenticationApi/Abstraction/Commands/ICommand.cs +++ b/JwtAuthenticationApi.Common/Abstraction/Commands/ICommand.cs @@ -1,7 +1,7 @@ -using JwtAuthenticationApi.Commands.Models; - -namespace JwtAuthenticationApi.Abstraction.Commands +namespace JwtAuthenticationApi.Common.Abstraction.Commands { + using Models; + /// /// Defines base method that should be implemented command classes. /// diff --git a/JwtAuthenticationApi.Common/Abstraction/Factories/IGuidFactory.cs b/JwtAuthenticationApi.Common/Abstraction/Factories/IGuidFactory.cs new file mode 100644 index 0000000..472f280 --- /dev/null +++ b/JwtAuthenticationApi.Common/Abstraction/Factories/IGuidFactory.cs @@ -0,0 +1,11 @@ +namespace JwtAuthenticationApi.Common.Abstraction.Factories +{ + /// + /// Defines method for wrapper. + /// + public interface IGuidFactory + { + /// + Guid CreateGuid(); + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi.Common/Abstraction/Factories/Polly/IPollySleepingIntervalsFactory.cs b/JwtAuthenticationApi.Common/Abstraction/Factories/Polly/IPollySleepingIntervalsFactory.cs new file mode 100644 index 0000000..3b51905 --- /dev/null +++ b/JwtAuthenticationApi.Common/Abstraction/Factories/Polly/IPollySleepingIntervalsFactory.cs @@ -0,0 +1,26 @@ +namespace JwtAuthenticationApi.Common.Abstraction.Factories.Polly +{ + + /// + /// Defines method for polly sleeping intervals factory class. + /// + public interface IPollySleepingIntervalsFactory + { + /// + /// Creates constant time polly sleep interval and returns it as collection of . + /// + /// Constant value of sleep time per retry. + /// Total amount of retries. + /// that contains sleep intervals as a + IEnumerable CreateConstantInterval(uint sleepTime, uint retryCount); + + /// + /// Creates time polly sleep interval that increases linear and returns it as collection of . + /// + /// Starting sleep time. + /// Defines increase of time interval between retries. + /// Total amount of retires. + /// that contains sleep intervals as a + IEnumerable CreateLinearInterval(uint baseSleepTime, uint sleepTimeIncrease, uint retryCount); + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi.Common/Abstraction/Factories/Wrappers/IMutexWrapperFactory.cs b/JwtAuthenticationApi.Common/Abstraction/Factories/Wrappers/IMutexWrapperFactory.cs new file mode 100644 index 0000000..ec809e0 --- /dev/null +++ b/JwtAuthenticationApi.Common/Abstraction/Factories/Wrappers/IMutexWrapperFactory.cs @@ -0,0 +1,18 @@ +namespace JwtAuthenticationApi.Common.Abstraction.Factories.Wrappers +{ + using JwtAuthenticationApi.Common.Abstraction.Wrappers.Threading; + + /// + /// Defines method for factory class. + /// + public interface IMutexWrapperFactory + { + /// + /// Creates . + /// + /// Defines if calling thread is mutex owner. + /// Mutex name. + /// New instance of with provided name. + IMutexWrapper Create(bool initiallyOwned, string name); + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi.Common/Abstraction/Factories/Wrappers/ISemaphoreWrapperFactory.cs b/JwtAuthenticationApi.Common/Abstraction/Factories/Wrappers/ISemaphoreWrapperFactory.cs new file mode 100644 index 0000000..fa86926 --- /dev/null +++ b/JwtAuthenticationApi.Common/Abstraction/Factories/Wrappers/ISemaphoreWrapperFactory.cs @@ -0,0 +1,23 @@ +namespace JwtAuthenticationApi.Common.Abstraction.Factories.Wrappers +{ + using JwtAuthenticationApi.Common.Abstraction.Wrappers.Threading; + using JwtAuthenticationApi.Common.Wrappers.Threading; + + /// + /// Defines method for factory class. + /// + public interface ISemaphoreWrapperFactory + { + /// + /// Creates and returns . + /// + /// The initial number of requests for the semaphore that can be granted concurrently + /// The maximum number of requests for the semaphore that can be granted concurrently. + /// + /// The name, if the synchronization object is to be shared with other processes; otherwise, or an empty string. The name is case-sensitive. + /// + /// New instance of . + ISemaphoreWrapper Create(int initialCount, int maximalCount, string name); + } +} + diff --git a/JwtAuthenticationApi.Common/Abstraction/Handlers/ICommandHandler.cs b/JwtAuthenticationApi.Common/Abstraction/Handlers/ICommandHandler.cs new file mode 100644 index 0000000..16a484b --- /dev/null +++ b/JwtAuthenticationApi.Common/Abstraction/Handlers/ICommandHandler.cs @@ -0,0 +1,20 @@ +namespace JwtAuthenticationApi.Common.Abstraction.Handlers +{ + using Models; + using Commands; + /// + /// Define method for handling requests in asynchronous way. + /// + public interface ICommandHandler + { + /// + /// Handles command and returns its result. + /// + /// Defines type that will be returned after handling command. + /// Command that implements interface. + /// Cancellation token. + /// + /// A task that represents the asynchronous password mix operation. The task result of handling provided command. + Task> HandleAsync(ICommand command, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi.Common/Abstraction/Wrappers/Threading/IMutexWrapper.cs b/JwtAuthenticationApi.Common/Abstraction/Wrappers/Threading/IMutexWrapper.cs new file mode 100644 index 0000000..8e742b2 --- /dev/null +++ b/JwtAuthenticationApi.Common/Abstraction/Wrappers/Threading/IMutexWrapper.cs @@ -0,0 +1,13 @@ +namespace JwtAuthenticationApi.Common.Abstraction.Wrappers.Threading +{ + /// + /// Defines method for wrapping class. + /// + public interface IMutexWrapper : IDisposable + { + /// + void WaitOne(); + /// + void ReleaseMutex(); + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi.Common/Abstraction/Wrappers/Threading/ISemaphoreWrapper.cs b/JwtAuthenticationApi.Common/Abstraction/Wrappers/Threading/ISemaphoreWrapper.cs new file mode 100644 index 0000000..400d0a9 --- /dev/null +++ b/JwtAuthenticationApi.Common/Abstraction/Wrappers/Threading/ISemaphoreWrapper.cs @@ -0,0 +1,17 @@ +namespace JwtAuthenticationApi.Common.Abstraction.Wrappers.Threading; + +/// +/// Defines methods for wrapper. +/// +public interface ISemaphoreWrapper : IDisposable +{ + /// + /// Blocks current thread until the current receives a signal. + /// + /// + /// if the current instance receives a signal. If the current instance is never signaled, WaitOne() never returns. + /// + bool WaitOne(); + /// + int Release(); +} \ No newline at end of file diff --git a/JwtAuthenticationApi.Common/CommonInstaller.cs b/JwtAuthenticationApi.Common/CommonInstaller.cs new file mode 100644 index 0000000..4986a76 --- /dev/null +++ b/JwtAuthenticationApi.Common/CommonInstaller.cs @@ -0,0 +1,27 @@ +namespace JwtAuthenticationApi.Common +{ + using Abstraction.Factories.Polly; + using Abstraction.Factories.Wrappers; + using Abstraction.Handlers; + using Abstraction.Wrappers; + using Factories.Polly; + using Factories.Wrappers; + using Handlers; + using JwtAuthenticationApi.Common.Abstraction.Factories; + using JwtAuthenticationApi.Common.Factories; + using Microsoft.Extensions.DependencyInjection; + using Wrappers; + + public static class CommonInstaller + { + public static void InstallCommon(this IServiceCollection serviceCollection) + { + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + } + + } +} diff --git a/JwtAuthenticationApi/Constants/JaaConstants.cs b/JwtAuthenticationApi.Common/Constants/JaaConstants.cs similarity index 95% rename from JwtAuthenticationApi/Constants/JaaConstants.cs rename to JwtAuthenticationApi.Common/Constants/JaaConstants.cs index 4d751c5..370986f 100644 --- a/JwtAuthenticationApi/Constants/JaaConstants.cs +++ b/JwtAuthenticationApi.Common/Constants/JaaConstants.cs @@ -1,4 +1,4 @@ -namespace JwtAuthenticationApi.Constants +namespace JwtAuthenticationApi.Common.Constants { using System.Diagnostics.CodeAnalysis; diff --git a/JwtAuthenticationApi/Exceptions/CommandExecutionException.cs b/JwtAuthenticationApi.Common/Exceptions/CommandExecutionException.cs similarity index 94% rename from JwtAuthenticationApi/Exceptions/CommandExecutionException.cs rename to JwtAuthenticationApi.Common/Exceptions/CommandExecutionException.cs index 579ed64..0eb9749 100644 --- a/JwtAuthenticationApi/Exceptions/CommandExecutionException.cs +++ b/JwtAuthenticationApi.Common/Exceptions/CommandExecutionException.cs @@ -1,4 +1,4 @@ -namespace JwtAuthenticationApi.Exceptions +namespace JwtAuthenticationApi.Common.Exceptions { using System.Diagnostics.CodeAnalysis; diff --git a/JwtAuthenticationApi.Common/Factories/GuidFactory.cs b/JwtAuthenticationApi.Common/Factories/GuidFactory.cs new file mode 100644 index 0000000..4984f81 --- /dev/null +++ b/JwtAuthenticationApi.Common/Factories/GuidFactory.cs @@ -0,0 +1,16 @@ +namespace JwtAuthenticationApi.Common.Factories +{ + using JwtAuthenticationApi.Common.Abstraction.Factories; + + /// + /// wrapper. + /// + internal class GuidFactory : IGuidFactory + { + /// + public Guid CreateGuid() + { + return Guid.NewGuid(); + } + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi/Factories/Polly/PollySleepingIntervalsFactory.cs b/JwtAuthenticationApi.Common/Factories/Polly/PollySleepingIntervalsFactory.cs similarity index 83% rename from JwtAuthenticationApi/Factories/Polly/PollySleepingIntervalsFactory.cs rename to JwtAuthenticationApi.Common/Factories/Polly/PollySleepingIntervalsFactory.cs index 3e79129..cd0c412 100644 --- a/JwtAuthenticationApi/Factories/Polly/PollySleepingIntervalsFactory.cs +++ b/JwtAuthenticationApi.Common/Factories/Polly/PollySleepingIntervalsFactory.cs @@ -1,10 +1,11 @@ -namespace JwtAuthenticationApi.Factories.Polly +namespace JwtAuthenticationApi.Common.Factories.Polly { + using JwtAuthenticationApi.Common.Abstraction.Factories.Polly; /// /// Polly sleeping intervals factory that creates intervals for polly. Implementation of interface. /// - public class PollySleepingIntervalsFactory : IPollySleepingIntervalsFactory + internal class PollySleepingIntervalsFactory : IPollySleepingIntervalsFactory { public IEnumerable CreateConstantInterval(uint sleepTime, uint retryCount) { diff --git a/JwtAuthenticationApi/Factories/Wrappers/MutexWrapperFactory.cs b/JwtAuthenticationApi.Common/Factories/Wrappers/MutexWrapperFactory.cs similarity index 50% rename from JwtAuthenticationApi/Factories/Wrappers/MutexWrapperFactory.cs rename to JwtAuthenticationApi.Common/Factories/Wrappers/MutexWrapperFactory.cs index d97900f..e19bcf2 100644 --- a/JwtAuthenticationApi/Factories/Wrappers/MutexWrapperFactory.cs +++ b/JwtAuthenticationApi.Common/Factories/Wrappers/MutexWrapperFactory.cs @@ -1,11 +1,14 @@ -using JwtAuthenticationApi.Wrappers.Threading; - -namespace JwtAuthenticationApi.Factories.Wrappers +namespace JwtAuthenticationApi.Common.Factories.Wrappers { + using JwtAuthenticationApi.Common.Abstraction.Factories.Wrappers; + using JwtAuthenticationApi.Common.Abstraction.Wrappers.Threading; + using JwtAuthenticationApi.Common.Wrappers.Threading; + + /// /// Implementation of interface. Factory class for . /// - public sealed class MutexWrapperFactory : IMutexWrapperFactory + internal sealed class MutexWrapperFactory : IMutexWrapperFactory { /// public IMutexWrapper Create(bool initiallyOwned, string name) diff --git a/JwtAuthenticationApi.Common/Factories/Wrappers/SemaphoreWrapperFactory.cs b/JwtAuthenticationApi.Common/Factories/Wrappers/SemaphoreWrapperFactory.cs new file mode 100644 index 0000000..a4635ce --- /dev/null +++ b/JwtAuthenticationApi.Common/Factories/Wrappers/SemaphoreWrapperFactory.cs @@ -0,0 +1,17 @@ +namespace JwtAuthenticationApi.Common.Factories.Wrappers +{ + using JwtAuthenticationApi.Common.Abstraction.Factories.Wrappers; + using JwtAuthenticationApi.Common.Abstraction.Wrappers.Threading; + using JwtAuthenticationApi.Common.Wrappers.Threading; + + /// + /// Implementation of interface. Creates and returns . + /// + internal sealed class SemaphoreWrapperFactory: ISemaphoreWrapperFactory + { + public ISemaphoreWrapper Create(int initialCount, int maximalCount, string name) + { + return new SemaphoreWrapper(initialCount, maximalCount, name); + } + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi/Handlers/CommandHandler.cs b/JwtAuthenticationApi.Common/Handlers/CommandHandler.cs similarity index 67% rename from JwtAuthenticationApi/Handlers/CommandHandler.cs rename to JwtAuthenticationApi.Common/Handlers/CommandHandler.cs index 95381f8..342fc41 100644 --- a/JwtAuthenticationApi/Handlers/CommandHandler.cs +++ b/JwtAuthenticationApi.Common/Handlers/CommandHandler.cs @@ -1,12 +1,13 @@ -namespace JwtAuthenticationApi.Handlers +namespace JwtAuthenticationApi.Common.Handlers { - using Abstraction.Commands; - using Commands.Models; + using JwtAuthenticationApi.Common.Abstraction.Handlers; + using Abstraction.Commands; + using Models; /// /// Implementation of . Provides environment that will handle commands in asynchronous way. /// - public sealed class CommandHandler : ICommandHandler + internal sealed class CommandHandler : ICommandHandler { /// public async Task> HandleAsync(ICommand command, CancellationToken cancellationToken = new CancellationToken()) diff --git a/JwtAuthenticationApi.Common/JwtAuthenticationApi.Common.csproj b/JwtAuthenticationApi.Common/JwtAuthenticationApi.Common.csproj new file mode 100644 index 0000000..3fa5e3f --- /dev/null +++ b/JwtAuthenticationApi.Common/JwtAuthenticationApi.Common.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + disable + + + + + + + + + <_Parameter1>JwtAuthenticationApi.Common.Tests + + + diff --git a/JwtAuthenticationApi.Common/Models/Result.cs b/JwtAuthenticationApi.Common/Models/Result.cs new file mode 100644 index 0000000..2ae93b4 --- /dev/null +++ b/JwtAuthenticationApi.Common/Models/Result.cs @@ -0,0 +1,33 @@ +namespace JwtAuthenticationApi.Common.Models +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// Represents a result of command execution. + /// + /// Result type. + [ExcludeFromCodeCoverage] + public sealed class Result + { + /// + /// Result value. + /// + public TResult Value { get; init; } + + /// + /// Gets whether command execution was successful. + /// + public bool IsSuccessful { get; init; } + + /// + /// Initializes new instance of + /// + /// Command execution result. + /// Specifies if command execution was successful. + public Result(TResult value, bool isSuccessful) + { + Value = value; + IsSuccessful = isSuccessful; + } + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi/Models/Options/PasswordPepper.cs b/JwtAuthenticationApi.Common/Options/PasswordPepper.cs similarity index 77% rename from JwtAuthenticationApi/Models/Options/PasswordPepper.cs rename to JwtAuthenticationApi.Common/Options/PasswordPepper.cs index 5fbefa7..b02e0f9 100644 --- a/JwtAuthenticationApi/Models/Options/PasswordPepper.cs +++ b/JwtAuthenticationApi.Common/Options/PasswordPepper.cs @@ -1,4 +1,4 @@ -namespace JwtAuthenticationApi.Models.Options +namespace JwtAuthenticationApi.Common.Options { using System.Diagnostics.CodeAnalysis; @@ -13,5 +13,7 @@ public sealed class PasswordPepper /// /// value of password pepper. public string Pepper { get; init; } + + public const string Position = "PasswordPepper"; } } diff --git a/JwtAuthenticationApi/Wrappers/Threading/MutexWrapper.cs b/JwtAuthenticationApi.Common/Wrappers/Threading/MutexWrapper.cs similarity index 60% rename from JwtAuthenticationApi/Wrappers/Threading/MutexWrapper.cs rename to JwtAuthenticationApi.Common/Wrappers/Threading/MutexWrapper.cs index 580c871..0656d5b 100644 --- a/JwtAuthenticationApi/Wrappers/Threading/MutexWrapper.cs +++ b/JwtAuthenticationApi.Common/Wrappers/Threading/MutexWrapper.cs @@ -1,12 +1,13 @@ -namespace JwtAuthenticationApi.Wrappers.Threading +namespace JwtAuthenticationApi.Common.Wrappers.Threading { - using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.CodeAnalysis; + using JwtAuthenticationApi.Common.Abstraction.Wrappers.Threading; - /// - /// wrapper. - /// - [ExcludeFromCodeCoverage] - public class MutexWrapper: IMutexWrapper + /// + /// wrapper. + /// + [ExcludeFromCodeCoverage] + internal class MutexWrapper: IMutexWrapper { private readonly Mutex _mutex; diff --git a/JwtAuthenticationApi/Wrappers/Threading/SemaphoreWrapper.cs b/JwtAuthenticationApi.Common/Wrappers/Threading/SemaphoreWrapper.cs similarity index 79% rename from JwtAuthenticationApi/Wrappers/Threading/SemaphoreWrapper.cs rename to JwtAuthenticationApi.Common/Wrappers/Threading/SemaphoreWrapper.cs index 731df8b..d8542f0 100644 --- a/JwtAuthenticationApi/Wrappers/Threading/SemaphoreWrapper.cs +++ b/JwtAuthenticationApi.Common/Wrappers/Threading/SemaphoreWrapper.cs @@ -1,10 +1,11 @@ -using System.Diagnostics.CodeAnalysis; - -namespace JwtAuthenticationApi.Wrappers.Threading +namespace JwtAuthenticationApi.Common.Wrappers.Threading { - [ExcludeFromCodeCoverage] + using System.Diagnostics.CodeAnalysis; + using JwtAuthenticationApi.Common.Abstraction.Wrappers.Threading; + + [ExcludeFromCodeCoverage] /// - public sealed class SemaphoreWrapper: ISemaphoreWrapper + internal sealed class SemaphoreWrapper: ISemaphoreWrapper { private readonly Semaphore _semaphore; diff --git a/JwtAuthenticationApi.UnitTests/Controllers/UserRegisterControllerTests.cs b/JwtAuthenticationApi.Controllers.Tests/Controllers/UserRegisterControllerTests.cs similarity index 79% rename from JwtAuthenticationApi.UnitTests/Controllers/UserRegisterControllerTests.cs rename to JwtAuthenticationApi.Controllers.Tests/Controllers/UserRegisterControllerTests.cs index a13333b..6805ed1 100644 --- a/JwtAuthenticationApi.UnitTests/Controllers/UserRegisterControllerTests.cs +++ b/JwtAuthenticationApi.Controllers.Tests/Controllers/UserRegisterControllerTests.cs @@ -1,16 +1,18 @@ -using System.Net; -using JwtAuthenticationApi.Controllers; -using JwtAuthenticationApi.Models.Enums; -using JwtAuthenticationApi.Models.Registration.Requests; -using JwtAuthenticationApi.Models.Registration.Responses; -using JwtAuthenticationApi.Registration; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using TddXt.AnyRoot.Numbers; -using static TddXt.AnyRoot.Root; - -namespace JwtAuthenticationApi.UnitTests.Controllers +namespace JwtAuthenticationApi.Controllers.Tests.Controllers { + using System.Net; + using FluentAssertions; + using JwtAuthenticationApi.Controllers.Controllers; + using Microsoft.AspNetCore.Mvc; + using NSubstitute; + using NUnit.Framework; + using Services.Abstraction.Registration; + using Services.Models.Enums; + using Services.Models.Registration.Requests; + using Services.Models.Registration.Responses; + using TddXt.AnyRoot.Numbers; + using static TddXt.AnyRoot.Root; + [TestFixture, Parallelizable] public sealed class UserRegisterControllerTests { @@ -25,7 +27,7 @@ public void SetUp() } [Test] - public async Task ShouldReturn201IfUserIsCreated() + public async Task RegisterUserAsync_IfUserIsCreated_Returns201Created() { // Arrange var userId = Any.Integer(); @@ -49,7 +51,7 @@ public async Task ShouldReturn201IfUserIsCreated() } [Test] - public async Task ShouldReturn400IfPasswordValidationFails() + public async Task RegisterUserAsync_IfPasswordValidationFails_Return400BadRequest() { // Arrange var userId = Any.Integer(); @@ -73,10 +75,9 @@ public async Task ShouldReturn400IfPasswordValidationFails() } [Test] - public async Task ShouldReturn409IfUserWithThatUserNameExists() + public async Task RegisterUserAsync_IfUserNameExists_Return409Conflict() { // Arrange - var userId = Any.Integer(); var serviceResponse = new RegisterUserResponse() { IsSuccessful = false, @@ -97,10 +98,9 @@ public async Task ShouldReturn409IfUserWithThatUserNameExists() } [Test] - public async Task ShouldReturn500IfDbErrorOccurred() + public async Task RegisterUserAsync_IfDbErrorOccurred_Returns500InternalServerError() { // Arrange - var userId = Any.Integer(); var serviceResponse = new RegisterUserResponse() { IsSuccessful = false, @@ -121,10 +121,9 @@ public async Task ShouldReturn500IfDbErrorOccurred() } [Test] - public async Task ShouldReturn500IfInternalErrorOccurred() + public async Task RegisterUserAsync_IfUnexpectedErrorOccured_Return500InternalServerError() { // Arrange - var userId = Any.Integer(); var serviceResponse = new RegisterUserResponse() { IsSuccessful = false, diff --git a/JwtAuthenticationApi.Controllers.Tests/JwtAuthenticationApi.Controllers.Tests.csproj b/JwtAuthenticationApi.Controllers.Tests/JwtAuthenticationApi.Controllers.Tests.csproj new file mode 100644 index 0000000..7c13f4b --- /dev/null +++ b/JwtAuthenticationApi.Controllers.Tests/JwtAuthenticationApi.Controllers.Tests.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + enable + disable + + false + true + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/JwtAuthenticationApi/Controllers/TestController.cs b/JwtAuthenticationApi.Controllers/Controllers/TestController.cs similarity index 68% rename from JwtAuthenticationApi/Controllers/TestController.cs rename to JwtAuthenticationApi.Controllers/Controllers/TestController.cs index a90dc61..722630c 100644 --- a/JwtAuthenticationApi/Controllers/TestController.cs +++ b/JwtAuthenticationApi.Controllers/Controllers/TestController.cs @@ -1,12 +1,13 @@ -namespace JwtAuthenticationApi.Controllers +namespace JwtAuthenticationApi.Controllers.Controllers { - using Models.Options; - using Microsoft.AspNetCore.Mvc; - using Microsoft.Extensions.Options; - using System.Diagnostics.CodeAnalysis; - using Abstraction.DatabaseContext; - using Security.Password.Salt; - using ILogger = Serilog.ILogger; + using System.Diagnostics.CodeAnalysis; + using JwtAuthenticationApi.Common.Options; + using JwtAuthenticationApi.Infrastructure.Abstraction.Database; + using JwtAuthenticationApi.Security.Abstraction.Salt; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using ILogger = Serilog.ILogger; [Obsolete] @@ -16,22 +17,16 @@ namespace JwtAuthenticationApi.Controllers public class TestController : ControllerBase { private readonly ILogger _logger; - private readonly IOptions _options; - private readonly IUserContext _userContext; - private readonly IPasswordSaltContext _passwordSaltContext; + private readonly IOptions _passwordPepperOptions; private readonly ILogger _seriLogger; private readonly ISaltService _saltService; public TestController(ILogger logger, - IOptions options, IUserContext userContext, - IPasswordSaltContext passwordSaltContext, IOptions passwordPepperOptions, ILogger seriLogger, + IOptions passwordPepperOptions, ILogger seriLogger, ISaltService saltService) { _logger = logger; - _options = options; - _userContext = userContext; - _passwordSaltContext = passwordSaltContext; _passwordPepperOptions = passwordPepperOptions; _seriLogger = seriLogger; _saltService = saltService; diff --git a/JwtAuthenticationApi.Controllers/Controllers/UserRegisterController.cs b/JwtAuthenticationApi.Controllers/Controllers/UserRegisterController.cs new file mode 100644 index 0000000..7e8b061 --- /dev/null +++ b/JwtAuthenticationApi.Controllers/Controllers/UserRegisterController.cs @@ -0,0 +1,51 @@ +namespace JwtAuthenticationApi.Controllers.Controllers +{ + using Services.Abstraction.Registration; + using Services.Models.Enums; + using Services.Models.Registration.Requests; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + + [ApiController] + [Route("Register")] + public class UserRegisterController : ControllerBase + { + private readonly IUserRegisterService _userRegisterService; + + public UserRegisterController(IUserRegisterService userRegisterService) + { + _userRegisterService = userRegisterService; + } + + [HttpPost] + [Route("User")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + + + public async Task RegisterUserAsync([FromBody] RegisterUserRequest registerUserRequest, CancellationToken cancellationToken) + { + var result = await _userRegisterService.RegisterUserAsync(registerUserRequest, cancellationToken); + if (result.IsSuccessful) + { + return Created(this.Request?.Path ?? "", result.UserId); + } + + switch (result.ErrorType) + { + case ErrorType.PasswordValidationError: + return BadRequest(result.ErrorMessage); + case ErrorType.DbErrorEntityExists: + return Conflict(result.ErrorMessage); + case ErrorType.DbError: + case ErrorType.InternalError: + default: + return StatusCode(500, result.ErrorMessage); + } + } + + } +} + diff --git a/JwtAuthenticationApi.Controllers/JwtAuthenticationApi.Controllers.csproj b/JwtAuthenticationApi.Controllers/JwtAuthenticationApi.Controllers.csproj new file mode 100644 index 0000000..e223b8c --- /dev/null +++ b/JwtAuthenticationApi.Controllers/JwtAuthenticationApi.Controllers.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + disable + + + + + + + + + + + + + diff --git a/JwtAuthenticationApi.Infrastructure.Tests/JwtAuthenticationApi.Infrastructure.Tests.csproj b/JwtAuthenticationApi.Infrastructure.Tests/JwtAuthenticationApi.Infrastructure.Tests.csproj new file mode 100644 index 0000000..2e7fc67 --- /dev/null +++ b/JwtAuthenticationApi.Infrastructure.Tests/JwtAuthenticationApi.Infrastructure.Tests.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + disable + + false + true + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/JwtAuthenticationApi/Abstraction/DatabaseContext/IContext.cs b/JwtAuthenticationApi.Infrastructure/Abstraction/Database/IContext.cs similarity index 79% rename from JwtAuthenticationApi/Abstraction/DatabaseContext/IContext.cs rename to JwtAuthenticationApi.Infrastructure/Abstraction/Database/IContext.cs index 052d616..2380ee1 100644 --- a/JwtAuthenticationApi/Abstraction/DatabaseContext/IContext.cs +++ b/JwtAuthenticationApi.Infrastructure/Abstraction/Database/IContext.cs @@ -1,9 +1,9 @@ -namespace JwtAuthenticationApi.Abstraction.DatabaseContext +namespace JwtAuthenticationApi.Infrastructure.Abstraction.Database { /// /// Base interface for database contexts. Defines saving methods. /// - public interface IContext + internal interface IContext { /// Task SaveChangesAsync(); diff --git a/JwtAuthenticationApi.Infrastructure/Abstraction/Database/IPasswordSaltContext.cs b/JwtAuthenticationApi.Infrastructure/Abstraction/Database/IPasswordSaltContext.cs new file mode 100644 index 0000000..dc96bad --- /dev/null +++ b/JwtAuthenticationApi.Infrastructure/Abstraction/Database/IPasswordSaltContext.cs @@ -0,0 +1,16 @@ +namespace JwtAuthenticationApi.Infrastructure.Abstraction.Database +{ + using Entities; + using Microsoft.EntityFrameworkCore; + + /// + /// Defines properties for password salt database context. + /// + internal interface IPasswordSaltContext : IContext + { + /// + /// Gets password salt database set. + /// + DbSet PasswordSalt { get; } + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi.Infrastructure/Abstraction/Database/IUserContext.cs b/JwtAuthenticationApi.Infrastructure/Abstraction/Database/IUserContext.cs new file mode 100644 index 0000000..3748923 --- /dev/null +++ b/JwtAuthenticationApi.Infrastructure/Abstraction/Database/IUserContext.cs @@ -0,0 +1,16 @@ +namespace JwtAuthenticationApi.Infrastructure.Abstraction.Database +{ + using Entities; + using Microsoft.EntityFrameworkCore; + + /// + /// Defines properties for user database context. + /// + internal interface IUserContext : IContext + { + /// + /// Gets users database set. + /// + DbSet Users { get; } + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi/Abstraction/Entity/EntityBase.cs b/JwtAuthenticationApi.Infrastructure/Abstraction/Entity/EntityBase.cs similarity index 57% rename from JwtAuthenticationApi/Abstraction/Entity/EntityBase.cs rename to JwtAuthenticationApi.Infrastructure/Abstraction/Entity/EntityBase.cs index 5356dea..de706c4 100644 --- a/JwtAuthenticationApi/Abstraction/Entity/EntityBase.cs +++ b/JwtAuthenticationApi.Infrastructure/Abstraction/Entity/EntityBase.cs @@ -1,12 +1,12 @@ -namespace JwtAuthenticationApi.Abstraction.Entity +namespace JwtAuthenticationApi.Infrastructure.Abstraction.Entity { - using System.ComponentModel.DataAnnotations; - using System.ComponentModel.DataAnnotations.Schema; + using System.ComponentModel.DataAnnotations; + using System.ComponentModel.DataAnnotations.Schema; - /// - /// Represents base property for Entity. - /// - public abstract class EntityBase + /// + /// Represents base property for Entity. + /// + public abstract class EntityBase { /// /// Entity Identifier. Represented by value. diff --git a/JwtAuthenticationApi.Infrastructure/Abstraction/Repository/ISaltRepository.cs b/JwtAuthenticationApi.Infrastructure/Abstraction/Repository/ISaltRepository.cs new file mode 100644 index 0000000..b540134 --- /dev/null +++ b/JwtAuthenticationApi.Infrastructure/Abstraction/Repository/ISaltRepository.cs @@ -0,0 +1,10 @@ +namespace JwtAuthenticationApi.Infrastructure.Abstraction.Repository +{ + using Entities; + + public interface ISaltRepository + { + Task AddAsync(PasswordSaltEntity salt, CancellationToken cancellationToken); + Task GetSaltAsync(int userId, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi.Infrastructure/Abstraction/Repository/IUserRepository.cs b/JwtAuthenticationApi.Infrastructure/Abstraction/Repository/IUserRepository.cs new file mode 100644 index 0000000..2344747 --- /dev/null +++ b/JwtAuthenticationApi.Infrastructure/Abstraction/Repository/IUserRepository.cs @@ -0,0 +1,11 @@ +namespace JwtAuthenticationApi.Infrastructure.Abstraction.Repository +{ + using Entities; + + public interface IUserRepository + { + Task UserExistsAsync(string username); + Task UserExistsAsync(int userId); + Task AddAsync(UserEntity userEntity, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi/DatabaseContext/PasswordSaltContext.cs b/JwtAuthenticationApi.Infrastructure/Database/PasswordSaltContext.cs similarity index 77% rename from JwtAuthenticationApi/DatabaseContext/PasswordSaltContext.cs rename to JwtAuthenticationApi.Infrastructure/Database/PasswordSaltContext.cs index 6b26ef3..1690a55 100644 --- a/JwtAuthenticationApi/DatabaseContext/PasswordSaltContext.cs +++ b/JwtAuthenticationApi.Infrastructure/Database/PasswordSaltContext.cs @@ -1,16 +1,16 @@ -namespace JwtAuthenticationApi.DatabaseContext +namespace JwtAuthenticationApi.Infrastructure.Database { - using Microsoft.EntityFrameworkCore; - using System.Diagnostics.CodeAnalysis; - using Entities; - using Abstraction.DatabaseContext; + using System.Diagnostics.CodeAnalysis; + using JwtAuthenticationApi.Infrastructure.Abstraction.Database; + using Entities; + using Microsoft.EntityFrameworkCore; /// /// Represents database context that contains password salt table and handles saving changes. /// Inherits from . /// [ExcludeFromCodeCoverage] - public sealed class PasswordSaltContext : DbContext, IPasswordSaltContext + internal sealed class PasswordSaltContext : DbContext, IPasswordSaltContext { /// public DbSet PasswordSalt { get; set; } diff --git a/JwtAuthenticationApi/DatabaseContext/UserContext.cs b/JwtAuthenticationApi.Infrastructure/Database/UserContext.cs similarity index 80% rename from JwtAuthenticationApi/DatabaseContext/UserContext.cs rename to JwtAuthenticationApi.Infrastructure/Database/UserContext.cs index 7e82bb6..19c29de 100644 --- a/JwtAuthenticationApi/DatabaseContext/UserContext.cs +++ b/JwtAuthenticationApi.Infrastructure/Database/UserContext.cs @@ -1,16 +1,16 @@ -namespace JwtAuthenticationApi.DatabaseContext +namespace JwtAuthenticationApi.Infrastructure.Database { - using Microsoft.EntityFrameworkCore; - using Entities; - using System.Diagnostics.CodeAnalysis; - using Abstraction.DatabaseContext; + using System.Diagnostics.CodeAnalysis; + using JwtAuthenticationApi.Infrastructure.Abstraction.Database; + using Entities; + using Microsoft.EntityFrameworkCore; /// /// Represents database context that contains user identity table and handles saving changes. /// Inherits from . /// [ExcludeFromCodeCoverage] - public sealed class UserContext: DbContext, IUserContext + internal sealed class UserContext: DbContext, IUserContext { /// public DbSet Users { get; set; } diff --git a/JwtAuthenticationApi/Entities/PasswordSaltEntity.cs b/JwtAuthenticationApi.Infrastructure/Entities/PasswordSaltEntity.cs similarity index 86% rename from JwtAuthenticationApi/Entities/PasswordSaltEntity.cs rename to JwtAuthenticationApi.Infrastructure/Entities/PasswordSaltEntity.cs index 29417c8..0526a5c 100644 --- a/JwtAuthenticationApi/Entities/PasswordSaltEntity.cs +++ b/JwtAuthenticationApi.Infrastructure/Entities/PasswordSaltEntity.cs @@ -1,9 +1,9 @@ -namespace JwtAuthenticationApi.Entities +namespace JwtAuthenticationApi.Infrastructure.Entities { - using Abstraction.Entity; using System.ComponentModel.DataAnnotations; + using Abstraction.Entity; - public class PasswordSaltEntity: EntityBase + public class PasswordSaltEntity: EntityBase { /// /// Gets or sets Password salt. diff --git a/JwtAuthenticationApi/Entities/UserEntity.cs b/JwtAuthenticationApi.Infrastructure/Entities/UserEntity.cs similarity index 91% rename from JwtAuthenticationApi/Entities/UserEntity.cs rename to JwtAuthenticationApi.Infrastructure/Entities/UserEntity.cs index 6336e9d..6386feb 100644 --- a/JwtAuthenticationApi/Entities/UserEntity.cs +++ b/JwtAuthenticationApi.Infrastructure/Entities/UserEntity.cs @@ -1,4 +1,4 @@ -namespace JwtAuthenticationApi.Entities +namespace JwtAuthenticationApi.Infrastructure.Entities { using Abstraction.Entity; @@ -36,7 +36,7 @@ public UserEntity(string username, string hashedPassword, string email) Username = username; HashedPassword = hashedPassword; Email = email; - CreationDate = DateTime.Now; + CreationDate = DateTime.UtcNow; } } } diff --git a/JwtAuthenticationApi.Infrastructure/InfrastructureInstaller.cs b/JwtAuthenticationApi.Infrastructure/InfrastructureInstaller.cs new file mode 100644 index 0000000..5d7577f --- /dev/null +++ b/JwtAuthenticationApi.Infrastructure/InfrastructureInstaller.cs @@ -0,0 +1,27 @@ +namespace JwtAuthenticationApi.Infrastructure +{ + using Abstraction.Repository; + using JwtAuthenticationApi.Infrastructure.Abstraction.Database; + using Database; + using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.DependencyInjection; + using Repository; + + public static class InfrastructureInstaller + { + public static void InstallInfrastructureProject(this IServiceCollection serviceCollection, Func identityDatabaseConnectionStringFunc, Func saltDatabaseConnectionStringFunc) + { + serviceCollection.AddDbContext(options => + { + options.UseNpgsql(identityDatabaseConnectionStringFunc()); + }); + serviceCollection.AddDbContext(options => + { + options.UseNpgsql(saltDatabaseConnectionStringFunc()); + }); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + + } + } +} diff --git a/JwtAuthenticationApi.Infrastructure/JwtAuthenticationApi.Infrastructure.csproj b/JwtAuthenticationApi.Infrastructure/JwtAuthenticationApi.Infrastructure.csproj new file mode 100644 index 0000000..8963262 --- /dev/null +++ b/JwtAuthenticationApi.Infrastructure/JwtAuthenticationApi.Infrastructure.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + disable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/JwtAuthenticationApi.Infrastructure/Repository/SaltRepository.cs b/JwtAuthenticationApi.Infrastructure/Repository/SaltRepository.cs new file mode 100644 index 0000000..fba339a --- /dev/null +++ b/JwtAuthenticationApi.Infrastructure/Repository/SaltRepository.cs @@ -0,0 +1,29 @@ +namespace JwtAuthenticationApi.Infrastructure.Repository +{ + using Abstraction.Database; + using Entities; + using JwtAuthenticationApi.Infrastructure.Abstraction.Repository; + using Microsoft.EntityFrameworkCore; + using System.Threading; + + internal class SaltRepository: ISaltRepository + { + private readonly IPasswordSaltContext _passwordSaltContext; + + public SaltRepository(IPasswordSaltContext passwordSaltContext) + { + _passwordSaltContext = passwordSaltContext; + } + + public async Task AddAsync(PasswordSaltEntity salt, CancellationToken cancellationToken) + { + await _passwordSaltContext.PasswordSalt.AddAsync(salt, cancellationToken); + return await _passwordSaltContext.SaveChangesAsync(cancellationToken); + } + + public Task GetSaltAsync(int userId, CancellationToken cancellationToken) + { + return _passwordSaltContext.PasswordSalt.FirstOrDefaultAsync(u => u.UserId.Equals(userId), cancellationToken); + } + } +} diff --git a/JwtAuthenticationApi.Infrastructure/Repository/UserRepository.cs b/JwtAuthenticationApi.Infrastructure/Repository/UserRepository.cs new file mode 100644 index 0000000..44e411a --- /dev/null +++ b/JwtAuthenticationApi.Infrastructure/Repository/UserRepository.cs @@ -0,0 +1,33 @@ +namespace JwtAuthenticationApi.Infrastructure.Repository +{ + using Abstraction.Database; + using Entities; + using JwtAuthenticationApi.Infrastructure.Abstraction.Repository; + using Microsoft.EntityFrameworkCore; + + internal sealed class UserRepository: IUserRepository + { + private readonly IUserContext _userContext; + + public UserRepository(IUserContext userContext) + { + _userContext = userContext; + } + + public Task UserExistsAsync(string username) + { + return _userContext.Users.AnyAsync(e => e.Username.Equals(username)); + } + + public Task UserExistsAsync(int userId) + { + return _userContext.Users.AnyAsync(e => e.Id.Equals(userId)); + } + + public async Task AddAsync(UserEntity userEntity, CancellationToken cancellationToken) + { + await _userContext.Users.AddAsync(userEntity, cancellationToken); + await _userContext.SaveChangesAsync(cancellationToken); + } + } +} diff --git a/JwtAuthenticationApi.UnitTests/Commands/PasswordMixCommandTests.cs b/JwtAuthenticationApi.Security.Tests/Commands/PasswordMixCommandTests.cs similarity index 79% rename from JwtAuthenticationApi.UnitTests/Commands/PasswordMixCommandTests.cs rename to JwtAuthenticationApi.Security.Tests/Commands/PasswordMixCommandTests.cs index 6e18453..fe80754 100644 --- a/JwtAuthenticationApi.UnitTests/Commands/PasswordMixCommandTests.cs +++ b/JwtAuthenticationApi.Security.Tests/Commands/PasswordMixCommandTests.cs @@ -1,16 +1,18 @@ -namespace JwtAuthenticationApi.UnitTests.Commands +namespace JwtAuthenticationApi.Security.Tests.Commands { - using JwtAuthenticationApi.Commands; + using Common.Exceptions; + using Common.Models; + using FluentAssertions; + using NUnit.Framework; + using Security.Commands; using TddXt.AnyRoot.Strings; - using Exceptions; - using JwtAuthenticationApi.Commands.Models; using static TddXt.AnyRoot.Root; [TestFixture, Parallelizable] public class PasswordMixCommandTests { [Test] - public async Task ShouldMixPasswordWithPepperAndSalt() + public async Task ExecuteAsync_MixesPasswordWithPepperAndSalt() { // Arrange var password = Any.String(); @@ -28,7 +30,7 @@ public async Task ShouldMixPasswordWithPepperAndSalt() [TestCase(null)] [TestCase("")] - public async Task ShouldThrowExceptionIfProvidedPasswordIsNullOrEmpty(string password) + public async Task ExecuteAsync_IfProvidedPasswordIsNullOrEmpty_ThrowsCommandExecutionException(string password) { // Arrange var salt = Any.String(); @@ -46,7 +48,7 @@ await function.Should() [TestCase(null)] [TestCase("")] - public async Task ShouldThrowExceptionIfProvidedSaltIsNullOrEmpty(string salt) + public async Task ExecuteAsync_IfProvidedSaltIsNullOrEmpty_ThrowsCommandExecutionException(string salt) { // Arrange var password = Any.String(); @@ -64,7 +66,7 @@ await function.Should() [TestCase(null)] [TestCase("")] - public async Task ShouldThrowExceptionIfProvidedPepperIsNullOrEmpty(string pepper) + public async Task ExecuteAsync_IfProvidedPepperIsNullOrEmpty_ThrowsCommandExecutionException(string pepper) { // Arrange var password = Any.String(); diff --git a/JwtAuthenticationApi.UnitTests/Security/Password/PasswordHashingServiceTests.cs b/JwtAuthenticationApi.Security.Tests/Cryptography/PasswordHashingServiceTests.cs similarity index 50% rename from JwtAuthenticationApi.UnitTests/Security/Password/PasswordHashingServiceTests.cs rename to JwtAuthenticationApi.Security.Tests/Cryptography/PasswordHashingServiceTests.cs index 87baa98..37d2179 100644 --- a/JwtAuthenticationApi.UnitTests/Security/Password/PasswordHashingServiceTests.cs +++ b/JwtAuthenticationApi.Security.Tests/Cryptography/PasswordHashingServiceTests.cs @@ -1,19 +1,20 @@ -namespace JwtAuthenticationApi.UnitTests.Security.Password +namespace JwtAuthenticationApi.Security.Tests.Cryptography { - using JwtAuthenticationApi.Handlers; - using Models.Options; - using JwtAuthenticationApi.Security.Password; - using Microsoft.Extensions.Options; - using JwtAuthenticationApi.Abstraction.Commands; - using JwtAuthenticationApi.Commands.Models; - using JwtAuthenticationApi.Factories.Commands; + using Common.Abstraction.Commands; + using Common.Abstraction.Handlers; + using Common.Models; + using Common.Options; + using FluentAssertions; + using Microsoft.Extensions.Options; + using NSubstitute; + using NUnit.Framework; + using Security.Cryptography; - [TestFixture, Parallelizable] + [TestFixture, Parallelizable] public class PasswordHashingServiceTests { private IOptions _passwordOptions; private ICommandHandler _commandHandler; - private ICommandFactory _commandFactory; private PasswordHashingService _uut; [SetUp] @@ -21,12 +22,11 @@ public void SetUp() { _passwordOptions = Substitute.For>(); _commandHandler = Substitute.For(); - _commandFactory = Substitute.For(); - _uut = new PasswordHashingService(_passwordOptions, _commandHandler, _commandFactory); + _uut = new PasswordHashingService(_passwordOptions, _commandHandler); } [Test] - public async Task ShouldHashPassword() + public async Task HashPasswordAsync_HashesPassword() { // Arrange const string salt = "SALT"; @@ -34,10 +34,8 @@ public async Task ShouldHashPassword() const string password = "PASSWORD"; const string expectedHash = "FsjT2moPUhUXzwImF0vUbj+Rd4QFgfYvOFcKbqSL4rY="; const string mixedPassword = $"{password}{pepper}{salt}"; - ICommand command = Substitute.For>(); - _passwordOptions.Value.Returns(new PasswordPepper() { Pepper = pepper }); - _commandFactory.CreatePasswordMixCommand(password, salt, pepper).Returns(command); - _commandHandler.HandleAsync(command, Arg.Any()) + _passwordOptions.Value.Returns(new PasswordPepper { Pepper = pepper }); + _commandHandler.HandleAsync(Arg.Any>(), Arg.Any()) .Returns(new Result(mixedPassword, true)); // Act diff --git a/JwtAuthenticationApi.Security.Tests/Helpers.cs b/JwtAuthenticationApi.Security.Tests/Helpers.cs new file mode 100644 index 0000000..d136b12 --- /dev/null +++ b/JwtAuthenticationApi.Security.Tests/Helpers.cs @@ -0,0 +1,49 @@ +namespace JwtAuthenticationApi.Security.Tests +{ + internal static class Helpers + { + public static void ExecuteAndCatch(this Action action) + { + try + { + action(); + } + finally { } + } + public static void ExecuteAndCatch(this Func func) + { + try + { + func(); + } + finally { } + } + + public static async Task ExecuteAndCatchAsync(this Func func) where TOut: Task + { + try + { + await func(); + } + finally { } + } + + public static void ExecuteAndCatch(this Func func, TIn arg1) + { + try + { + func(arg1); + } + finally { } + } + + public static void ExecuteAndCatch(this Func func, T1 arg1, T2 arg2) + { + try + { + func(arg1, arg2); + } + finally { } + } + } +} diff --git a/JwtAuthenticationApi.Security.Tests/JwtAuthenticationApi.Security.Tests.csproj b/JwtAuthenticationApi.Security.Tests/JwtAuthenticationApi.Security.Tests.csproj new file mode 100644 index 0000000..5cc44d8 --- /dev/null +++ b/JwtAuthenticationApi.Security.Tests/JwtAuthenticationApi.Security.Tests.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + enable + disable + + false + true + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/JwtAuthenticationApi.UnitTests/Security/Password/Salt/SaltProviderTests.cs b/JwtAuthenticationApi.Security.Tests/Salt/SaltProviderTests.cs similarity index 76% rename from JwtAuthenticationApi.UnitTests/Security/Password/Salt/SaltProviderTests.cs rename to JwtAuthenticationApi.Security.Tests/Salt/SaltProviderTests.cs index 8876726..14caafd 100644 --- a/JwtAuthenticationApi.UnitTests/Security/Password/Salt/SaltProviderTests.cs +++ b/JwtAuthenticationApi.Security.Tests/Salt/SaltProviderTests.cs @@ -1,10 +1,13 @@ -namespace JwtAuthenticationApi.UnitTests.Security.Password.Salt +namespace JwtAuthenticationApi.Security.Tests.Salt { - using JwtAuthenticationApi.Commands.Models; - using Models; - using JwtAuthenticationApi.Security.Password.Salt; - using static TddXt.AnyRoot.Root; + using Abstraction.Salt; + using Common.Models; + using FluentAssertions; + using NSubstitute; + using NUnit.Framework; + using Security.Salt; using TddXt.AnyRoot.Numbers; + using static TddXt.AnyRoot.Root; [TestFixture, Parallelizable] public class SaltProviderTests @@ -20,7 +23,7 @@ public void SetUp() } [Test] - public async Task ShouldCreateSaltIfUserIsNotInDatabase() + public async Task GetPasswordSaltAsync_IfUserIsNotInDatabase_CreatesSalt() { // Arrange int userId = Any.Integer(); @@ -37,7 +40,7 @@ public async Task ShouldCreateSaltIfUserIsNotInDatabase() } [Test] - public async Task ShouldReturnSaltFromDatabaseIfUserIsInDatabase() + public async Task GetPasswordSaltAsync_IfUserIsInDatabase_ReturnsSaltFromDatabase() { // Arrange int userId = Any.Integer(); diff --git a/JwtAuthenticationApi.Security.Tests/Salt/SaltServiceTests.cs b/JwtAuthenticationApi.Security.Tests/Salt/SaltServiceTests.cs new file mode 100644 index 0000000..1d3f1e6 --- /dev/null +++ b/JwtAuthenticationApi.Security.Tests/Salt/SaltServiceTests.cs @@ -0,0 +1,222 @@ +namespace JwtAuthenticationApi.Security.Tests.Salt +{ + using Common.Abstraction.Factories; + using Common.Abstraction.Factories.Polly; + using Common.Abstraction.Factories.Wrappers; + using Common.Abstraction.Wrappers.Threading; + using Common.Models; + using FluentAssertions; + using Infrastructure.Abstraction.Database; + using Infrastructure.Abstraction.Repository; + using Infrastructure.Entities; + using Microsoft.EntityFrameworkCore; + using MockQueryable.NSubstitute; + using NSubstitute; + using NSubstitute.ExceptionExtensions; + using NUnit.Framework; + using Security.Salt; + using Serilog; + using TddXt.AnyRoot.Numbers; + using TddXt.AnyRoot.Strings; + using static TddXt.AnyRoot.Root; + + [TestFixture, Parallelizable] + public class SaltServiceTests + { + private ISaltRepository _saltRepository; + private SaltService _uut; + private IGuidFactory _guidWrapper; + private IPollySleepingIntervalsFactory _pollySleepingIntervalsFactory; + private ILogger _logger; + #pragma warning disable NUnit1032 + private ISemaphoreWrapper _semaphoreWrapper; + #pragma warning restore NUnit1032 + private ISemaphoreWrapperFactory _semaphoreWrapperFactory; + + [SetUp] + public void SetUp() + { + _saltRepository = Substitute.For(); + _guidWrapper = Substitute.For(); + _semaphoreWrapper = Substitute.For(); + _semaphoreWrapperFactory = Substitute.For(); + _semaphoreWrapperFactory.Create(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(_semaphoreWrapper); + _pollySleepingIntervalsFactory = Substitute.For(); + _logger = Substitute.For(); + _uut = new SaltService(_saltRepository, _guidWrapper, + _pollySleepingIntervalsFactory,_semaphoreWrapperFactory, _logger); + } + + [Test] + public async Task SaveSaltAsync_CreatesAndSavesNewSalt() + { + // Arrange + int userId = Any.Integer(); + Guid salt = Guid.NewGuid(); + _guidWrapper.CreateGuid().Returns(salt); + + // Act + await _uut.SaveSaltAsync(salt.ToString(), userId); + + // Assert + await _saltRepository.Received(1).AddAsync(Arg.Is(x => x.Salt.Equals(salt.ToString())), Arg.Any()); + } + + [Test] + public async Task GetSaltAsync_IfUserDoesNotExists_ReturnsNotSuccessfulResult() + { + // Arrange + int userId = Any.Integer(); + + // Act + Result actual = await _uut.GetSaltAsync(userId); + + // Assert + actual.IsSuccessful.Should().BeFalse(); + actual.Value.Should().BeNull(); + } + + [Test] + public async Task GetSaltAsync_IfExceptionOccurs_ShouldRetryGettingSalt() + { + // Arrange + int userId = Any.Integer(); + _saltRepository.GetSaltAsync(userId, CancellationToken.None).ThrowsAsync(); + _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(new List{ TimeSpan.Zero, TimeSpan.Zero }); + Func func = async () => await _uut.GetSaltAsync(userId, CancellationToken.None); + + // Act + await func.ExecuteAndCatchAsync(); + + // Assert + await _saltRepository.Received(3).GetSaltAsync(userId, CancellationToken.None); + _semaphoreWrapper.Received(1).Release(); + } + + [Test] + public async Task GetSaltAsync_IfGettingSaltThrowsExceptionAfterRetries_ReturnsNotSuccessfulResult() + { + // Arrange + int userId = Any.Integer(); + + _saltRepository.GetSaltAsync(userId, CancellationToken.None).ThrowsAsync(); + _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(new List{ TimeSpan.Zero }); + + // Act + var actual = await _uut.GetSaltAsync(userId); + + // Assert + actual.IsSuccessful.Should().BeFalse(); + actual.Value.Should().Be(null); + _semaphoreWrapper.Received(1).Release(); + } + + [Test] + public async Task GetSaltAsync_IfUserExists_ReturnsSuccessfulResultWithSalt() + { + // Arrange + int userId = Any.Integer(); + string salt = Any.String(); + PasswordSaltEntity passwordSalt = new PasswordSaltEntity(salt, userId); + _saltRepository.GetSaltAsync(userId, CancellationToken.None).Returns(passwordSalt); + + // Act + var actual = await _uut.GetSaltAsync(userId); + + // Assert + actual.IsSuccessful.Should().BeTrue(); + actual.Value.Should().Be(salt); + } + + [Test] + public async Task SaveSaltAsync_ReleasesSemaphore_AfterDatabaseExceptionOccursInSavingSaltMethod() + { + // Arrange + int userId = Any.Integer(); + Guid salt = Guid.NewGuid(); + PasswordSaltEntity passwordSaltEntity = new PasswordSaltEntity(salt.ToString(), userId); + _guidWrapper.CreateGuid().Returns(salt); + _saltRepository.AddAsync(passwordSaltEntity, Arg.Any()) + .ThrowsAsync(); + + Func> func = async () => await _uut.SaveSaltAsync(salt.ToString(), userId); + + // Act & Assert + await func.Should().ThrowAsync(); + _semaphoreWrapper.Received(1).Release(); + } + + [Test] + public async Task GetSaltAsync_ReleasesSemaphore_AfterExceptionOccursInGettingSaltMethod() + { + // Arrange + int userId = Any.Integer(); + string salt = Any.String(); + _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) + .Throws(); + + // Act + await _uut.GetSaltAsync(userId); + + // Assert + _logger.Received(1).Error(Arg.Any(), Arg.Any()); + _semaphoreWrapper.Received(1).Release(); + } + + [Test] + public async Task SaveSaltAsync_IfExceptionOccurs_RetriesSavingChanges() + { + // Arrange + int userId = Any.Integer(); + Guid salt = Guid.NewGuid(); + PasswordSaltEntity passwordSaltEntity = new PasswordSaltEntity(salt.ToString(), userId); + _guidWrapper.CreateGuid().Returns(salt); + + _saltRepository.AddAsync(passwordSaltEntity, Arg.Any()) + .ThrowsAsync(); + _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(new List{ TimeSpan.Zero, TimeSpan.Zero }); + + // Act + await _uut.SaveSaltAsync(salt.ToString(), userId); + + // Assert + await _saltRepository.ReceivedWithAnyArgs(3).AddAsync(passwordSaltEntity, Arg.Any()); + _semaphoreWrapper.Received(1).Release(); + } + + [Test] + public async Task SaveSaltAsync_IfExceptionStillOccursAfterRetries_ShouldThrowDbUpdateException() + { + // Arrange + int userId = Any.Integer(); + Guid salt = Guid.NewGuid(); + PasswordSaltEntity passwordSaltEntity = new PasswordSaltEntity(salt.ToString(), userId); + _guidWrapper.CreateGuid().Returns(salt); + _saltRepository.AddAsync(passwordSaltEntity, Arg.Any()) + .ThrowsAsync(); + _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(new List(2) { TimeSpan.Zero, TimeSpan.Zero }); + + Func> function = async () => await _uut.SaveSaltAsync(salt.ToString(),userId); + + // Act & Assert + await function.Should().ThrowAsync(); + await _saltRepository.ReceivedWithAnyArgs(3).AddAsync(passwordSaltEntity, Arg.Any()); + _semaphoreWrapper.Received(1).Release(); + } + + [Test] + public void GenerateSalt_GeneratesSalt() + { + // Act + var actual = _uut.GenerateSalt(); + // Assert + actual.Should().NotBeNull(); + actual.Length.Should().BeGreaterThan(0); + } + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi.Security/Abstraction/Cryptography/IPasswordHashingService.cs b/JwtAuthenticationApi.Security/Abstraction/Cryptography/IPasswordHashingService.cs new file mode 100644 index 0000000..f49a88c --- /dev/null +++ b/JwtAuthenticationApi.Security/Abstraction/Cryptography/IPasswordHashingService.cs @@ -0,0 +1,18 @@ +namespace JwtAuthenticationApi.Security.Abstraction.Cryptography +{ + /// + /// Defines method for password hashing. + /// + public interface IPasswordHashingService + { + /// + /// Hashes provided password with pepper and salt using SHA256. + /// + /// Password. + /// Password salt. + /// Cancellation token. + /// A task that represents the asynchronous password mix operation. + /// The task result contains Base64 hashed password. + Task HashPasswordAsync(string password, string salt, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi.Security/Abstraction/Salt/ISaltProvider.cs b/JwtAuthenticationApi.Security/Abstraction/Salt/ISaltProvider.cs new file mode 100644 index 0000000..5fb78ec --- /dev/null +++ b/JwtAuthenticationApi.Security/Abstraction/Salt/ISaltProvider.cs @@ -0,0 +1,16 @@ +namespace JwtAuthenticationApi.Security.Abstraction.Salt +{ + /// + /// Defines method for providing password salt from database. + /// + public interface ISaltProvider + { + /// + /// Gets or creates password salt associated with . + /// + /// UserId for whom password salt will be queried. + /// Cancellation token. + /// Password salt. + Task GetPasswordSaltAsync(int userId, CancellationToken cancellationToken = new CancellationToken()); + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi.Security/Abstraction/Salt/ISaltService.cs b/JwtAuthenticationApi.Security/Abstraction/Salt/ISaltService.cs new file mode 100644 index 0000000..51be1ae --- /dev/null +++ b/JwtAuthenticationApi.Security/Abstraction/Salt/ISaltService.cs @@ -0,0 +1,35 @@ +namespace JwtAuthenticationApi.Security.Abstraction.Salt +{ + using Common.Models; + + /// + /// Defines methods for generating, receiving and saving password salts. + /// + public interface ISaltService + { + /// + /// Saves salt associated with in salt database. + /// + /// User associated salt. + /// UserId for whom password salt will be created. + /// Cancellation token. + /// A task that represents the asynchronous operation of creating and saving password salt. + /// The task result contains id of newly created salt. + Task SaveSaltAsync(string salt, int userId, CancellationToken cancellationToken = new CancellationToken()); + + /// + /// Gets password salt associated with . + /// + /// UserId for whom password salt will be queried. + /// Cancellation token. + /// A task that represents the asynchronous operation of getting password salt from database. + /// The task result contains value of operation result. + Task> GetSaltAsync(int userId, CancellationToken cancellationToken = new CancellationToken()); + + /// + /// Generates new password salt. + /// + /// value of salt. + string GenerateSalt(); + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi/Commands/PasswordMixCommand.cs b/JwtAuthenticationApi.Security/Commands/PasswordMixCommand.cs similarity index 90% rename from JwtAuthenticationApi/Commands/PasswordMixCommand.cs rename to JwtAuthenticationApi.Security/Commands/PasswordMixCommand.cs index cc99803..a136b3b 100644 --- a/JwtAuthenticationApi/Commands/PasswordMixCommand.cs +++ b/JwtAuthenticationApi.Security/Commands/PasswordMixCommand.cs @@ -1,13 +1,13 @@ -namespace JwtAuthenticationApi.Commands +namespace JwtAuthenticationApi.Security.Commands { - using Abstraction.Commands; - using Exceptions; - using Models; + using JwtAuthenticationApi.Common.Abstraction.Commands; + using Common.Models; + using Common.Exceptions; /// /// Command that is responsible for mixing password in predefined way. Implements . /// - public sealed class PasswordMixCommand: ICommand + internal sealed class PasswordMixCommand: ICommand { private readonly string _password; private readonly string _salt; diff --git a/JwtAuthenticationApi/Security/Password/PasswordHashingService.cs b/JwtAuthenticationApi.Security/Cryptography/PasswordHashingService.cs similarity index 63% rename from JwtAuthenticationApi/Security/Password/PasswordHashingService.cs rename to JwtAuthenticationApi.Security/Cryptography/PasswordHashingService.cs index 89e4ff3..01c979c 100644 --- a/JwtAuthenticationApi/Security/Password/PasswordHashingService.cs +++ b/JwtAuthenticationApi.Security/Cryptography/PasswordHashingService.cs @@ -1,32 +1,28 @@ -using JwtAuthenticationApi.Factories.Commands; - -namespace JwtAuthenticationApi.Security.Password +namespace JwtAuthenticationApi.Security.Cryptography { - using System.Security.Cryptography; - using System.Text; - using Models.Options; - using Microsoft.Extensions.Options; - using Handlers; + using System.Security.Cryptography; + using System.Text; + using JwtAuthenticationApi.Common.Abstraction.Handlers; + using Common.Options; + using JwtAuthenticationApi.Security.Abstraction.Cryptography; + using Microsoft.Extensions.Options; + using Commands; - /// - public class PasswordHashingService: IPasswordHashingService + /// + internal class PasswordHashingService: IPasswordHashingService { private readonly IOptions _passwordPepperOptions; private readonly ICommandHandler _commandHandler; - private readonly ICommandFactory _commandFactory; /// /// Initializes new instance of class. /// /// Password pepper option as . /// Command Handler. - /// Commands Factory. - public PasswordHashingService(IOptions passwordPepperOptions, ICommandHandler commandHandler, - ICommandFactory commandFactory) + public PasswordHashingService(IOptions passwordPepperOptions, ICommandHandler commandHandler) { _passwordPepperOptions = passwordPepperOptions; _commandHandler = commandHandler; - _commandFactory = commandFactory; } /// @@ -34,8 +30,7 @@ public PasswordHashingService(IOptions passwordPepperOptions, IC { using (var hashingAlgorithm = SHA256.Create()) { - var passwordMixCommand = - _commandFactory.CreatePasswordMixCommand(password, salt, _passwordPepperOptions.Value.Pepper); + PasswordMixCommand passwordMixCommand = new PasswordMixCommand(password, salt, _passwordPepperOptions.Value.Pepper); var passwordMixResult = await _commandHandler.HandleAsync(passwordMixCommand, cancellationToken); var mixedPasswordBytes = Encoding.UTF8.GetBytes(passwordMixResult.Value); var hashedPassword = await hashingAlgorithm.ComputeHashAsync(new MemoryStream(mixedPasswordBytes), cancellationToken); diff --git a/JwtAuthenticationApi.Security/JwtAuthenticationApi.Security.csproj b/JwtAuthenticationApi.Security/JwtAuthenticationApi.Security.csproj new file mode 100644 index 0000000..857961d --- /dev/null +++ b/JwtAuthenticationApi.Security/JwtAuthenticationApi.Security.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + disable + + + + + + + + + + + + + + + + <_Parameter1>JwtAuthenticationApi.Security.Tests + + + + diff --git a/JwtAuthenticationApi/Security/Password/Salt/SaltProvider.cs b/JwtAuthenticationApi.Security/Salt/SaltProvider.cs similarity index 81% rename from JwtAuthenticationApi/Security/Password/Salt/SaltProvider.cs rename to JwtAuthenticationApi.Security/Salt/SaltProvider.cs index c5b6470..2139e13 100644 --- a/JwtAuthenticationApi/Security/Password/Salt/SaltProvider.cs +++ b/JwtAuthenticationApi.Security/Salt/SaltProvider.cs @@ -1,11 +1,12 @@ -namespace JwtAuthenticationApi.Security.Password.Salt +namespace JwtAuthenticationApi.Security.Salt { - using Commands.Models; + using JwtAuthenticationApi.Common.Models; + using JwtAuthenticationApi.Security.Abstraction.Salt; /// /// Implementation of . Provides method for getting password salt. /// - public sealed class SaltProvider: ISaltProvider + internal sealed class SaltProvider: ISaltProvider { private readonly ISaltService _saltService; diff --git a/JwtAuthenticationApi/Security/Password/Salt/SaltService.cs b/JwtAuthenticationApi.Security/Salt/SaltService.cs similarity index 75% rename from JwtAuthenticationApi/Security/Password/Salt/SaltService.cs rename to JwtAuthenticationApi.Security/Salt/SaltService.cs index 8c80ef0..337f5cc 100644 --- a/JwtAuthenticationApi/Security/Password/Salt/SaltService.cs +++ b/JwtAuthenticationApi.Security/Salt/SaltService.cs @@ -1,24 +1,26 @@ -namespace JwtAuthenticationApi.Security.Password.Salt +namespace JwtAuthenticationApi.Security.Salt { - using Abstraction.DatabaseContext; - using Commands.Models; - using Wrappers; + using JwtAuthenticationApi.Common.Abstraction.Factories.Wrappers; + using JwtAuthenticationApi.Common.Abstraction.Wrappers.Threading; + using Common.Models; + using Infrastructure.Abstraction.Repository; + using JwtAuthenticationApi.Common.Abstraction.Factories.Polly; + using JwtAuthenticationApi.Infrastructure.Abstraction.Database; + using Infrastructure.Entities; + using JwtAuthenticationApi.Security.Abstraction.Salt; using Microsoft.EntityFrameworkCore; - using Factories.Wrappers; - using Wrappers.Threading; using ILogger = Serilog.ILogger; - using Factories.Polly; using Polly; - using Entities; + using JwtAuthenticationApi.Common.Abstraction.Factories; - /// - public sealed class SaltService: ISaltService + /// + internal sealed class SaltService: ISaltService { private const int SemaphoreInitialCount = 1; private const int SemaphoreMaximalCount = 1; - private readonly IPasswordSaltContext _context; - private readonly IGuidWrapper _guidWrapper; + private readonly ISaltRepository _saltRepository; + private readonly IGuidFactory _guidFactory; private readonly IPollySleepingIntervalsFactory _pollySleepingIntervalsFactory; private readonly ISemaphoreWrapperFactory _semaphoreWrapperFactory; private readonly ILogger _logger; @@ -26,23 +28,23 @@ public sealed class SaltService: ISaltService /// /// Initializes new instance of class. /// - /// Password salt database context. - /// Guid wrapper. + /// Password salt repository. + /// Guid wrapper. /// Factory for sleeping interval used in Polly. /// Semaphore wrapper factory. /// Logger. - public SaltService(IPasswordSaltContext context, IGuidWrapper guidWrapper, + public SaltService(ISaltRepository saltRepository, IGuidFactory guidFactory, IPollySleepingIntervalsFactory pollySleepingIntervalsFactory, ISemaphoreWrapperFactory semaphoreWrapperFactory, ILogger logger) { - _context = context; - _guidWrapper = guidWrapper; + _saltRepository = saltRepository; + _guidFactory = guidFactory; _pollySleepingIntervalsFactory = pollySleepingIntervalsFactory; _semaphoreWrapperFactory = semaphoreWrapperFactory; _logger = logger; } - public string GenerateSalt() => _guidWrapper.CreateGuid().ToString(); + public string GenerateSalt() => _guidFactory.CreateGuid().ToString(); /// public async Task SaveSaltAsync(string salt, int userId, CancellationToken cancellationToken = new CancellationToken()) @@ -60,8 +62,7 @@ public SaltService(IPasswordSaltContext context, IGuidWrapper guidWrapper, int? id; try { - await _context.PasswordSalt.AddAsync(passwordSaltContext, cancellationToken); - id = await dbSavePolicy.ExecuteAsync(async () => await _context.SaveChangesAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); + id = await dbSavePolicy.ExecuteAsync(async () => await _saltRepository.AddAsync(passwordSaltContext, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); _logger.Information($"Password salt saved for user {userId}."); } catch (DbUpdateException ex) @@ -99,9 +100,7 @@ public SaltService(IPasswordSaltContext context, IGuidWrapper guidWrapper, }); var a = await policy.ExecuteAsync( - async (ct) => await _context - .PasswordSalt - .FirstOrDefaultAsync(u => u.UserId.Equals(userId), ct) + async (ct) => await _saltRepository.GetSaltAsync(userId, cancellationToken) .ConfigureAwait(false), cancellationToken, false).ConfigureAwait(false); passwordSaltModel = a; } diff --git a/JwtAuthenticationApi.Security/SecurityInstaller.cs b/JwtAuthenticationApi.Security/SecurityInstaller.cs new file mode 100644 index 0000000..58847e8 --- /dev/null +++ b/JwtAuthenticationApi.Security/SecurityInstaller.cs @@ -0,0 +1,18 @@ +namespace JwtAuthenticationApi.Security +{ + using Abstraction.Cryptography; + using Abstraction.Salt; + using Cryptography; + using Microsoft.Extensions.DependencyInjection; + using Salt; + + public static class SecurityInstaller + { + public static void InstallSecurity(this IServiceCollection serviceCollection) + { + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + } + } +} diff --git a/JwtAuthenticationApi.UnitTests/Commands/ConvertRequestToUserEntityCommandTests.cs b/JwtAuthenticationApi.Services.Tests/Commands/ConvertRequestToUserEntityCommandTests.cs similarity index 67% rename from JwtAuthenticationApi.UnitTests/Commands/ConvertRequestToUserEntityCommandTests.cs rename to JwtAuthenticationApi.Services.Tests/Commands/ConvertRequestToUserEntityCommandTests.cs index 9c3761a..3bec6af 100644 --- a/JwtAuthenticationApi.UnitTests/Commands/ConvertRequestToUserEntityCommandTests.cs +++ b/JwtAuthenticationApi.Services.Tests/Commands/ConvertRequestToUserEntityCommandTests.cs @@ -1,16 +1,18 @@ -namespace JwtAuthenticationApi.UnitTests.Commands +namespace JwtAuthenticationApi.Services.Tests.Commands { - using JwtAuthenticationApi.Commands; - using JwtAuthenticationApi.Models.Registration.Requests; - using TddXt.AnyRoot.Strings; - using static TddXt.AnyRoot.Root; + using FluentAssertions; + using Models.Registration.Requests; + using NUnit.Framework; + using Services.Commands; + using TddXt.AnyRoot.Strings; + using static TddXt.AnyRoot.Root; [TestFixture, Parallelizable] public class ConvertRequestToUserEntityCommandTests { [Test] - public async Task ShouldCreateUserModelFromRequest() + public async Task ExecuteAsync_CreatesUserModelFromRequest() { // Arrange RegisterUserRequest request = Any.Instance(); diff --git a/JwtAuthenticationApi.UnitTests/Factories/Password/PasswordContextFactoryTests.cs b/JwtAuthenticationApi.Services.Tests/Factories/Password/PasswordContextFactoryTests.cs similarity index 76% rename from JwtAuthenticationApi.UnitTests/Factories/Password/PasswordContextFactoryTests.cs rename to JwtAuthenticationApi.Services.Tests/Factories/Password/PasswordContextFactoryTests.cs index f1808f0..18140bf 100644 --- a/JwtAuthenticationApi.UnitTests/Factories/Password/PasswordContextFactoryTests.cs +++ b/JwtAuthenticationApi.Services.Tests/Factories/Password/PasswordContextFactoryTests.cs @@ -1,9 +1,10 @@ -namespace JwtAuthenticationApi.UnitTests.Factories.Password +namespace JwtAuthenticationApi.Services.Tests.Factories.Password { - using TestHelpers.Attributes; - using JwtAuthenticationApi.Factories.Password; + using FluentAssertions; + using NUnit.Framework; + using Services.Factories.Password; - [TestFixture, Parallelizable, FactoryTest] + [TestFixture, Parallelizable] public class PasswordContextFactoryTests { private PasswordContextFactory _sut; @@ -17,7 +18,7 @@ public void SetUp() [TestCase("AAAaaaAAA",6)] [TestCase("CvCvCC", 4)] [TestCase("aaa", 0)] - public void ShouldCountCorrectlyUpperLetters(string password, int totalUpperLetters) + public void Create_CountsCorrectlyUpperLetters(string password, int totalUpperLetters) { // Act var actual = _sut.Create(password, password); @@ -29,7 +30,7 @@ public void ShouldCountCorrectlyUpperLetters(string password, int totalUpperLett [TestCase("AAAaaaAAA", 3)] [TestCase("CvCvCC", 2)] [TestCase("AAA", 0)] - public void ShouldCountCorrectlyLowerLetters(string password, int totalUpperLetters) + public void Create_CountsCorrectlyLowerLetters(string password, int totalUpperLetters) { // Act var actual = _sut.Create(password, password); @@ -41,7 +42,7 @@ public void ShouldCountCorrectlyLowerLetters(string password, int totalUpperLett [TestCase("!Password!", 2)] [TestCase("!;[]ggg78CCSs&^", 8)] [TestCase("AAA", 0)] - public void ShouldCountCorrectlySpecialLetters(string password, int totalUpperLetters) + public void Create_CountsCorrectlySpecialLetters(string password, int totalUpperLetters) { // Act var actual = _sut.Create(password, password); @@ -51,7 +52,7 @@ public void ShouldCountCorrectlySpecialLetters(string password, int totalUpperLe } [Test] - public void ShouldCorrectlyCreatePasswordContext() + public void Create_CorrectlyCreatePasswordContext() { // Arrange const string password = "!HArDToBr3a4Passw0rd%"; diff --git a/JwtAuthenticationApi.UnitTests/Factories/Password/PasswordRuleFactoryTests.cs b/JwtAuthenticationApi.Services.Tests/Factories/Password/PasswordRuleFactoryTests.cs similarity index 66% rename from JwtAuthenticationApi.UnitTests/Factories/Password/PasswordRuleFactoryTests.cs rename to JwtAuthenticationApi.Services.Tests/Factories/Password/PasswordRuleFactoryTests.cs index 5d45bb2..a9167f1 100644 --- a/JwtAuthenticationApi.UnitTests/Factories/Password/PasswordRuleFactoryTests.cs +++ b/JwtAuthenticationApi.Services.Tests/Factories/Password/PasswordRuleFactoryTests.cs @@ -1,12 +1,13 @@ -namespace JwtAuthenticationApi.UnitTests.Factories.Password +namespace JwtAuthenticationApi.Services.Tests.Factories.Password { - using Abstraction.RuleEngine; - using JwtAuthenticationApi.Factories.Password; - using JwtAuthenticationApi.Models.Password; - using TestHelpers.Attributes; - using JwtAuthenticationApi.Validators.Password.Rules; + using Abstraction.RuleEngine; + using FluentAssertions; + using Models.Password; + using NUnit.Framework; + using Services.Factories.Password; + using Services.Validators.Password.Rules; - [TestFixture, Parallelizable, FactoryTest] + [TestFixture, Parallelizable] public sealed class PasswordRuleFactoryTests { private PasswordRuleFactory _sut; @@ -18,7 +19,7 @@ public void Setup() } [Test] - public void ShouldCreateEqualityRule() + public void CreateEqualityRule_CreatesEqualityRule() { // Act var actual = _sut.CreateEqualityRule(); @@ -29,7 +30,7 @@ public void ShouldCreateEqualityRule() } [Test] - public void ShouldCreateLengthRule() + public void CreateLengthRule_CreatesLengthRule() { // Act var actual = _sut.CreateLengthRule(); @@ -40,7 +41,7 @@ public void ShouldCreateLengthRule() } [Test] - public void ShouldCreateLowerLettersRule() + public void CreateLowerLettersRule_CreatesLowerLettersRule() { // Act var actual = _sut.CreateLowerLettersRule(); @@ -51,7 +52,7 @@ public void ShouldCreateLowerLettersRule() } [Test] - public void ShouldCreateUpperLettersRule() + public void CreateUpperLettersRule_CreatesUpperLettersRule() { // Act var actual = _sut.CreateUpperLettersRule(); @@ -62,7 +63,7 @@ public void ShouldCreateUpperLettersRule() } [Test] - public void ShouldCreateSpecialLettersRule() + public void CreateSpecialLetterRule_CreatesSpecialLettersRule() { // Act var actual = _sut.CreateSpecialLetterRule(); diff --git a/JwtAuthenticationApi.Services.Tests/Helpers.cs b/JwtAuthenticationApi.Services.Tests/Helpers.cs new file mode 100644 index 0000000..90de6e7 --- /dev/null +++ b/JwtAuthenticationApi.Services.Tests/Helpers.cs @@ -0,0 +1,51 @@ +namespace JwtAuthenticationApi.Services.Tests +{ + using NSubstitute; + + internal static class Helpers + { + public static void ExecuteAndCatch(this Action action) + { + try + { + action(); + } + finally { } + } + public static void ExecuteAndCatch(this Func func) + { + try + { + func(); + } + finally { } + } + + public static async Task ExecuteAndCatchAsync(this Func func) where TOut: Task + { + try + { + await func(); + } + finally { } + } + + public static void ExecuteAndCatch(this Func func, TIn arg1) + { + try + { + func(arg1); + } + finally { } + } + + public static void ExecuteAndCatch(this Func func, T1 arg1, T2 arg2) + { + try + { + func(arg1, arg2); + } + finally { } + } + } +} diff --git a/JwtAuthenticationApi.Services.Tests/Identity/User/UserServiceTests.cs b/JwtAuthenticationApi.Services.Tests/Identity/User/UserServiceTests.cs new file mode 100644 index 0000000..1eca1b8 --- /dev/null +++ b/JwtAuthenticationApi.Services.Tests/Identity/User/UserServiceTests.cs @@ -0,0 +1,153 @@ +namespace JwtAuthenticationApi.Services.Tests.Identity.User +{ + using Common.Abstraction.Factories.Polly; + using FluentAssertions; + using Infrastructure.Abstraction.Database; + using Infrastructure.Abstraction.Repository; + using Infrastructure.Entities; + using Microsoft.EntityFrameworkCore; + using MockQueryable.NSubstitute; + using NSubstitute; + using NSubstitute.ExceptionExtensions; + using NUnit.Framework; + using Serilog; + using Services.Identity.User; + using TddXt.AnyRoot.Numbers; + using static TddXt.AnyRoot.Root; + + [TestFixture, Parallelizable] + public sealed class UserServiceTests + { + private IUserRepository _userRepository; + private IPollySleepingIntervalsFactory _pollySleepingIntervalsFactory; + private ILogger _logger; + private UserService _sut; + + [SetUp] + public void SetUp() + { + _userRepository = Substitute.For(); + _pollySleepingIntervalsFactory = Substitute.For(); + _logger = Substitute.For(); + _sut = new UserService(_userRepository, _pollySleepingIntervalsFactory, _logger); + } + + [Test] + public async Task SaveUserAsync_SavesUserInDatabase() + { + // Arrange + UserEntity userEntity = Any.Instance(); + _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(new List(1) + { + TimeSpan.Zero + }); + + // Act + int? actual = await _sut.SaveUserAsync(userEntity, CancellationToken.None); + + // Assert + actual.HasValue.Should().BeTrue(); + await _userRepository.Received(1).AddAsync(userEntity,CancellationToken.None); + } + + [Test] + public async Task SaveUserAsync_IfDbUpdateExceptionOccurs_RetriesSavingUser() + { + // Arrange + UserEntity userEntity = Any.Instance(); + _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(new List(1) + { + TimeSpan.Zero, + TimeSpan.Zero + }); + _userRepository.AddAsync(userEntity,CancellationToken.None).ThrowsAsync(); + Func func = async () => await _sut.SaveUserAsync(userEntity, CancellationToken.None); + + // Act + await func.ExecuteAndCatchAsync(); + + // Assert + await _userRepository.Received(3).AddAsync(userEntity, CancellationToken.None); + } + + [Test] + public async Task SaveUserAsync_IfExceptionIsReceivedAfterMaxRetryCount_ThrowsDbUpdateException() + { + // Arrange + int id = Any.Integer(); + UserEntity userEntity = Any.Instance(); + _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(new List(1) + { + TimeSpan.Zero, + TimeSpan.Zero, + TimeSpan.Zero + }); + _userRepository.AddAsync(userEntity, CancellationToken.None).ThrowsAsync(); + Func> func = async () => await _sut.SaveUserAsync(userEntity, CancellationToken.None); + + // Act & Assert + await func.Should().ThrowAsync(); + await _userRepository.Received(4).AddAsync(userEntity, CancellationToken.None); + } + + [Test] + public async Task UserExistsAsync_IfUserExistsBasedOnUsername_ReturnsTrue() + { + // Arrange + const string username = "Username"; + _userRepository.UserExistsAsync(username).Returns(true); + + // Act + var actual = await _sut.UserExistsAsync(username); + + // Assert + actual.Should().BeTrue(); + } + + [Test] + public async Task UserExistsAsync_IfUserDoesNotExistsBasedOnUsername_ReturnsFalse() + { + // Arrange + const string username = "Username"; + _userRepository.UserExistsAsync(username).Returns(false); + + // Act + var actual = await _sut.UserExistsAsync(username); + + // Assert + actual.Should().BeFalse(); + } + + [Test] + public async Task UserExistsAsync_IUserExistsBasedOnUserId_ReturnsTrue() + { + // Arrange + const int userId = 1; + _userRepository.UserExistsAsync(userId).Returns(true); + + // Act + var actual = await _sut.UserExistsAsync(userId); + + // Assert + actual.Should().BeTrue(); + } + + [Test] + public async Task UserExistsAsync_IfUserDoesNotExistsBasedOnUserId_ReturnsFalse() + { + // Arrange + const int userId = 1; + _userRepository.UserExistsAsync(userId).Returns(true); + + // Act + var actual = await _sut.UserExistsAsync(userId); + + // Assert + actual.Should().BeFalse(); + } + + } +} diff --git a/JwtAuthenticationApi.Services.Tests/JwtAuthenticationApi.Services.Tests.csproj b/JwtAuthenticationApi.Services.Tests/JwtAuthenticationApi.Services.Tests.csproj new file mode 100644 index 0000000..d3e27ce --- /dev/null +++ b/JwtAuthenticationApi.Services.Tests/JwtAuthenticationApi.Services.Tests.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + enable + disable + + false + true + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/JwtAuthenticationApi.UnitTests/Registration/UserRegisterServiceTests.cs b/JwtAuthenticationApi.Services.Tests/Registration/UserRegisterServiceTests.cs similarity index 70% rename from JwtAuthenticationApi.UnitTests/Registration/UserRegisterServiceTests.cs rename to JwtAuthenticationApi.Services.Tests/Registration/UserRegisterServiceTests.cs index f566e38..7af2a3f 100644 --- a/JwtAuthenticationApi.UnitTests/Registration/UserRegisterServiceTests.cs +++ b/JwtAuthenticationApi.Services.Tests/Registration/UserRegisterServiceTests.cs @@ -1,25 +1,27 @@ -using JwtAuthenticationApi.Abstraction.Commands; -using JwtAuthenticationApi.Commands.Models; -using JwtAuthenticationApi.Entities; -using JwtAuthenticationApi.Factories.Commands; -using JwtAuthenticationApi.Handlers; -using JwtAuthenticationApi.Identity.User; -using JwtAuthenticationApi.Models.Enums; -using JwtAuthenticationApi.Models.Registration.Requests; -using JwtAuthenticationApi.Registration; -using JwtAuthenticationApi.Security.Password; -using JwtAuthenticationApi.Security.Password.Salt; -using JwtAuthenticationApi.Validators.Password; -using Microsoft.EntityFrameworkCore; -using NSubstitute.ExceptionExtensions; -using NSubstitute.ReturnsExtensions; -using Serilog; -using TddXt.AnyRoot.Numbers; -using TddXt.AnyRoot.Strings; -using static TddXt.AnyRoot.Root; - -namespace JwtAuthenticationApi.UnitTests.Registration +namespace JwtAuthenticationApi.Services.Tests.Registration { + using Abstraction.Identity.User; + using Abstraction.Validators.Password; + using Common.Abstraction.Commands; + using Common.Abstraction.Handlers; + using Common.Models; + using FluentAssertions; + using Infrastructure.Entities; + using Microsoft.EntityFrameworkCore; + using Models.Enums; + using Models.Registration.Requests; + using NSubstitute; + using NSubstitute.ExceptionExtensions; + using NSubstitute.ReturnsExtensions; + using NUnit.Framework; + using Security.Abstraction.Cryptography; + using Security.Abstraction.Salt; + using Serilog; + using Services.Registration; + using TddXt.AnyRoot.Numbers; + using TddXt.AnyRoot.Strings; + using static TddXt.AnyRoot.Root; + [TestFixture, Parallelizable] public sealed class UserRegisterServiceTests { @@ -27,11 +29,9 @@ public sealed class UserRegisterServiceTests private IPasswordValidator _passwordValidator; private IPasswordHashingService _passwordHashingService; private ICommandHandler _commandHandler; - private ICommandFactory _commandFactory; private IUserService _userService; private ILogger _logger; private UserRegisterService _sut; - private ICommand _convertRequestToUserEntityCommand; [SetUp] public void Setup() @@ -40,20 +40,13 @@ public void Setup() _passwordValidator = Substitute.For(); _passwordHashingService = Substitute.For(); _commandHandler = Substitute.For(); - _commandFactory = Substitute.For(); _userService = Substitute.For(); _logger = Substitute.For(); - _convertRequestToUserEntityCommand = Substitute.For>(); - - _commandFactory.CreateConvertRequestToUserEntityCommand(Arg.Any(), Arg.Any()) - .Returns(_convertRequestToUserEntityCommand); - - _sut = new UserRegisterService(_saltService, _passwordValidator, _passwordHashingService, _commandHandler, - _commandFactory, _userService, _logger); + _sut = new UserRegisterService(_saltService, _passwordValidator, _passwordHashingService, _commandHandler, _userService, _logger); } [Test] - public async Task ShouldReturnValidationErrorIfPasswordValidationFails() + public async Task RegisterUserAsync_IfPasswordValidationFails_ReturnsValidationError() { // Arrange var request = Any.Instance(); @@ -70,14 +63,14 @@ public async Task ShouldReturnValidationErrorIfPasswordValidationFails() } [Test] - public async Task ShouldReturnInternalErrorIfErrorOccurredDuringEntityCreation() + public async Task RegisterUserAsync_IfErrorOccurredDuringEntityCreation_ReturnsInternalError() { // Arrange var request = Any.Instance(); var salt = Any.String(); _passwordValidator.Validate(request.Password, request.PasswordConfirmation).Returns(true); _saltService.GenerateSalt().Returns(salt); - _commandHandler.HandleAsync(_convertRequestToUserEntityCommand, CancellationToken.None) + _commandHandler.HandleAsync(Arg.Any>(), CancellationToken.None) .Returns(new Result(null, false)); // Act @@ -91,7 +84,7 @@ public async Task ShouldReturnInternalErrorIfErrorOccurredDuringEntityCreation() } [Test] - public async Task ShouldReturnInternalErrorIfErrorOccurredDuringSavingUserInDatabase() + public async Task RegisterUserAsync_IfErrorOccurredDuringSavingUserInDatabase_ReturnsInternalError() { // Arrange var request = Any.Instance(); @@ -99,7 +92,7 @@ public async Task ShouldReturnInternalErrorIfErrorOccurredDuringSavingUserInData var userEntity = Any.Instance(); _passwordValidator.Validate(request.Password, request.PasswordConfirmation).Returns(true); _saltService.GenerateSalt().Returns(salt); - _commandHandler.HandleAsync(_convertRequestToUserEntityCommand, CancellationToken.None) + _commandHandler.HandleAsync(Arg.Any>(), CancellationToken.None) .Returns(new Result(userEntity, true)); _userService.SaveUserAsync(userEntity, CancellationToken.None).ReturnsNull(); @@ -114,7 +107,7 @@ public async Task ShouldReturnInternalErrorIfErrorOccurredDuringSavingUserInData } [Test] - public async Task ShouldReturnUserIdIfEveryStepIsSuccessful() + public async Task RegisterUserAsync_IfEveryStepIsSuccessful_ReturnsUserId() { // Arrange var request = Any.Instance(); @@ -123,7 +116,7 @@ public async Task ShouldReturnUserIdIfEveryStepIsSuccessful() var id = Any.Integer(); _passwordValidator.Validate(request.Password, request.PasswordConfirmation).Returns(true); _saltService.GenerateSalt().Returns(salt); - _commandHandler.HandleAsync(_convertRequestToUserEntityCommand, CancellationToken.None) + _commandHandler.HandleAsync(Arg.Any>(), CancellationToken.None) .Returns(new Result(userEntity, true)); _userService.SaveUserAsync(userEntity, CancellationToken.None).Returns(id); @@ -138,7 +131,7 @@ public async Task ShouldReturnUserIdIfEveryStepIsSuccessful() } [Test] - public async Task ShouldReturnUserExistsIfDbUpdateExceptionOccurred() + public async Task RegisterUserAsync_IfDbUpdateExceptionOccurred_ReturnsUserExists() { // Arrange var request = Any.Instance(); @@ -146,7 +139,7 @@ public async Task ShouldReturnUserExistsIfDbUpdateExceptionOccurred() var userEntity = Any.Instance(); _passwordValidator.Validate(request.Password, request.PasswordConfirmation).Returns(true); _saltService.GenerateSalt().Returns(salt); - _commandHandler.HandleAsync(_convertRequestToUserEntityCommand, CancellationToken.None) + _commandHandler.HandleAsync(Arg.Any>(), CancellationToken.None) .Returns(new Result(userEntity, true)); _userService.SaveUserAsync(userEntity, CancellationToken.None).ThrowsAsync(); _userService.UserExistsAsync(request.Username).Returns(true); @@ -163,7 +156,7 @@ public async Task ShouldReturnUserExistsIfDbUpdateExceptionOccurred() [Test] - public async Task ShouldReturnDbErrorIfDbUpdateExceptionOccurredAndUserDoesNotExists() + public async Task RegisterUserAsync_IfDbUpdateExceptionOccurredAndUserDoesNotExists_ReturnsDbError() { // Arrange var request = Any.Instance(); @@ -171,7 +164,7 @@ public async Task ShouldReturnDbErrorIfDbUpdateExceptionOccurredAndUserDoesNotEx var userEntity = Any.Instance(); _passwordValidator.Validate(request.Password, request.PasswordConfirmation).Returns(true); _saltService.GenerateSalt().Returns(salt); - _commandHandler.HandleAsync(_convertRequestToUserEntityCommand, CancellationToken.None) + _commandHandler.HandleAsync(Arg.Any>(), CancellationToken.None) .Returns(new Result(userEntity, true)); _userService.SaveUserAsync(userEntity, CancellationToken.None).ThrowsAsync(); _userService.UserExistsAsync(request.Username).Returns(false); @@ -187,7 +180,7 @@ public async Task ShouldReturnDbErrorIfDbUpdateExceptionOccurredAndUserDoesNotEx } [Test] - public async Task ShouldReturnInternalErrorIfAnyErrorThatIsNotHandledOccurs() + public async Task RegisterUserAsync_IfAnyErrorThatIsNotHandledOccurs_ReturnsInternalError() { // Arrange var request = Any.Instance(); diff --git a/JwtAuthenticationApi.UnitTests/Validators/Password/PasswordValidatorTests.cs b/JwtAuthenticationApi.Services.Tests/Validators/Password/PasswordValidatorTests.cs similarity index 86% rename from JwtAuthenticationApi.UnitTests/Validators/Password/PasswordValidatorTests.cs rename to JwtAuthenticationApi.Services.Tests/Validators/Password/PasswordValidatorTests.cs index 8945e07..4095549 100644 --- a/JwtAuthenticationApi.UnitTests/Validators/Password/PasswordValidatorTests.cs +++ b/JwtAuthenticationApi.Services.Tests/Validators/Password/PasswordValidatorTests.cs @@ -1,11 +1,14 @@ -namespace JwtAuthenticationApi.UnitTests.Validators.Password +namespace JwtAuthenticationApi.Services.Tests.Validators.Password { - using JwtAuthenticationApi.Abstraction.RuleEngine; + using Abstraction.Factories.Password; + using Abstraction.RuleEngine; using Exceptions; - using JwtAuthenticationApi.Factories.Password; - using JwtAuthenticationApi.Models.Password; - using JwtAuthenticationApi.Validators.Password; + using FluentAssertions; + using Models.Password; + using NSubstitute; + using NUnit.Framework; using Serilog; + using Services.Validators.Password; using TddXt.AnyRoot.Strings; using static TddXt.AnyRoot.Root; @@ -29,7 +32,7 @@ public void SetUp() } [Test] - public void ShouldReturnTrueIfPasswordIsValid() + public void Validate_IfPasswordIsValid_ReturnsTrue() { // Arrange string password = Any.String(); @@ -51,7 +54,7 @@ public void ShouldReturnTrueIfPasswordIsValid() } [Test] - public void ShouldReturnFalseIfPasswordIsInvalid() + public void Validate_IfPasswordIsInvalid_ReturnsFalse() { // Arrange string password = Any.String(); diff --git a/JwtAuthenticationApi.UnitTests/Validators/Password/RuleEngineTests.cs b/JwtAuthenticationApi.Services.Tests/Validators/Password/RuleEngineTests.cs similarity index 76% rename from JwtAuthenticationApi.UnitTests/Validators/Password/RuleEngineTests.cs rename to JwtAuthenticationApi.Services.Tests/Validators/Password/RuleEngineTests.cs index aab4941..339d7af 100644 --- a/JwtAuthenticationApi.UnitTests/Validators/Password/RuleEngineTests.cs +++ b/JwtAuthenticationApi.Services.Tests/Validators/Password/RuleEngineTests.cs @@ -1,12 +1,13 @@ -using JwtAuthenticationApi.Abstraction.RuleEngine; -using JwtAuthenticationApi.Extensions; -using JwtAuthenticationApi.Models.Password; - -namespace JwtAuthenticationApi.UnitTests.Validators.Password +namespace JwtAuthenticationApi.Services.Tests.Validators.Password { - using JwtAuthenticationApi.Validators.Password; + using Abstraction.RuleEngine; + using Extensions; + using Models.Password; + using NSubstitute; + using NUnit.Framework; + using Services.Validators.Password; - [TestFixture, Parallelizable] + [TestFixture, Parallelizable] public sealed class RuleEngineTests { private RuleEngine _sut; @@ -18,7 +19,7 @@ public void SetUp() } [Test] - public void ShouldEvaluateAllRules() + public void Validate_EvaluatesAllRules() { // Arrange PasswordContext passwordContext = new PasswordContext(null, null, 0, 0, 0, 0); diff --git a/JwtAuthenticationApi.UnitTests/Validators/Password/Rules/EqualityRuleTests.cs b/JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/EqualityRuleTests.cs similarity index 71% rename from JwtAuthenticationApi.UnitTests/Validators/Password/Rules/EqualityRuleTests.cs rename to JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/EqualityRuleTests.cs index fdecde4..b8163f5 100644 --- a/JwtAuthenticationApi.UnitTests/Validators/Password/Rules/EqualityRuleTests.cs +++ b/JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/EqualityRuleTests.cs @@ -1,11 +1,12 @@ -namespace JwtAuthenticationApi.UnitTests.Validators.Password.Rules +namespace JwtAuthenticationApi.Services.Tests.Validators.Password.Rules { - using Exceptions; - using JwtAuthenticationApi.Models.Password; - using TestHelpers.Attributes; - using JwtAuthenticationApi.Validators.Password.Rules; + using Exceptions; + using FluentAssertions; + using Models.Password; + using NUnit.Framework; + using Services.Validators.Password.Rules; - [TestFixture, Parallelizable, RuleTest] + [TestFixture, Parallelizable] public class EqualityRuleTests { private EqualityRule _sut; @@ -17,14 +18,14 @@ public void SetUp() } [Test] - public void ShouldReturnTrueForCanEvaluateRule() + public void CanEvaluateRule_ReturnsTrue() { // Act & Assert _sut.CanEvaluateRule(null).Should().BeTrue(); } [Test] - public void ShouldThrowPasswordValidationExceptionIfPasswordsAreNotEqual() + public void Evaluate_IfPasswordsAreNotEqual_ThrowsPasswordValidationException() { // Arrange const string password = "AAAAA"; @@ -38,7 +39,7 @@ public void ShouldThrowPasswordValidationExceptionIfPasswordsAreNotEqual() } [Test] - public void ShouldNotThrowPasswordValidationExceptionIfPasswordsAreEqual() + public void Evaluate_IfPasswordsAreEqual_ShouldNotThrowPasswordValidationException() { // Arrange const string password = "AAAAA"; diff --git a/JwtAuthenticationApi.UnitTests/Validators/Password/Rules/LengthRuleTests.cs b/JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/LengthRuleTests.cs similarity index 69% rename from JwtAuthenticationApi.UnitTests/Validators/Password/Rules/LengthRuleTests.cs rename to JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/LengthRuleTests.cs index f894984..0cdfedd 100644 --- a/JwtAuthenticationApi.UnitTests/Validators/Password/Rules/LengthRuleTests.cs +++ b/JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/LengthRuleTests.cs @@ -1,12 +1,13 @@ -namespace JwtAuthenticationApi.UnitTests.Validators.Password.Rules +namespace JwtAuthenticationApi.Services.Tests.Validators.Password.Rules { - using Constants; - using Exceptions; - using JwtAuthenticationApi.Models.Password; - using TestHelpers.Attributes; - using JwtAuthenticationApi.Validators.Password.Rules; - - [TestFixture, Parallelizable, RuleTest] + using Common.Constants; + using Exceptions; + using FluentAssertions; + using Models.Password; + using NUnit.Framework; + using Services.Validators.Password.Rules; + + [TestFixture, Parallelizable] public sealed class LengthRuleTests { private LengthRule _sut; @@ -18,14 +19,14 @@ public void SetUp() } [Test] - public void ShouldReturnTrueForCanEvaluateRule() + public void CanEvaluateRule_ReturnsTrue() { // Act & Assert _sut.CanEvaluateRule(null).Should().BeTrue(); } [Test] - public void ShouldThrowPasswordValidationExceptionIfPasswordsAreTooShort() + public void Evaluate_IfPasswordsIsTooShort_ThrowsPasswordValidationException() { // Arrange var passwordContext = new PasswordContext(null, null, JaaConstants.MinPasswordLength - 1, 0, 0, 0); @@ -36,7 +37,7 @@ public void ShouldThrowPasswordValidationExceptionIfPasswordsAreTooShort() } [Test] - public void ShouldThrowPasswordValidationExceptionIfPasswordsAreTooLong() + public void Evaluate_IfPasswordsIsTooLong_ThrowsPasswordValidationException() { // Arrange var passwordContext = new PasswordContext(null, null, JaaConstants.MaxPasswordLength + 9, 0, 0, 0); @@ -47,7 +48,7 @@ public void ShouldThrowPasswordValidationExceptionIfPasswordsAreTooLong() } [Test] - public void ShouldNotThrowPasswordValidationExceptionIfPasswordsLengthIsCorrect() + public void Evaluate_IfPasswordsLengthIsCorrect_ShouldNotThrowPasswordValidationException() { // Arrange var passwordContext = new PasswordContext(null, null, JaaConstants.MaxPasswordLength - 9, 0, 0, 0); diff --git a/JwtAuthenticationApi.UnitTests/Validators/Password/Rules/LowerLettersRuleTests.cs b/JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/LowerLettersRuleTests.cs similarity index 65% rename from JwtAuthenticationApi.UnitTests/Validators/Password/Rules/LowerLettersRuleTests.cs rename to JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/LowerLettersRuleTests.cs index 4907778..2ffce94 100644 --- a/JwtAuthenticationApi.UnitTests/Validators/Password/Rules/LowerLettersRuleTests.cs +++ b/JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/LowerLettersRuleTests.cs @@ -1,11 +1,12 @@ -namespace JwtAuthenticationApi.UnitTests.Validators.Password.Rules +namespace JwtAuthenticationApi.Services.Tests.Validators.Password.Rules { - using Exceptions; - using JwtAuthenticationApi.Models.Password; - using TestHelpers.Attributes; - using JwtAuthenticationApi.Validators.Password.Rules; + using Exceptions; + using FluentAssertions; + using Models.Password; + using NUnit.Framework; + using Services.Validators.Password.Rules; - [TestFixture, Parallelizable, RuleTest] + [TestFixture, Parallelizable] public sealed class LowerLettersRuleTests { private LowerLettersRule _sut; @@ -17,14 +18,14 @@ public void SetUp() } [Test] - public void ShouldReturnTrueForCanEvaluateRule() + public void CanEvaluateRule_ReturnsTrue() { // Act & Assert _sut.CanEvaluateRule(null).Should().BeTrue(); } [Test] - public void ShouldThrowPasswordValidationExceptionIfPasswordsDoesNotContainUpperLetters() + public void Evaluate_IfPasswordsDoesNotContainUpperLetters_ThrowsPasswordValidationException() { // Arrange var passwordContext = new PasswordContext(null, null, 0, 0, 0, 0); @@ -36,7 +37,7 @@ public void ShouldThrowPasswordValidationExceptionIfPasswordsDoesNotContainUpper } [Test] - public void ShouldNotThrowPasswordValidationExceptionIfPasswordsContainsUpperLetters() + public void Evaluate_IfPasswordsContainsUpperLetters_ShouldNotThrowPasswordValidationException() { // Arrange var passwordContext = new PasswordContext(null, null, 0, 0, 10, 0); diff --git a/JwtAuthenticationApi.UnitTests/Validators/Password/Rules/SpecialLettersRuleTests.cs b/JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/SpecialLettersRuleTests.cs similarity index 65% rename from JwtAuthenticationApi.UnitTests/Validators/Password/Rules/SpecialLettersRuleTests.cs rename to JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/SpecialLettersRuleTests.cs index 4b35bd8..d4804dc 100644 --- a/JwtAuthenticationApi.UnitTests/Validators/Password/Rules/SpecialLettersRuleTests.cs +++ b/JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/SpecialLettersRuleTests.cs @@ -1,11 +1,12 @@ -namespace JwtAuthenticationApi.UnitTests.Validators.Password.Rules +namespace JwtAuthenticationApi.Services.Tests.Validators.Password.Rules { - using Exceptions; - using JwtAuthenticationApi.Models.Password; - using TestHelpers.Attributes; - using JwtAuthenticationApi.Validators.Password.Rules; + using Exceptions; + using FluentAssertions; + using Models.Password; + using NUnit.Framework; + using Services.Validators.Password.Rules; - [TestFixture, Parallelizable, RuleTest] + [TestFixture, Parallelizable] public sealed class SpecialLettersRuleTests { private SpecialLettersRule _sut; @@ -17,14 +18,14 @@ public void SetUp() } [Test] - public void ShouldReturnTrueForCanEvaluateRule() + public void CanEvaluateRule_ReturnsTrue() { // Act & Assert _sut.CanEvaluateRule(null).Should().BeTrue(); } [Test] - public void ShouldThrowPasswordValidationExceptionIfPasswordsDoesNotContainUpperLetters() + public void Evaluate_ThrowsPasswordValidationException_IfPasswordsDoesNotContainUpperLetters() { // Arrange var passwordContext = new PasswordContext(null, null, 0, 0, 0, 0); @@ -35,7 +36,7 @@ public void ShouldThrowPasswordValidationExceptionIfPasswordsDoesNotContainUpper } [Test] - public void ShouldNotThrowPasswordValidationExceptionIfPasswordsContainsUpperLetters() + public void Evaluate_ShouldNotThrowPasswordValidationException_IfPasswordsContainsUpperLetters() { // Arrange var passwordContext = new PasswordContext(null, null, 0, 0, 0, 10); diff --git a/JwtAuthenticationApi.UnitTests/Validators/Password/Rules/UpperLettersRuleTests.cs b/JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/UpperLettersRuleTests.cs similarity index 65% rename from JwtAuthenticationApi.UnitTests/Validators/Password/Rules/UpperLettersRuleTests.cs rename to JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/UpperLettersRuleTests.cs index 259440e..0a75068 100644 --- a/JwtAuthenticationApi.UnitTests/Validators/Password/Rules/UpperLettersRuleTests.cs +++ b/JwtAuthenticationApi.Services.Tests/Validators/Password/Rules/UpperLettersRuleTests.cs @@ -1,11 +1,12 @@ -namespace JwtAuthenticationApi.UnitTests.Validators.Password.Rules +namespace JwtAuthenticationApi.Services.Tests.Validators.Password.Rules { - using Exceptions; - using JwtAuthenticationApi.Models.Password; - using TestHelpers.Attributes; - using JwtAuthenticationApi.Validators.Password.Rules; + using Exceptions; + using FluentAssertions; + using Models.Password; + using NUnit.Framework; + using Services.Validators.Password.Rules; - [TestFixture, Parallelizable, RuleTest] + [TestFixture, Parallelizable] public sealed class UpperLettersRuleTests { private UpperLettersRule _sut; @@ -17,14 +18,14 @@ public void SetUp() } [Test] - public void ShouldReturnTrueForCanEvaluateRule() + public void CanEvaluateRule_ReturnTrue() { // Act & Assert _sut.CanEvaluateRule(null).Should().BeTrue(); } [Test] - public void ShouldThrowPasswordValidationExceptionIfPasswordsDoesNotContainUpperLetters() + public void Evaluate_IfPasswordsDoesNotContainUpperLetters_ThrowsPasswordValidationException() { // Arrange var passwordContext = new PasswordContext(null, null, 0, 0, 0, 0); @@ -35,7 +36,7 @@ public void ShouldThrowPasswordValidationExceptionIfPasswordsDoesNotContainUpper } [Test] - public void ShouldNotThrowPasswordValidationExceptionIfPasswordsContainsUpperLetters() + public void Evaluate_IfPasswordsContainsUpperLetters_ShouldNotThrowPasswordValidationException() { // Arrange var passwordContext = new PasswordContext(null, null, 0, 7, 0, 0); diff --git a/JwtAuthenticationApi.Services/Abstraction/Factories/Password/IPasswordContextFactory.cs b/JwtAuthenticationApi.Services/Abstraction/Factories/Password/IPasswordContextFactory.cs new file mode 100644 index 0000000..c2bee0c --- /dev/null +++ b/JwtAuthenticationApi.Services/Abstraction/Factories/Password/IPasswordContextFactory.cs @@ -0,0 +1,18 @@ +namespace JwtAuthenticationApi.Services.Abstraction.Factories.Password +{ + using JwtAuthenticationApi.Services.Models.Password; + + /// + /// Defines method for password context factory. + /// + public interface IPasswordContextFactory + { + /// + /// Creates from provided password and password confirmation. + /// + /// Password. + /// Password confirmation. + /// + PasswordContext Create(string password, string passwordConfirmation); + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi.Services/Abstraction/Factories/Password/IPasswordRuleFactory.cs b/JwtAuthenticationApi.Services/Abstraction/Factories/Password/IPasswordRuleFactory.cs new file mode 100644 index 0000000..9308c4c --- /dev/null +++ b/JwtAuthenticationApi.Services/Abstraction/Factories/Password/IPasswordRuleFactory.cs @@ -0,0 +1,41 @@ +namespace JwtAuthenticationApi.Services.Abstraction.Factories.Password +{ + using RuleEngine; + using Models.Password; + + /// + /// Defines methods for password rules factory. + /// + public interface IPasswordRuleFactory + { + /// + /// Creates and returns . + /// + /// Instance of that implements + IRule CreateEqualityRule(); + + /// + /// Creates and returns . + /// + /// Instance of that implements + IRule CreateLengthRule(); + + /// + /// Creates and returns . + /// + /// Instance of that implements + IRule CreateLowerLettersRule(); + + /// + /// Creates and returns . + /// + /// Instance of that implements + IRule CreateSpecialLetterRule(); + + /// + /// Creates and returns . + /// + /// Instance of that implements + IRule CreateUpperLettersRule(); + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi.Services/Abstraction/Identity/User/IUserService.cs b/JwtAuthenticationApi.Services/Abstraction/Identity/User/IUserService.cs new file mode 100644 index 0000000..b5b1d5c --- /dev/null +++ b/JwtAuthenticationApi.Services/Abstraction/Identity/User/IUserService.cs @@ -0,0 +1,36 @@ +namespace JwtAuthenticationApi.Services.Abstraction.Identity.User +{ + using Infrastructure.Entities; + using Microsoft.EntityFrameworkCore; + + /// + /// Defines method for saving user in database. + /// + public interface IUserService + { + /// + /// Saves provided user in database, and returns its identifier in database. + /// + /// User entity which will be saved in database. + /// Cancellation token. + /// + /// User identifier in database, value is or . + Task SaveUserAsync(UserEntity userEntity, CancellationToken cancellationToken); + + /// + /// Determines if user exists in database. + /// + /// Its more efficient to use overload with userId if you already know user id. + /// Username for which database will be queried. + /// if user exists. Otherwise . + Task UserExistsAsync(string userName); + + /// + /// Determines if user exists in database. + /// + /// User id for which database will be queried. + /// if user exists. Otherwise . + Task UserExistsAsync(int userId); + } +} + diff --git a/JwtAuthenticationApi.Services/Abstraction/Registration/IUserRegisterService.cs b/JwtAuthenticationApi.Services/Abstraction/Registration/IUserRegisterService.cs new file mode 100644 index 0000000..1469e79 --- /dev/null +++ b/JwtAuthenticationApi.Services/Abstraction/Registration/IUserRegisterService.cs @@ -0,0 +1,21 @@ +namespace JwtAuthenticationApi.Services.Abstraction.Registration +{ + using JwtAuthenticationApi.Services.Models.Registration.Requests; + using JwtAuthenticationApi.Services.Models.Registration.Responses; + + /// + /// Defines method for user registration. + /// + public interface IUserRegisterService + { + /// + /// Register user - create user model, hash password and save this data in databases. + /// + /// user registration request. + /// Cancellation token. + /// A task that represents the asynchronous operation of registration + /// The task result contains ."/> + Task RegisterUserAsync(RegisterUserRequest registerUserRequest, CancellationToken cancellationToken); + } +} + diff --git a/JwtAuthenticationApi/Abstraction/RuleEngine/IRule.cs b/JwtAuthenticationApi.Services/Abstraction/RuleEngine/IRule.cs similarity index 90% rename from JwtAuthenticationApi/Abstraction/RuleEngine/IRule.cs rename to JwtAuthenticationApi.Services/Abstraction/RuleEngine/IRule.cs index dab5176..99f67d3 100644 --- a/JwtAuthenticationApi/Abstraction/RuleEngine/IRule.cs +++ b/JwtAuthenticationApi.Services/Abstraction/RuleEngine/IRule.cs @@ -1,4 +1,4 @@ -namespace JwtAuthenticationApi.Abstraction.RuleEngine +namespace JwtAuthenticationApi.Services.Abstraction.RuleEngine { /// /// Defines methods for rule in rule engine. diff --git a/JwtAuthenticationApi/Abstraction/RuleEngine/IRuleEngine.cs b/JwtAuthenticationApi.Services/Abstraction/RuleEngine/IRuleEngine.cs similarity index 87% rename from JwtAuthenticationApi/Abstraction/RuleEngine/IRuleEngine.cs rename to JwtAuthenticationApi.Services/Abstraction/RuleEngine/IRuleEngine.cs index ae06723..8e7e014 100644 --- a/JwtAuthenticationApi/Abstraction/RuleEngine/IRuleEngine.cs +++ b/JwtAuthenticationApi.Services/Abstraction/RuleEngine/IRuleEngine.cs @@ -1,4 +1,4 @@ -namespace JwtAuthenticationApi.Abstraction.RuleEngine +namespace JwtAuthenticationApi.Services.Abstraction.RuleEngine { /// diff --git a/JwtAuthenticationApi.Services/Abstraction/Validators/Password/IPasswordValidator.cs b/JwtAuthenticationApi.Services/Abstraction/Validators/Password/IPasswordValidator.cs new file mode 100644 index 0000000..0640f7e --- /dev/null +++ b/JwtAuthenticationApi.Services/Abstraction/Validators/Password/IPasswordValidator.cs @@ -0,0 +1,6 @@ +namespace JwtAuthenticationApi.Services.Abstraction.Validators.Password; + +public interface IPasswordValidator +{ + public bool Validate(string password, string passwordConfirmation); +} \ No newline at end of file diff --git a/JwtAuthenticationApi.Services/Commands/ConvertRequestToUserEntityCommand.cs b/JwtAuthenticationApi.Services/Commands/ConvertRequestToUserEntityCommand.cs new file mode 100644 index 0000000..8873a1d --- /dev/null +++ b/JwtAuthenticationApi.Services/Commands/ConvertRequestToUserEntityCommand.cs @@ -0,0 +1,40 @@ +namespace JwtAuthenticationApi.Services.Commands +{ + using JwtAuthenticationApi.Common.Abstraction.Commands; + using JwtAuthenticationApi.Common.Models; + using Infrastructure.Entities; + using JwtAuthenticationApi.Services.Models.Registration.Requests; + + /// + /// Command that is responsible for creating from request. Implements . + /// + internal class ConvertRequestToUserEntityCommand : ICommand + { + private readonly RegisterUserRequest _registerUserRequest; + private readonly string _hashedPassword; + + /// + /// Initializes a new with user request and hashed password. + /// + /// User registration request. + /// Hashed user password using SHA256 hashing algorithm. + public ConvertRequestToUserEntityCommand(RegisterUserRequest registerUserRequest, string hashedPassword) + { + _registerUserRequest = registerUserRequest; + _hashedPassword = hashedPassword; + } + + /// + /// Executes creating user entity from request. + /// + /// Cancellation token. + /// A task that represents the asynchronous creating of user entity operation. + /// The task result contains object that contains user entity. + public Task> ExecuteAsync(CancellationToken cancellationToken) + { + UserEntity userModel = new UserEntity(_registerUserRequest.Username, _hashedPassword, _registerUserRequest.Email); + Result result = new Result(userModel, true); + return Task.FromResult(result); + } + } +} diff --git a/JwtAuthenticationApi/Exceptions/PasswordValidationException.cs b/JwtAuthenticationApi.Services/Exceptions/PasswordValidationException.cs similarity index 88% rename from JwtAuthenticationApi/Exceptions/PasswordValidationException.cs rename to JwtAuthenticationApi.Services/Exceptions/PasswordValidationException.cs index 9ce8c8a..0ae44fb 100644 --- a/JwtAuthenticationApi/Exceptions/PasswordValidationException.cs +++ b/JwtAuthenticationApi.Services/Exceptions/PasswordValidationException.cs @@ -1,4 +1,4 @@ -namespace JwtAuthenticationApi.Exceptions +namespace JwtAuthenticationApi.Services.Exceptions { using System.Diagnostics.CodeAnalysis; @@ -6,7 +6,7 @@ /// Represents one or more errors that occurs during password validation process. /// [ExcludeFromCodeCoverage] - public class PasswordValidationException : Exception + internal class PasswordValidationException : Exception { /// /// Initializes new instance of class. diff --git a/JwtAuthenticationApi/Extensions/ListExtension.cs b/JwtAuthenticationApi.Services/Extensions/ListExtension.cs similarity index 86% rename from JwtAuthenticationApi/Extensions/ListExtension.cs rename to JwtAuthenticationApi.Services/Extensions/ListExtension.cs index dca515c..8eb9410 100644 --- a/JwtAuthenticationApi/Extensions/ListExtension.cs +++ b/JwtAuthenticationApi.Services/Extensions/ListExtension.cs @@ -1,11 +1,11 @@ -namespace JwtAuthenticationApi.Extensions +namespace JwtAuthenticationApi.Services.Extensions { using Abstraction.RuleEngine; /// /// Extension methods for List to create list of rules. /// - public static class ListExtension + internal static class ListExtension { /// /// Adds rules to using params. diff --git a/JwtAuthenticationApi/Factories/Password/PasswordContextFactory.cs b/JwtAuthenticationApi.Services/Factories/Password/PasswordContextFactory.cs similarity index 73% rename from JwtAuthenticationApi/Factories/Password/PasswordContextFactory.cs rename to JwtAuthenticationApi.Services/Factories/Password/PasswordContextFactory.cs index 37bac13..140899d 100644 --- a/JwtAuthenticationApi/Factories/Password/PasswordContextFactory.cs +++ b/JwtAuthenticationApi.Services/Factories/Password/PasswordContextFactory.cs @@ -1,12 +1,13 @@ -namespace JwtAuthenticationApi.Factories.Password +namespace JwtAuthenticationApi.Services.Factories.Password { - using Models.Password; - using Constants; + using Common.Constants; + using JwtAuthenticationApi.Services.Abstraction.Factories.Password; + using JwtAuthenticationApi.Services.Models.Password; /// /// Password context factory, responsible for creating . Implements /// - public sealed class PasswordContextFactory: IPasswordContextFactory + internal sealed class PasswordContextFactory: IPasswordContextFactory { public PasswordContext Create(string password, string passwordConfirmation) { diff --git a/JwtAuthenticationApi/Factories/Password/PasswordRuleFactory.cs b/JwtAuthenticationApi.Services/Factories/Password/PasswordRuleFactory.cs similarity index 65% rename from JwtAuthenticationApi/Factories/Password/PasswordRuleFactory.cs rename to JwtAuthenticationApi.Services/Factories/Password/PasswordRuleFactory.cs index 50e60c9..59cb390 100644 --- a/JwtAuthenticationApi/Factories/Password/PasswordRuleFactory.cs +++ b/JwtAuthenticationApi.Services/Factories/Password/PasswordRuleFactory.cs @@ -1,13 +1,14 @@ -namespace JwtAuthenticationApi.Factories.Password +namespace JwtAuthenticationApi.Services.Factories.Password { + using JwtAuthenticationApi.Services.Abstraction.Factories.Password; using Abstraction.RuleEngine; - using Models.Password; - using Validators.Password.Rules; + using JwtAuthenticationApi.Services.Validators.Password.Rules; + using JwtAuthenticationApi.Services.Models.Password; /// /// Password rule factory responsible for creating . Implements . /// - public sealed class PasswordRuleFactory: IPasswordRuleFactory + internal sealed class PasswordRuleFactory: IPasswordRuleFactory { public IRule CreateEqualityRule() => new EqualityRule(); public IRule CreateLengthRule() => new LengthRule(); diff --git a/JwtAuthenticationApi.Services/Identity/User/UserService.cs b/JwtAuthenticationApi.Services/Identity/User/UserService.cs new file mode 100644 index 0000000..7ee2dc1 --- /dev/null +++ b/JwtAuthenticationApi.Services/Identity/User/UserService.cs @@ -0,0 +1,57 @@ +namespace JwtAuthenticationApi.Services.Identity.User +{ + using Common.Abstraction.Factories.Polly; + using Infrastructure.Abstraction.Repository; + using Infrastructure.Entities; + using JwtAuthenticationApi.Services.Abstraction.Identity.User; + using Microsoft.EntityFrameworkCore; + using Polly; + using Polly.Retry; + using Serilog; + + /// + /// User service that is responsible for saving user in database. Implements . + /// + internal class UserService: IUserService + { + private readonly IUserRepository _userRepository; + private readonly IPollySleepingIntervalsFactory _pollySleepingIntervalsFactory; + private readonly ILogger _logger; + + /// + /// Returns instance of . + /// + /// User context + /// Polly sleeping intervals factory. + /// Logger. + public UserService(IUserRepository userRepository, IPollySleepingIntervalsFactory pollySleepingIntervalsFactory, ILogger logger) + { + _userRepository = userRepository; + _pollySleepingIntervalsFactory = pollySleepingIntervalsFactory; + _logger = logger; + } + + public async Task SaveUserAsync(UserEntity userEntity, CancellationToken cancellationToken) + { + AsyncRetryPolicy saveUserInDatabasePolicy = Policy + .Handle() + .WaitAndRetryAsync(_pollySleepingIntervalsFactory.CreateLinearInterval(4, 1, 3), + (exception, span) => _logger.Error($"Error occurred during execution of {nameof(SaveUserAsync)}. Attempting to retry in {span.Seconds} seconds. Error Message: {exception.Message}.")); + + _logger.Information("Attempting to save user in database"); + await saveUserInDatabasePolicy.ExecuteAsync(async () => await _userRepository.AddAsync(userEntity, cancellationToken)); + _logger.Information("User saved in database"); + return userEntity.Id; + } + + public Task UserExistsAsync(string userName) + { + return _userRepository.UserExistsAsync(userName); + } + + public Task UserExistsAsync(int userId) + { + return _userRepository.UserExistsAsync(userId); + } + } +} diff --git a/JwtAuthenticationApi.Services/JwtAuthenticationApi.Services.csproj b/JwtAuthenticationApi.Services/JwtAuthenticationApi.Services.csproj new file mode 100644 index 0000000..d27b099 --- /dev/null +++ b/JwtAuthenticationApi.Services/JwtAuthenticationApi.Services.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + disable + + + + + + + + + + + + + + <_Parameter1>JwtAuthenticationApi.Services.Tests + + + diff --git a/JwtAuthenticationApi/Models/Enums/ErrorType.cs b/JwtAuthenticationApi.Services/Models/Enums/ErrorType.cs similarity index 76% rename from JwtAuthenticationApi/Models/Enums/ErrorType.cs rename to JwtAuthenticationApi.Services/Models/Enums/ErrorType.cs index 489d204..c6c88b6 100644 --- a/JwtAuthenticationApi/Models/Enums/ErrorType.cs +++ b/JwtAuthenticationApi.Services/Models/Enums/ErrorType.cs @@ -1,4 +1,4 @@ -namespace JwtAuthenticationApi.Models.Enums; +namespace JwtAuthenticationApi.Services.Models.Enums; /// /// Error types that can occurs in services. diff --git a/JwtAuthenticationApi/Models/Password/PasswordContext.cs b/JwtAuthenticationApi.Services/Models/Password/PasswordContext.cs similarity index 94% rename from JwtAuthenticationApi/Models/Password/PasswordContext.cs rename to JwtAuthenticationApi.Services/Models/Password/PasswordContext.cs index 6f41648..173faff 100644 --- a/JwtAuthenticationApi/Models/Password/PasswordContext.cs +++ b/JwtAuthenticationApi.Services/Models/Password/PasswordContext.cs @@ -1,6 +1,7 @@ -namespace JwtAuthenticationApi.Models.Password +namespace JwtAuthenticationApi.Services.Models.Password { using System.Diagnostics.CodeAnalysis; + using Common.Constants; /// /// Password Context for rule engine. @@ -37,7 +38,7 @@ public sealed class PasswordContext : IEquatable /// Gets total special characters in password. /// /// - /// Checkout to see which counts as a special characters. + /// Checkout to see which counts as a special characters. /// public int TotalSpecialCharacters { get; init; } diff --git a/JwtAuthenticationApi/Models/Registration/Requests/RegisterUserRequest.cs b/JwtAuthenticationApi.Services/Models/Registration/Requests/RegisterUserRequest.cs similarity index 70% rename from JwtAuthenticationApi/Models/Registration/Requests/RegisterUserRequest.cs rename to JwtAuthenticationApi.Services/Models/Registration/Requests/RegisterUserRequest.cs index a026009..1e61c47 100644 --- a/JwtAuthenticationApi/Models/Registration/Requests/RegisterUserRequest.cs +++ b/JwtAuthenticationApi.Services/Models/Registration/Requests/RegisterUserRequest.cs @@ -1,12 +1,11 @@ -namespace JwtAuthenticationApi.Models.Registration.Requests -{ - using System.ComponentModel.DataAnnotations; - using Controllers; +using System.ComponentModel.DataAnnotations; - /// - /// Register user request. HTTP Request body in is resolved to this class. - /// - public class RegisterUserRequest +namespace JwtAuthenticationApi.Services.Models.Registration.Requests +{ + /// + /// Register user request. HTTP Request body in is resolved to this class. + /// + public class RegisterUserRequest { /// /// Username resolved from request. diff --git a/JwtAuthenticationApi.Services/Models/Registration/Responses/RegisterUserResponse.cs b/JwtAuthenticationApi.Services/Models/Registration/Responses/RegisterUserResponse.cs new file mode 100644 index 0000000..6dd1fd1 --- /dev/null +++ b/JwtAuthenticationApi.Services/Models/Registration/Responses/RegisterUserResponse.cs @@ -0,0 +1,30 @@ +namespace JwtAuthenticationApi.Services.Models.Registration.Responses +{ + using Enums; + + /// + /// User registration response. + /// + public class RegisterUserResponse + { + /// + /// User unique identifier. + /// + public int UserId { get; set; } + + /// + /// Describes if service execution was successful + /// + public bool IsSuccessful { get; set; } + + /// + /// Describes what kind of error occurred. Null if no errors occurred. + /// + public ErrorType ErrorType { get; set; } + + /// + /// Error message. Null if no errors occurred. + /// + public string ErrorMessage { get; set; } + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi/Registration/UserRegisterService.cs b/JwtAuthenticationApi.Services/Registration/UserRegisterService.cs similarity index 77% rename from JwtAuthenticationApi/Registration/UserRegisterService.cs rename to JwtAuthenticationApi.Services/Registration/UserRegisterService.cs index 764fccb..0d3298a 100644 --- a/JwtAuthenticationApi/Registration/UserRegisterService.cs +++ b/JwtAuthenticationApi.Services/Registration/UserRegisterService.cs @@ -1,32 +1,30 @@ -using JwtAuthenticationApi.Models.Enums; -using JwtAuthenticationApi.Models.Registration.Requests; -using JwtAuthenticationApi.Models.Registration.Responses; - -namespace JwtAuthenticationApi.Registration +namespace JwtAuthenticationApi.Services.Registration { - using Abstraction.Commands; - using Entities; - using Handlers; - using Identity.User; - using Security.Password; - using Security.Password.Salt; - using Validators.Password; - using Commands.Models; - using Factories.Commands; - using Microsoft.EntityFrameworkCore; - using ILogger = Serilog.ILogger; + using Abstraction.Identity.User; + using JwtAuthenticationApi.Common.Abstraction.Commands; + using JwtAuthenticationApi.Common.Abstraction.Handlers; + using JwtAuthenticationApi.Common.Models; + using Infrastructure.Entities; + using JwtAuthenticationApi.Security.Abstraction.Cryptography; + using JwtAuthenticationApi.Security.Abstraction.Salt; + using JwtAuthenticationApi.Services.Abstraction.Registration; + using JwtAuthenticationApi.Services.Abstraction.Validators.Password; + using Commands; + using JwtAuthenticationApi.Services.Models.Registration.Requests; + using JwtAuthenticationApi.Services.Models.Registration.Responses; + using Microsoft.EntityFrameworkCore; + using Models.Enums; + using Serilog; - /// - /// Service that is responsible for registering user. - /// - public class UserRegisterService : IUserRegisterService + /// + /// Service that is responsible for registering user. + /// + internal class UserRegisterService : IUserRegisterService { private readonly ISaltService _saltService; private readonly IPasswordValidator _passwordValidator; private readonly IPasswordHashingService _passwordHashingService; private readonly ICommandHandler _commandHandler; - - private readonly ICommandFactory _commandFactory; private readonly IUserService _userService; private readonly ILogger _logger; @@ -34,7 +32,6 @@ public UserRegisterService(ISaltService saltService, IPasswordValidator passwordValidator, IPasswordHashingService passwordHashingService, ICommandHandler commandHandler, - ICommandFactory commandFactory, IUserService userService, ILogger logger) { @@ -42,7 +39,6 @@ public UserRegisterService(ISaltService saltService, _passwordValidator = passwordValidator; _passwordHashingService = passwordHashingService; _commandHandler = commandHandler; - _commandFactory = commandFactory; _userService = userService; _logger = logger; } @@ -66,11 +62,9 @@ public async Task RegisterUserAsync(RegisterUserRequest re string salt = _saltService.GenerateSalt(); string hashedPassword = - await _passwordHashingService.HashPasswordAsync(registerUserRequest.Password, salt, - cancellationToken); - ICommand command = - _commandFactory.CreateConvertRequestToUserEntityCommand(registerUserRequest, hashedPassword); - Result userEntity = await _commandHandler.HandleAsync(command, cancellationToken); + await _passwordHashingService.HashPasswordAsync(registerUserRequest.Password, salt, cancellationToken).ConfigureAwait(false); + ICommand command = new ConvertRequestToUserEntityCommand(registerUserRequest, hashedPassword); + Result userEntity = await _commandHandler.HandleAsync(command, cancellationToken).ConfigureAwait(false); if (!userEntity.IsSuccessful) { return new RegisterUserResponse() @@ -82,12 +76,12 @@ await _passwordHashingService.HashPasswordAsync(registerUserRequest.Password, sa } // Save user model - userId = await _userService.SaveUserAsync(userEntity.Value, cancellationToken); + userId = await _userService.SaveUserAsync(userEntity.Value, cancellationToken).ConfigureAwait(false); // Save salt if (userId.HasValue) { - await _saltService.SaveSaltAsync(salt, userId.Value, cancellationToken); + await _saltService.SaveSaltAsync(salt, userId.Value, cancellationToken).ConfigureAwait(false); } else { @@ -101,7 +95,7 @@ await _passwordHashingService.HashPasswordAsync(registerUserRequest.Password, sa } catch (DbUpdateException exception) { - if (await _userService.UserExistsAsync(registerUserRequest.Username)) + if (await _userService.UserExistsAsync(registerUserRequest.Username).ConfigureAwait(false)) { _logger.Error($"User with username {registerUserRequest.Username} already exists in database"); return new RegisterUserResponse() diff --git a/JwtAuthenticationApi.Services/ServicesInstaller.cs b/JwtAuthenticationApi.Services/ServicesInstaller.cs new file mode 100644 index 0000000..89a1d49 --- /dev/null +++ b/JwtAuthenticationApi.Services/ServicesInstaller.cs @@ -0,0 +1,26 @@ +namespace JwtAuthenticationApi.Services +{ + using Abstraction.Factories.Password; + using Abstraction.Identity.User; + using Abstraction.Registration; + using Abstraction.RuleEngine; + using Abstraction.Validators.Password; + using Factories.Password; + using Identity.User; + using Microsoft.Extensions.DependencyInjection; + using Registration; + using Validators.Password; + + public static class ServicesInstaller + { + public static void InstallServices(this IServiceCollection serviceCollection) + { + serviceCollection.AddTransient(); + serviceCollection.AddTransient(typeof(IRuleEngine<>), typeof(RuleEngine<>)); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + } + } +} diff --git a/JwtAuthenticationApi/Validators/Password/PasswordValidator.cs b/JwtAuthenticationApi.Services/Validators/Password/PasswordValidator.cs similarity index 80% rename from JwtAuthenticationApi/Validators/Password/PasswordValidator.cs rename to JwtAuthenticationApi.Services/Validators/Password/PasswordValidator.cs index 938a167..30562d3 100644 --- a/JwtAuthenticationApi/Validators/Password/PasswordValidator.cs +++ b/JwtAuthenticationApi.Services/Validators/Password/PasswordValidator.cs @@ -1,13 +1,14 @@ -namespace JwtAuthenticationApi.Validators.Password +namespace JwtAuthenticationApi.Services.Validators.Password { - using Abstraction.RuleEngine; + using JwtAuthenticationApi.Services.Abstraction.Factories.Password; using Exceptions; + using JwtAuthenticationApi.Services.Models.Password; + using Abstraction.RuleEngine; + using JwtAuthenticationApi.Services.Abstraction.Validators.Password; using Extensions; - using Factories.Password; - using Models.Password; using ILogger = Serilog.ILogger; - public sealed class PasswordValidator: IPasswordValidator + internal sealed class PasswordValidator: IPasswordValidator { private const int TotalRules = 5; private readonly IPasswordContextFactory _passwordContextFactory; @@ -35,8 +36,7 @@ public bool Validate(string password, string passwordConfirmation) _passwordRuleFactory.CreateLengthRule(), _passwordRuleFactory.CreateLowerLettersRule(), _passwordRuleFactory.CreateUpperLettersRule(), - _passwordRuleFactory.CreateSpecialLetterRule() - ); + _passwordRuleFactory.CreateSpecialLetterRule()); try { _passwordRuleEngine.Validate(context, rules); diff --git a/JwtAuthenticationApi/Validators/Password/RuleEngine.cs b/JwtAuthenticationApi.Services/Validators/Password/RuleEngine.cs similarity index 69% rename from JwtAuthenticationApi/Validators/Password/RuleEngine.cs rename to JwtAuthenticationApi.Services/Validators/Password/RuleEngine.cs index 39191ff..9b8d439 100644 --- a/JwtAuthenticationApi/Validators/Password/RuleEngine.cs +++ b/JwtAuthenticationApi.Services/Validators/Password/RuleEngine.cs @@ -1,8 +1,8 @@ -namespace JwtAuthenticationApi.Validators.Password +namespace JwtAuthenticationApi.Services.Validators.Password { - using Abstraction.RuleEngine; + using Abstraction.RuleEngine; - public class RuleEngine : IRuleEngine + internal class RuleEngine : IRuleEngine { public void Validate(T context, IEnumerable> rules) { diff --git a/JwtAuthenticationApi/Validators/Password/Rules/EqualityRule.cs b/JwtAuthenticationApi.Services/Validators/Password/Rules/EqualityRule.cs similarity index 60% rename from JwtAuthenticationApi/Validators/Password/Rules/EqualityRule.cs rename to JwtAuthenticationApi.Services/Validators/Password/Rules/EqualityRule.cs index c759c31..3445957 100644 --- a/JwtAuthenticationApi/Validators/Password/Rules/EqualityRule.cs +++ b/JwtAuthenticationApi.Services/Validators/Password/Rules/EqualityRule.cs @@ -1,10 +1,10 @@ -namespace JwtAuthenticationApi.Validators.Password.Rules +namespace JwtAuthenticationApi.Services.Validators.Password.Rules { - using Abstraction.RuleEngine; - using Exceptions; - using Models.Password; + using JwtAuthenticationApi.Services.Models.Password; + using Abstraction.RuleEngine; + using Exceptions; - public class EqualityRule : IRule + internal class EqualityRule : IRule { public void Evaluate(PasswordContext context) { diff --git a/JwtAuthenticationApi/Validators/Password/Rules/LengthRule.cs b/JwtAuthenticationApi.Services/Validators/Password/Rules/LengthRule.cs similarity index 61% rename from JwtAuthenticationApi/Validators/Password/Rules/LengthRule.cs rename to JwtAuthenticationApi.Services/Validators/Password/Rules/LengthRule.cs index 1660500..c076ec0 100644 --- a/JwtAuthenticationApi/Validators/Password/Rules/LengthRule.cs +++ b/JwtAuthenticationApi.Services/Validators/Password/Rules/LengthRule.cs @@ -1,11 +1,11 @@ -namespace JwtAuthenticationApi.Validators.Password.Rules +namespace JwtAuthenticationApi.Services.Validators.Password.Rules { - using Abstraction.RuleEngine; - using Exceptions; - using Constants; - using Models.Password; + using JwtAuthenticationApi.Services.Models.Password; + using Abstraction.RuleEngine; + using Exceptions; + using Common.Constants; - public sealed class LengthRule : IRule + internal sealed class LengthRule : IRule { public void Evaluate(PasswordContext context) { diff --git a/JwtAuthenticationApi/Validators/Password/Rules/LowerLettersRule.cs b/JwtAuthenticationApi.Services/Validators/Password/Rules/LowerLettersRule.cs similarity index 60% rename from JwtAuthenticationApi/Validators/Password/Rules/LowerLettersRule.cs rename to JwtAuthenticationApi.Services/Validators/Password/Rules/LowerLettersRule.cs index 56a6a68..8365cea 100644 --- a/JwtAuthenticationApi/Validators/Password/Rules/LowerLettersRule.cs +++ b/JwtAuthenticationApi.Services/Validators/Password/Rules/LowerLettersRule.cs @@ -1,11 +1,11 @@ -namespace JwtAuthenticationApi.Validators.Password.Rules +namespace JwtAuthenticationApi.Services.Validators.Password.Rules { - using Abstraction.RuleEngine; - using Constants; - using Exceptions; - using Models.Password; + using Common.Constants; + using Abstraction.RuleEngine; + using Exceptions; + using JwtAuthenticationApi.Services.Models.Password; - public class LowerLettersRule : IRule + internal class LowerLettersRule : IRule { public void Evaluate(PasswordContext context) { diff --git a/JwtAuthenticationApi/Validators/Password/Rules/SpecialLettersRule.cs b/JwtAuthenticationApi.Services/Validators/Password/Rules/SpecialLettersRule.cs similarity index 60% rename from JwtAuthenticationApi/Validators/Password/Rules/SpecialLettersRule.cs rename to JwtAuthenticationApi.Services/Validators/Password/Rules/SpecialLettersRule.cs index 5775618..49e9814 100644 --- a/JwtAuthenticationApi/Validators/Password/Rules/SpecialLettersRule.cs +++ b/JwtAuthenticationApi.Services/Validators/Password/Rules/SpecialLettersRule.cs @@ -1,11 +1,11 @@ -namespace JwtAuthenticationApi.Validators.Password.Rules +namespace JwtAuthenticationApi.Services.Validators.Password.Rules { - using Abstraction.RuleEngine; - using Constants; - using Exceptions; - using Models.Password; + using Common.Constants; + using Abstraction.RuleEngine; + using Exceptions; + using JwtAuthenticationApi.Services.Models.Password; - public class SpecialLettersRule : IRule + internal class SpecialLettersRule : IRule { public void Evaluate(PasswordContext context) { diff --git a/JwtAuthenticationApi/Validators/Password/Rules/UpperLettersRule.cs b/JwtAuthenticationApi.Services/Validators/Password/Rules/UpperLettersRule.cs similarity index 60% rename from JwtAuthenticationApi/Validators/Password/Rules/UpperLettersRule.cs rename to JwtAuthenticationApi.Services/Validators/Password/Rules/UpperLettersRule.cs index 2d460fd..96f729d 100644 --- a/JwtAuthenticationApi/Validators/Password/Rules/UpperLettersRule.cs +++ b/JwtAuthenticationApi.Services/Validators/Password/Rules/UpperLettersRule.cs @@ -1,12 +1,11 @@ -namespace JwtAuthenticationApi.Validators.Password.Rules +namespace JwtAuthenticationApi.Services.Validators.Password.Rules { - using Exceptions; - using Models.Password; - using Abstraction.RuleEngine; - using Constants; + using Common.Constants; + using Abstraction.RuleEngine; + using Exceptions; + using JwtAuthenticationApi.Services.Models.Password; - - public class UpperLettersRule : IRule + internal class UpperLettersRule : IRule { public void Evaluate(PasswordContext context) { diff --git a/JwtAuthenticationApi.UnitTests/Factories/Commands/CommandsFactoryTests.cs b/JwtAuthenticationApi.UnitTests/Factories/Commands/CommandsFactoryTests.cs deleted file mode 100644 index f7a9a84..0000000 --- a/JwtAuthenticationApi.UnitTests/Factories/Commands/CommandsFactoryTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using JwtAuthenticationApi.Entities; - -namespace JwtAuthenticationApi.UnitTests.Factories.Commands -{ - using JwtAuthenticationApi.Abstraction.Commands; - using JwtAuthenticationApi.Commands; - using Models; - using JwtAuthenticationApi.Factories.Commands; - - [TestFixture, Parallelizable] - public class CommandsFactoryTests - { - private CommandFactory _uut; - - [SetUp] - public void SetUp() - { - _uut = new CommandFactory(); - } - - [Test] - public void ShouldCreatePasswordMixCommand() - { - // Act - var actual = _uut.CreatePasswordMixCommand(null, null, null); - - // Assert - actual.Should().NotBeNull(); - actual.Should().BeOfType(); - actual.Should().BeAssignableTo>(); - } - - [Test] - public void ShouldCreateUserModelFromRequestCommand() - { - // Act - var actual = _uut.CreateConvertRequestToUserEntityCommand(null, null); - - // Assert - actual.Should().NotBeNull(); - actual.Should().BeOfType(); - actual.Should().BeAssignableTo>(); - } - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi.UnitTests/Identity/User/UserServiceTests.cs b/JwtAuthenticationApi.UnitTests/Identity/User/UserServiceTests.cs deleted file mode 100644 index 098c3a5..0000000 --- a/JwtAuthenticationApi.UnitTests/Identity/User/UserServiceTests.cs +++ /dev/null @@ -1,160 +0,0 @@ -namespace JwtAuthenticationApi.UnitTests.Identity.User -{ - using DatabaseContext; - using JwtAuthenticationApi.Factories.Polly; - using JwtAuthenticationApi.Identity.User; - using Serilog; - using Entities; - using Microsoft.EntityFrameworkCore; - using TddXt.AnyRoot.Numbers; - using static TddXt.AnyRoot.Root; - using MockQueryable.NSubstitute; - using JwtAuthenticationApi.Abstraction.DatabaseContext; - - [TestFixture, Parallelizable] - public sealed class UserServiceTests - { - private IUserContext _userContext; - private IPollySleepingIntervalsFactory _pollySleepingIntervalsFactory; - private ILogger _logger; - private UserService _sut; - - [SetUp] - public void SetUp() - { - _userContext = Substitute.For(); - _pollySleepingIntervalsFactory = Substitute.For(); - _logger = Substitute.For(); - _sut = new UserService(_userContext, _pollySleepingIntervalsFactory, _logger); - } - - [Test] - public async Task ShouldSaveUserInDatabase() - { - // Arrange - UserEntity userEntity = Any.Instance(); - var userDbSet = new List().AsQueryable().BuildMockDbSet(); - _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(new List(1) - { - TimeSpan.Zero - }); - _userContext.Users.Returns(userDbSet); - _userContext.SaveChangesAsync(CancellationToken.None).Returns(1); - - // Act - int? acutal = await _sut.SaveUserAsync(userEntity, CancellationToken.None); - - // Assert - acutal.HasValue.Should().BeTrue(); - await _userContext.Received(1).SaveChangesAsync(CancellationToken.None); - } - - [Test] - public async Task ShouldRetrySavingUserIfDbUpdateExceptionOccurs() - { - // Arrange - UserEntity userEntity = Any.Instance(); - _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(new List(1) - { - TimeSpan.Zero, - TimeSpan.Zero - }); - _userContext.SaveChangesAsync(CancellationToken.None).Returns(_ => throw new DbUpdateException(), _ => 1); - - // Act - var acutal = await _sut.SaveUserAsync(userEntity, CancellationToken.None); - - // Assert - acutal.HasValue.Should().BeTrue(); - await _userContext.Received(2).SaveChangesAsync(CancellationToken.None); - } - - [Test] - public async Task ShouldThrowDbUpdateExceptionIfExceptionIsReceivedAfterMaxRetryCount() - { - // Arrange - int id = Any.Integer(); - UserEntity userEntity = Any.Instance(); - _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(new List(1) - { - TimeSpan.Zero, - TimeSpan.Zero, - TimeSpan.Zero - }); - _userContext.SaveChangesAsync(CancellationToken.None).Returns(_ => throw new DbUpdateException(), - _ => throw new DbUpdateException(), - _ => throw new DbUpdateException(), - _ => throw new DbUpdateException(), - _ => id); - Func> func = async () => await _sut.SaveUserAsync(userEntity, CancellationToken.None); - - // Act & Assert - await func.Should().ThrowAsync(); - await _userContext.Received(4).SaveChangesAsync(CancellationToken.None); - } - - [Test] - public async Task ShouldReturnTrueIfUserExistsBasedOnUserName() - { - // Arrange - UserEntity userEntity = Any.Instance(); - var userEntitiesDbSet = new List(1){userEntity}.AsQueryable().BuildMockDbSet(); - _userContext.Users.Returns(userEntitiesDbSet); - - // Act - var actual = await _sut.UserExistsAsync(userEntity.Username); - - // Assert - actual.Should().BeTrue(); - } - - [Test] - public async Task ShouldReturnFalseIfUserDoesNotExistsBasedOnUserName() - { - // Arrange - UserEntity userEntity = Any.Instance(); - var userEntitiesDbSet = new List(1) { userEntity }.AsQueryable().BuildMockDbSet(); - _userContext.Users.Returns(userEntitiesDbSet); - - // Act - var actual = await _sut.UserExistsAsync(string.Empty); - - // Assert - actual.Should().BeFalse(); - } - - [Test] - public async Task ShouldReturnTrueIfUserExistsBasedOnUserId() - { - // Arrange - UserEntity userEntity = Any.Instance(); - var userEntitiesDbSet = new List(1) { userEntity }.AsQueryable().BuildMockDbSet(); - _userContext.Users.Returns(userEntitiesDbSet); - - // Act - var actual = await _sut.UserExistsAsync(userEntity.Id); - - // Assert - actual.Should().BeTrue(); - } - - [Test] - public async Task ShouldReturnFalseIfUserDoesNotExistsBasedOnUserID() - { - // Arrange - UserEntity userEntity = Any.Instance(); - var userEntitiesDbSet = new List(1) { userEntity }.AsQueryable().BuildMockDbSet(); - _userContext.Users.Returns(userEntitiesDbSet); - - // Act - var actual = await _sut.UserExistsAsync(-1); - - // Assert - actual.Should().BeFalse(); - } - - } -} diff --git a/JwtAuthenticationApi.UnitTests/JwtAuthenticationApi.UnitTests.csproj b/JwtAuthenticationApi.UnitTests/JwtAuthenticationApi.UnitTests.csproj deleted file mode 100644 index da82dd7..0000000 --- a/JwtAuthenticationApi.UnitTests/JwtAuthenticationApi.UnitTests.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - net8.0 - enable - disable - false - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - \ No newline at end of file diff --git a/JwtAuthenticationApi.UnitTests/Security/Password/Salt/SaltServiceTests.cs b/JwtAuthenticationApi.UnitTests/Security/Password/Salt/SaltServiceTests.cs deleted file mode 100644 index 0cc1fdf..0000000 --- a/JwtAuthenticationApi.UnitTests/Security/Password/Salt/SaltServiceTests.cs +++ /dev/null @@ -1,244 +0,0 @@ -using JwtAuthenticationApi.Entities; -using TddXt.AnyRoot.Numbers; - -namespace JwtAuthenticationApi.UnitTests.Security.Password.Salt -{ - using JwtAuthenticationApi.Factories.Polly; - using Serilog; - using JwtAuthenticationApi.Commands.Models; - using DatabaseContext; - using JwtAuthenticationApi.Security.Password.Salt; - using JwtAuthenticationApi.Wrappers; - using Microsoft.EntityFrameworkCore; - using MockQueryable.NSubstitute; - using TddXt.AnyRoot.Strings; - using JwtAuthenticationApi.Factories.Wrappers; - using JwtAuthenticationApi.Wrappers.Threading; - using NSubstitute.ExceptionExtensions; - using static TddXt.AnyRoot.Root; - using JwtAuthenticationApi.Abstraction.DatabaseContext; - - [TestFixture, Parallelizable] - public class SaltServiceTests - { - private IPasswordSaltContext _passwordSaltContext; - private SaltService _uut; - private IGuidWrapper _guidWrapper; - private IPollySleepingIntervalsFactory _pollySleepingIntervalsFactory; - private ILogger _logger; - private ISemaphoreWrapper _semaphoreWrapper; - private ISemaphoreWrapperFactory _semaphoreWrapperFactory; - - [SetUp] - public void SetUp() - { - _passwordSaltContext = Substitute.For(); - _guidWrapper = Substitute.For(); - _semaphoreWrapper = Substitute.For(); - _semaphoreWrapperFactory = Substitute.For(); - _semaphoreWrapperFactory.Create(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(_semaphoreWrapper); - _pollySleepingIntervalsFactory = Substitute.For(); - _logger = Substitute.For(); - _uut = new SaltService(_passwordSaltContext, _guidWrapper, - _pollySleepingIntervalsFactory,_semaphoreWrapperFactory, _logger); - } - - [Test] - public async Task ShouldCreateAndSaveNewSalt() - { - // Arrange - int userId = Any.Integer(); - Guid salt = Guid.NewGuid(); - IEnumerable saltSource = Enumerable.Empty(); - DbSet saltDbSet = saltSource.AsQueryable().BuildMockDbSet(); - _guidWrapper.CreateGuid().Returns(salt); - _passwordSaltContext.PasswordSalt.Returns(saltDbSet); - - // Act - await _uut.SaveSaltAsync(salt.ToString(), userId); - - // Assert - await _passwordSaltContext.Received(1).SaveChangesAsync(Arg.Any()); - } - - [Test] - public async Task ShouldReturnNotSuccessfulResultIfUserDoesNotExists() - { - // Arrange - int userId = Any.Integer(); - IEnumerable saltSource = Enumerable.Empty(); - DbSet saltDbSet = saltSource.AsQueryable().BuildMockDbSet(); - _passwordSaltContext.PasswordSalt.Returns(saltDbSet); - - // Act - Result actual = await _uut.GetSaltAsync(userId); - - // Assert - actual.IsSuccessful.Should().BeFalse(); - actual.Value.Should().BeNull(); - } - - [Test] - public async Task ShouldRetryGettingSaltIfExceptionOccurs() - { - // Arrange - int userId = Any.Integer(); - string salt = Any.String(); - PasswordSaltEntity passwordSalt = new PasswordSaltEntity(salt, userId); - IEnumerable saltSource = new List(1) { passwordSalt }; - DbSet saltDbSet = saltSource.AsQueryable().BuildMockDbSet(); - _passwordSaltContext.PasswordSalt.Returns(_ => throw new Exception(), _ => saltDbSet); - _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(new List(2) { TimeSpan.Zero, TimeSpan.Zero }); - // Act - var actual = await _uut.GetSaltAsync(userId); - - // Assert - actual.IsSuccessful.Should().BeTrue(); - actual.Value.Should().Be(salt); - _passwordSaltContext.PasswordSalt.ReceivedWithAnyArgs(2); - _semaphoreWrapper.Received(1).Release(); - } - - [Test] - public async Task ShouldReturnNotSuccessfulResultIfGettingSaltThrowsExceptionAfterRetries() - { - // Arrange - int userId = Any.Integer(); - string salt = Any.String(); - PasswordSaltEntity passwordSalt = new PasswordSaltEntity(salt, userId); - IEnumerable saltSource = new List(1) { passwordSalt }; - DbSet saltDbSet = saltSource.AsQueryable().BuildMockDbSet(); - _passwordSaltContext.PasswordSalt.Returns(x => throw new Exception(), x => throw new Exception(), x => throw new Exception(),x => saltDbSet); - _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(new List(2) { TimeSpan.Zero, TimeSpan.Zero }); - - // Act - var actual = await _uut.GetSaltAsync(userId); - - // Assert - actual.IsSuccessful.Should().BeFalse(); - actual.Value.Should().Be(null); - _passwordSaltContext.PasswordSalt.ReceivedWithAnyArgs(3); - _semaphoreWrapper.Received(1).Release(); - } - - [Test] - public async Task ShouldReturnSuccessfulResultWithSaltIfUserExists() - { - // Arrange - int userId = Any.Integer(); - string salt = Any.String(); - PasswordSaltEntity passwordSalt = new PasswordSaltEntity(salt, userId); - IEnumerable saltSource = new List(1) { passwordSalt }; - DbSet saltDbSet = saltSource.AsQueryable().BuildMockDbSet(); - _passwordSaltContext.PasswordSalt.Returns(saltDbSet); - - // Act - var actual = await _uut.GetSaltAsync(userId); - - // Assert - actual.IsSuccessful.Should().BeTrue(); - actual.Value.Should().Be(salt); - } - - [Test] - public async Task ShouldReleaseSemaphoreAfterDatabaseExceptionOccursInSavingSaltMethod() - { - // Arrange - int userId = Any.Integer(); - Guid salt = Guid.NewGuid(); - IEnumerable saltSource = Enumerable.Empty(); - DbSet saltDbSet = saltSource.AsQueryable().BuildMockDbSet(); - _guidWrapper.CreateGuid().Returns(salt); - _passwordSaltContext.PasswordSalt.Returns(saltDbSet); - _passwordSaltContext.SaveChangesAsync(Arg.Any()) - .ThrowsAsync(new DbUpdateException()); - - Func> func = async () => await _uut.SaveSaltAsync(salt.ToString(), userId); - - // Act & Assert - await func.Should().ThrowAsync(); - _semaphoreWrapper.Received(1).Release(); - } - - [Test] - public async Task ShouldReleaseSemaphoreAfterExceptionOccursInGettingSaltMethod() - { - // Arrange - int userId = Any.Integer(); - string salt = Any.String(); - PasswordSaltEntity passwordSalt = new PasswordSaltEntity(salt, userId); - IEnumerable saltSource = new List(1) { passwordSalt }; - DbSet saltDbSet = saltSource.AsQueryable().BuildMockDbSet(); - _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) - .Throws(); - _passwordSaltContext.PasswordSalt.Returns(saltDbSet); - - await _uut.GetSaltAsync(userId); - - // Act & Assert - _logger.Received(1).Error(Arg.Any(), Arg.Any()); - _semaphoreWrapper.Received(1).Release(); - } - - [Test] - public async Task ShouldRetrySavingChangesIfExceptionOccurs() - { - // Arrange - int userId = Any.Integer(); - Guid salt = Guid.NewGuid(); - IEnumerable saltSource = Enumerable.Empty(); - DbSet saltDbSet = saltSource.AsQueryable().BuildMockDbSet(); - _guidWrapper.CreateGuid().Returns(salt); - _passwordSaltContext.PasswordSalt.Returns(saltDbSet); - _passwordSaltContext.SaveChangesAsync(Arg.Any()) - .Returns(x => throw new DbUpdateException(), x => throw new DbUpdateException(), x => 1); - _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(new List(2) { TimeSpan.Zero, TimeSpan.Zero }); - - // Act - await _uut.SaveSaltAsync(salt.ToString(), userId); - - // Assert - await _passwordSaltContext.ReceivedWithAnyArgs(3).SaveChangesAsync(Arg.Any()); - _semaphoreWrapper.Received(1).Release(); - } - - [Test] - public async Task ShouldThrowExceptionIfExceptionStillOccursAfterRetries() - { - // Arrange - int userId = Any.Integer(); - Guid salt = Guid.NewGuid(); - IEnumerable saltSource = Enumerable.Empty(); - DbSet saltDbSet = saltSource.AsQueryable().BuildMockDbSet(); - _guidWrapper.CreateGuid().Returns(salt); - _passwordSaltContext.PasswordSalt.Returns(saltDbSet); - _passwordSaltContext.SaveChangesAsync(Arg.Any()) - .Returns(x => throw new DbUpdateException(), x => throw new DbUpdateException(), - x => throw new DbUpdateException(), x => 1); - _pollySleepingIntervalsFactory.CreateLinearInterval(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(new List(2) { TimeSpan.Zero, TimeSpan.Zero }); - - Func> function = async () => await _uut.SaveSaltAsync(salt.ToString(),userId); - - // Act & Assert - await function.Should().ThrowAsync(); - await _passwordSaltContext.ReceivedWithAnyArgs(3).SaveChangesAsync(Arg.Any()); - _semaphoreWrapper.Received(1).Release(); - } - - [Test] - public void ShouldGenerateSalt() - { - // Act - var actual = _uut.GenerateSalt(); - - // Assert - actual.Should().NotBeNull(); - actual.Length.Should().BeGreaterThan(0); - } - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi.UnitTests/TestHelpers/Attributes/FactoryTestAttribute.cs b/JwtAuthenticationApi.UnitTests/TestHelpers/Attributes/FactoryTestAttribute.cs deleted file mode 100644 index d33ac3d..0000000 --- a/JwtAuthenticationApi.UnitTests/TestHelpers/Attributes/FactoryTestAttribute.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace JwtAuthenticationApi.UnitTests.TestHelpers.Attributes -{ - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] - internal sealed class FactoryTestAttribute: CategoryAttribute - { - } -} diff --git a/JwtAuthenticationApi.UnitTests/TestHelpers/Attributes/RuleTestAttribute.cs b/JwtAuthenticationApi.UnitTests/TestHelpers/Attributes/RuleTestAttribute.cs deleted file mode 100644 index cee89b7..0000000 --- a/JwtAuthenticationApi.UnitTests/TestHelpers/Attributes/RuleTestAttribute.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace JwtAuthenticationApi.UnitTests.TestHelpers.Attributes -{ - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] - public sealed class RuleTestAttribute: CategoryAttribute - { - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi.UnitTests/Usings/Usings.cs b/JwtAuthenticationApi.UnitTests/Usings/Usings.cs deleted file mode 100644 index 962aff4..0000000 --- a/JwtAuthenticationApi.UnitTests/Usings/Usings.cs +++ /dev/null @@ -1,3 +0,0 @@ -global using NUnit.Framework; -global using NSubstitute; -global using FluentAssertions; \ No newline at end of file diff --git a/JwtAuthenticationApi.UnitTests/Wrappers/GuidWrapperTests.cs b/JwtAuthenticationApi.UnitTests/Wrappers/GuidWrapperTests.cs deleted file mode 100644 index c2f7461..0000000 --- a/JwtAuthenticationApi.UnitTests/Wrappers/GuidWrapperTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace JwtAuthenticationApi.UnitTests.Wrappers -{ - using JwtAuthenticationApi.Wrappers; - - [TestFixture, Parallelizable] - public class GuidWrapperTests - { - private GuidWrapper _uut; - - [SetUp] - public void SetUp() - { - _uut = new GuidWrapper(); - } - - [Test] - public void ShouldCreateGuid() - { - // Act - Guid actual = _uut.CreateGuid(); - - // Assert - actual.Should().NotBeEmpty(); - } - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi.sln b/JwtAuthenticationApi.sln index 9402509..1dc9e53 100644 --- a/JwtAuthenticationApi.sln +++ b/JwtAuthenticationApi.sln @@ -1,11 +1,32 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.33213.308 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthenticationApi", "JwtAuthenticationApi\JwtAuthenticationApi.csproj", "{1C6EEF71-D66B-416F-A402-D94C5F0F11D8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthenticationApi.UnitTests", "JwtAuthenticationApi.UnitTests\JwtAuthenticationApi.UnitTests.csproj", "{D7377D72-C271-4928-8102-4F7C2C49CA2D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthenticationApi.Infrastructure", "JwtAuthenticationApi.Infrastructure\JwtAuthenticationApi.Infrastructure.csproj", "{69E991F4-51A4-4DA1-AAD6-A8D50D7FF873}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{2A735D45-97A8-46FE-A12D-83FCE55A77C8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceCode", "SourceCode", "{4E806FA8-E219-42BC-8191-1C237DCA977F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthenticationApi.Services", "JwtAuthenticationApi.Services\JwtAuthenticationApi.Services.csproj", "{2DECB90A-2121-4589-ABC9-D097D51F0E5F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthenticationApi.Controllers", "JwtAuthenticationApi.Controllers\JwtAuthenticationApi.Controllers.csproj", "{E829EEA0-358F-4A49-B311-614AEBF88116}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthenticationApi.Security", "JwtAuthenticationApi.Security\JwtAuthenticationApi.Security.csproj", "{59379F0B-8C84-436B-BB2D-BB600ED44EC4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthenticationApi.Common", "JwtAuthenticationApi.Common\JwtAuthenticationApi.Common.csproj", "{8D7E0BF8-E7C0-4C52-B72D-E593AEC4AD52}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthenticationApi.Common.Tests", "JwtAuthenticationApi.Common.Tests\JwtAuthenticationApi.Common.Tests.csproj", "{0913B478-E66F-42F5-9FD1-B295D7CBE55B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthenticationApi.Infrastructure.Tests", "JwtAuthenticationApi.Infrastructure.Tests\JwtAuthenticationApi.Infrastructure.Tests.csproj", "{9D671041-2F4D-4135-AB52-F0CCE9398A61}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthenticationApi.Security.Tests", "JwtAuthenticationApi.Security.Tests\JwtAuthenticationApi.Security.Tests.csproj", "{735C061E-AA63-44D8-8FE3-711D843D1628}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthenticationApi.Services.Tests", "JwtAuthenticationApi.Services.Tests\JwtAuthenticationApi.Services.Tests.csproj", "{2FDB8BB3-9126-4B54-933B-CAF36740CB9A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JwtAuthenticationApi.Controllers.Tests", "JwtAuthenticationApi.Controllers.Tests\JwtAuthenticationApi.Controllers.Tests.csproj", "{B49736C9-85D4-42D3-9FA7-1946259D1931}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -17,14 +38,63 @@ Global {1C6EEF71-D66B-416F-A402-D94C5F0F11D8}.Debug|Any CPU.Build.0 = Debug|Any CPU {1C6EEF71-D66B-416F-A402-D94C5F0F11D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {1C6EEF71-D66B-416F-A402-D94C5F0F11D8}.Release|Any CPU.Build.0 = Release|Any CPU - {D7377D72-C271-4928-8102-4F7C2C49CA2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D7377D72-C271-4928-8102-4F7C2C49CA2D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D7377D72-C271-4928-8102-4F7C2C49CA2D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D7377D72-C271-4928-8102-4F7C2C49CA2D}.Release|Any CPU.Build.0 = Release|Any CPU + {69E991F4-51A4-4DA1-AAD6-A8D50D7FF873}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69E991F4-51A4-4DA1-AAD6-A8D50D7FF873}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69E991F4-51A4-4DA1-AAD6-A8D50D7FF873}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69E991F4-51A4-4DA1-AAD6-A8D50D7FF873}.Release|Any CPU.Build.0 = Release|Any CPU + {2DECB90A-2121-4589-ABC9-D097D51F0E5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DECB90A-2121-4589-ABC9-D097D51F0E5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DECB90A-2121-4589-ABC9-D097D51F0E5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DECB90A-2121-4589-ABC9-D097D51F0E5F}.Release|Any CPU.Build.0 = Release|Any CPU + {E829EEA0-358F-4A49-B311-614AEBF88116}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E829EEA0-358F-4A49-B311-614AEBF88116}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E829EEA0-358F-4A49-B311-614AEBF88116}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E829EEA0-358F-4A49-B311-614AEBF88116}.Release|Any CPU.Build.0 = Release|Any CPU + {59379F0B-8C84-436B-BB2D-BB600ED44EC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59379F0B-8C84-436B-BB2D-BB600ED44EC4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59379F0B-8C84-436B-BB2D-BB600ED44EC4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59379F0B-8C84-436B-BB2D-BB600ED44EC4}.Release|Any CPU.Build.0 = Release|Any CPU + {8D7E0BF8-E7C0-4C52-B72D-E593AEC4AD52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D7E0BF8-E7C0-4C52-B72D-E593AEC4AD52}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D7E0BF8-E7C0-4C52-B72D-E593AEC4AD52}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D7E0BF8-E7C0-4C52-B72D-E593AEC4AD52}.Release|Any CPU.Build.0 = Release|Any CPU + {0913B478-E66F-42F5-9FD1-B295D7CBE55B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0913B478-E66F-42F5-9FD1-B295D7CBE55B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0913B478-E66F-42F5-9FD1-B295D7CBE55B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0913B478-E66F-42F5-9FD1-B295D7CBE55B}.Release|Any CPU.Build.0 = Release|Any CPU + {9D671041-2F4D-4135-AB52-F0CCE9398A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D671041-2F4D-4135-AB52-F0CCE9398A61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D671041-2F4D-4135-AB52-F0CCE9398A61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D671041-2F4D-4135-AB52-F0CCE9398A61}.Release|Any CPU.Build.0 = Release|Any CPU + {735C061E-AA63-44D8-8FE3-711D843D1628}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {735C061E-AA63-44D8-8FE3-711D843D1628}.Debug|Any CPU.Build.0 = Debug|Any CPU + {735C061E-AA63-44D8-8FE3-711D843D1628}.Release|Any CPU.ActiveCfg = Release|Any CPU + {735C061E-AA63-44D8-8FE3-711D843D1628}.Release|Any CPU.Build.0 = Release|Any CPU + {2FDB8BB3-9126-4B54-933B-CAF36740CB9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2FDB8BB3-9126-4B54-933B-CAF36740CB9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2FDB8BB3-9126-4B54-933B-CAF36740CB9A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2FDB8BB3-9126-4B54-933B-CAF36740CB9A}.Release|Any CPU.Build.0 = Release|Any CPU + {B49736C9-85D4-42D3-9FA7-1946259D1931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B49736C9-85D4-42D3-9FA7-1946259D1931}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B49736C9-85D4-42D3-9FA7-1946259D1931}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B49736C9-85D4-42D3-9FA7-1946259D1931}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {1C6EEF71-D66B-416F-A402-D94C5F0F11D8} = {4E806FA8-E219-42BC-8191-1C237DCA977F} + {69E991F4-51A4-4DA1-AAD6-A8D50D7FF873} = {4E806FA8-E219-42BC-8191-1C237DCA977F} + {2DECB90A-2121-4589-ABC9-D097D51F0E5F} = {4E806FA8-E219-42BC-8191-1C237DCA977F} + {E829EEA0-358F-4A49-B311-614AEBF88116} = {4E806FA8-E219-42BC-8191-1C237DCA977F} + {59379F0B-8C84-436B-BB2D-BB600ED44EC4} = {4E806FA8-E219-42BC-8191-1C237DCA977F} + {8D7E0BF8-E7C0-4C52-B72D-E593AEC4AD52} = {4E806FA8-E219-42BC-8191-1C237DCA977F} + {0913B478-E66F-42F5-9FD1-B295D7CBE55B} = {2A735D45-97A8-46FE-A12D-83FCE55A77C8} + {9D671041-2F4D-4135-AB52-F0CCE9398A61} = {2A735D45-97A8-46FE-A12D-83FCE55A77C8} + {735C061E-AA63-44D8-8FE3-711D843D1628} = {2A735D45-97A8-46FE-A12D-83FCE55A77C8} + {2FDB8BB3-9126-4B54-933B-CAF36740CB9A} = {2A735D45-97A8-46FE-A12D-83FCE55A77C8} + {B49736C9-85D4-42D3-9FA7-1946259D1931} = {2A735D45-97A8-46FE-A12D-83FCE55A77C8} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4FBE2AA5-1DC0-47F0-9864-6D4DF9C2EFDD} EndGlobalSection diff --git a/JwtAuthenticationApi/Abstraction/DatabaseContext/IPasswordSaltContext.cs b/JwtAuthenticationApi/Abstraction/DatabaseContext/IPasswordSaltContext.cs deleted file mode 100644 index c9bb5d7..0000000 --- a/JwtAuthenticationApi/Abstraction/DatabaseContext/IPasswordSaltContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace JwtAuthenticationApi.Abstraction.DatabaseContext -{ - using Microsoft.EntityFrameworkCore; - using Entities; - - /// - /// Defines properties for password salt database context. - /// - public interface IPasswordSaltContext : IContext - { - /// - /// Gets password salt database set. - /// - DbSet PasswordSalt { get; } - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Abstraction/DatabaseContext/IUserContext.cs b/JwtAuthenticationApi/Abstraction/DatabaseContext/IUserContext.cs deleted file mode 100644 index b4ccedb..0000000 --- a/JwtAuthenticationApi/Abstraction/DatabaseContext/IUserContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace JwtAuthenticationApi.Abstraction.DatabaseContext -{ - using Microsoft.EntityFrameworkCore; - using Entities; - - /// - /// Defines properties for user database context. - /// - public interface IUserContext : IContext - { - /// - /// Gets users database set. - /// - DbSet Users { get; } - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Abstraction/Models/ModelBase.cs b/JwtAuthenticationApi/Abstraction/Models/ModelBase.cs deleted file mode 100644 index 2235f69..0000000 --- a/JwtAuthenticationApi/Abstraction/Models/ModelBase.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace JwtAuthenticationApi.Abstraction.Models -{ - using System.Diagnostics.CodeAnalysis; - - /// - /// Base class for all models in relational databases. - /// - [ExcludeFromCodeCoverage] - public abstract class ModelBase - { - /// - /// Entity unique identifier. - /// - public int Id { get; init; } - - protected ModelBase(int id) - { - Id = id; - } - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Commands/ConvertRequestToUserEntityCommand.cs b/JwtAuthenticationApi/Commands/ConvertRequestToUserEntityCommand.cs index dc765d1..847fd13 100644 --- a/JwtAuthenticationApi/Commands/ConvertRequestToUserEntityCommand.cs +++ b/JwtAuthenticationApi/Commands/ConvertRequestToUserEntityCommand.cs @@ -1,11 +1,11 @@ -using JwtAuthenticationApi.Abstraction.Commands; -using JwtAuthenticationApi.Commands.Models; -using JwtAuthenticationApi.Entities; -using JwtAuthenticationApi.Models.Registration.Requests; - -namespace JwtAuthenticationApi.Commands +namespace JwtAuthenticationApi.Commands { - /// + using Common.Abstraction.Commands; + using Common.Models; + using Infrastructure.Entities; + using Services.Models.Registration.Requests; + + /// /// Command that is responsible for creating from request. Implements . /// public class ConvertRequestToUserEntityCommand : ICommand diff --git a/JwtAuthenticationApi/Commands/Models/Result.cs b/JwtAuthenticationApi/Commands/Models/Result.cs deleted file mode 100644 index 39ed3e0..0000000 --- a/JwtAuthenticationApi/Commands/Models/Result.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace JwtAuthenticationApi.Commands.Models -{ - using System.Diagnostics.CodeAnalysis; - - /// - /// Represents a result of command execution. - /// - /// Result type. - [ExcludeFromCodeCoverage] - public sealed class Result - { - /// - /// Result value. - /// - public TResult Value { get; init; } - - /// - /// Gets whether command execution was successful. - /// - public bool IsSuccessful { get; init; } - - /// - /// Initializes new instance of - /// - /// Command execution result. - /// Specifies if command execution was successful. - public Result(TResult value, bool isSuccessful) - { - Value = value; - IsSuccessful = isSuccessful; - } - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Container/ContainerSetup.cs b/JwtAuthenticationApi/Container/ContainerSetup.cs deleted file mode 100644 index 7598b9c..0000000 --- a/JwtAuthenticationApi/Container/ContainerSetup.cs +++ /dev/null @@ -1,107 +0,0 @@ -namespace JwtAuthenticationApi.Container -{ - using DatabaseContext; - using Models.Options; - using Microsoft.EntityFrameworkCore; - using System.Diagnostics.CodeAnalysis; - using Abstraction.RuleEngine; - using Factories.Password; - using Factories.Polly; - using Factories.Wrappers; - using Handlers; - using Security.Password; - using Security.Password.Salt; - using Validators.Password; - using Wrappers; - using Registration; - using Identity.User; - using Factories.Commands; - using Abstraction.DatabaseContext; - - /// - /// Defines extensions methods for container setup. - /// - [ExcludeFromCodeCoverage] - public static class ContainerSetup - { - /// - /// Register options. - /// - /// Web application builder. - public static void RegisterOptions(this WebApplicationBuilder builder) - { - builder.Services.Configure( - builder.Configuration.GetSection(nameof(DatabaseConnectionStrings))); - builder.Services.Configure(builder.Configuration.GetSection(nameof(PasswordPepper))); - } - - /// - /// Register user identity database context. - /// - /// Web application builder. - public static void RegisterUserIdentityDatabaseContext(this WebApplicationBuilder builder) - { - builder.Services.AddDbContext(options => - { - options.UseNpgsql(builder.Configuration - .GetSection( - $"{nameof(DatabaseConnectionStrings)}:{nameof(DatabaseConnectionStrings.IdentityDatabaseConnectionString)}") - .Value); - }); - } - - /// - /// Register password salt database context. - /// - /// Web application builder. - public static void RegisterPasswordSaltDatabaseContext(this WebApplicationBuilder builder) - { - builder.Services.AddDbContext(options => - { - options.UseNpgsql(builder.Configuration - .GetSection( - $"{nameof(DatabaseConnectionStrings)}:{nameof(DatabaseConnectionStrings.SaltDatabaseConnectionString)}") - .Value); - }); - } - - /// - /// Register common services. - /// - /// Web application builder. - public static void RegisterServices(this WebApplicationBuilder builder) - { - builder.Services.AddSingleton(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(typeof(IRuleEngine<>), typeof(RuleEngine<>)); - builder.Services.AddTransient(); - } - - /// - /// Register security services. - /// - /// Web application builder. - public static void RegisterSecurityServices(this WebApplicationBuilder builder) - { - builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); - } - - /// - /// Register factories. - /// - /// Web application builder. - public static void RegisterFactories(this WebApplicationBuilder builder) - { - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - } - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Controllers/UserRegisterController.cs b/JwtAuthenticationApi/Controllers/UserRegisterController.cs deleted file mode 100644 index 6d72d8e..0000000 --- a/JwtAuthenticationApi/Controllers/UserRegisterController.cs +++ /dev/null @@ -1,48 +0,0 @@ -using JwtAuthenticationApi.Models.Enums; -using JwtAuthenticationApi.Models.Registration.Requests; -using JwtAuthenticationApi.Registration; -using Microsoft.AspNetCore.Mvc; - -namespace JwtAuthenticationApi.Controllers; - -[ApiController] -[Route("Register")] -public class UserRegisterController: ControllerBase -{ - private readonly IUserRegisterService _userRegisterService; - - public UserRegisterController(IUserRegisterService userRegisterService) - { - _userRegisterService = userRegisterService; - } - - [HttpPost] - [Route("User")] - [ProducesResponseType(StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - - - public async Task RegisterUserAsync([FromBody] RegisterUserRequest registerUserRequest, CancellationToken cancellationToken) - { - var result = await _userRegisterService.RegisterUserAsync(registerUserRequest, cancellationToken); - if (result.IsSuccessful) - { - return Created(this.Request?.Path ?? "", result.UserId); - } - - switch (result.ErrorType) - { - case ErrorType.PasswordValidationError: - return BadRequest(result.ErrorMessage); - case ErrorType.DbErrorEntityExists: - return Conflict(result.ErrorMessage); - case ErrorType.DbError: - case ErrorType.InternalError: - default: - return StatusCode(500, result.ErrorMessage); - } - } - -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Factories/Commands/CommandFactory.cs b/JwtAuthenticationApi/Factories/Commands/CommandFactory.cs deleted file mode 100644 index a5bb3d7..0000000 --- a/JwtAuthenticationApi/Factories/Commands/CommandFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -using JwtAuthenticationApi.Commands; -using JwtAuthenticationApi.Models.Registration.Requests; - -namespace JwtAuthenticationApi.Factories.Commands -{ - using Entities; - using Abstraction.Commands; - - /// - /// Implementation of interface. Provides methods to create different implementation of . - /// - public sealed class CommandFactory : ICommandFactory - { - public ICommand CreatePasswordMixCommand(string password, string salt, string pepper) - { - return new PasswordMixCommand(password, salt, pepper); - } - - public ICommand CreateConvertRequestToUserEntityCommand(RegisterUserRequest registerUserRequest, string hashedPassword) - { - return new ConvertRequestToUserEntityCommand(registerUserRequest, hashedPassword); - } - } -} diff --git a/JwtAuthenticationApi/Factories/Commands/ICommandFactory.cs b/JwtAuthenticationApi/Factories/Commands/ICommandFactory.cs deleted file mode 100644 index 4684493..0000000 --- a/JwtAuthenticationApi/Factories/Commands/ICommandFactory.cs +++ /dev/null @@ -1,31 +0,0 @@ -using JwtAuthenticationApi.Commands; -using JwtAuthenticationApi.Models.Registration.Requests; - -namespace JwtAuthenticationApi.Factories.Commands -{ - using Abstraction.Commands; - using Entities; - - /// - /// Defines methods for creating instances of classes that implements . - /// - public interface ICommandFactory - { - /// - /// Creates and returns instance of - /// - /// Password. - /// Password salt. - /// Password pepper. - /// Instance of that implements - ICommand CreatePasswordMixCommand(string password, string salt, string pepper); - - /// - /// Creates and returns instance of - /// - /// Register user request. - /// Hashed user password. - /// Instance of that implements - ICommand CreateConvertRequestToUserEntityCommand(RegisterUserRequest registerUserRequest, string hashedPassword); - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Factories/Password/IPasswordContextFactory.cs b/JwtAuthenticationApi/Factories/Password/IPasswordContextFactory.cs deleted file mode 100644 index 1cd2b11..0000000 --- a/JwtAuthenticationApi/Factories/Password/IPasswordContextFactory.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace JwtAuthenticationApi.Factories.Password -{ - using Models.Password; - - /// - /// Defines method for password context factory. - /// - public interface IPasswordContextFactory - { - /// - /// Creates from provided password and password confirmation. - /// - /// Password. - /// Password confirmation. - /// - PasswordContext Create(string password, string passwordConfirmation); - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Factories/Password/IPasswordRuleFactory.cs b/JwtAuthenticationApi/Factories/Password/IPasswordRuleFactory.cs deleted file mode 100644 index 2def4c6..0000000 --- a/JwtAuthenticationApi/Factories/Password/IPasswordRuleFactory.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace JwtAuthenticationApi.Factories.Password -{ - using Abstraction.RuleEngine; - using Models.Password; - using Validators.Password.Rules; - - /// - /// Defines methods for password rules factory. - /// - public interface IPasswordRuleFactory - { - /// - /// Creates and returns . - /// - /// Instance of that implements - IRule CreateEqualityRule(); - - /// - /// Creates and returns . - /// - /// Instance of that implements - IRule CreateLengthRule(); - - /// - /// Creates and returns . - /// - /// Instance of that implements - IRule CreateLowerLettersRule(); - - /// - /// Creates and returns . - /// - /// Instance of that implements - IRule CreateSpecialLetterRule(); - - /// - /// Creates and returns . - /// - /// Instance of that implements - IRule CreateUpperLettersRule(); - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Factories/Polly/IPollySleepingIntervalsFactory.cs b/JwtAuthenticationApi/Factories/Polly/IPollySleepingIntervalsFactory.cs deleted file mode 100644 index 4487525..0000000 --- a/JwtAuthenticationApi/Factories/Polly/IPollySleepingIntervalsFactory.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace JwtAuthenticationApi.Factories.Polly -{ - - /// - /// Defines method for polly sleeping intervals factory class. - /// - public interface IPollySleepingIntervalsFactory - { - /// - /// Creates constant time polly sleep interval and returns it as collection of . - /// - /// Constant value of sleep time per retry. - /// Total amount of retries. - /// that contains sleep intervals as a - IEnumerable CreateConstantInterval(uint sleepTime, uint retryCount); - - /// - /// Creates time polly sleep interval that increases linear and returns it as collection of . - /// - /// Starting sleep time. - /// Defines increase of time interval between retries. - /// Total amount of retires. - /// that contains sleep intervals as a - IEnumerable CreateLinearInterval(uint baseSleepTime, uint sleepTimeIncrease, uint retryCount); - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Factories/Wrappers/IMutexWrapperFactory.cs b/JwtAuthenticationApi/Factories/Wrappers/IMutexWrapperFactory.cs deleted file mode 100644 index 8d234aa..0000000 --- a/JwtAuthenticationApi/Factories/Wrappers/IMutexWrapperFactory.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JwtAuthenticationApi.Wrappers.Threading; - -namespace JwtAuthenticationApi.Factories.Wrappers -{ - /// - /// Defines method for factory class. - /// - public interface IMutexWrapperFactory - { - /// - /// Creates . - /// - /// Defines if calling thread is mutex owner. - /// Mutex name. - /// New instance of with provided name. - IMutexWrapper Create(bool initiallyOwned, string name); - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Factories/Wrappers/ISemaphoreWrapperFactory.cs b/JwtAuthenticationApi/Factories/Wrappers/ISemaphoreWrapperFactory.cs deleted file mode 100644 index 662fe8f..0000000 --- a/JwtAuthenticationApi/Factories/Wrappers/ISemaphoreWrapperFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -using JwtAuthenticationApi.Wrappers.Threading; - -namespace JwtAuthenticationApi.Factories.Wrappers; - -/// -/// Defines method for factory class. -/// -public interface ISemaphoreWrapperFactory -{ - /// - /// Creates and returns . - /// - /// The initial number of requests for the semaphore that can be granted concurrently - /// The maximum number of requests for the semaphore that can be granted concurrently. - /// - /// The name, if the synchronization object is to be shared with other processes; otherwise, or an empty string. The name is case-sensitive. - /// - /// New instance of . - ISemaphoreWrapper Create(int initialCount, int maximalCount, string name); -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Factories/Wrappers/SemaphoreWrapperFactory.cs b/JwtAuthenticationApi/Factories/Wrappers/SemaphoreWrapperFactory.cs deleted file mode 100644 index 7b33f69..0000000 --- a/JwtAuthenticationApi/Factories/Wrappers/SemaphoreWrapperFactory.cs +++ /dev/null @@ -1,15 +0,0 @@ -using JwtAuthenticationApi.Wrappers.Threading; - -namespace JwtAuthenticationApi.Factories.Wrappers -{ - /// - /// Implementation of interface. Creates and returns . - /// - public sealed class SemaphoreWrapperFactory: ISemaphoreWrapperFactory - { - public ISemaphoreWrapper Create(int initialCount, int maximalCount, string name) - { - return new SemaphoreWrapper(initialCount, maximalCount, name); - } - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Handlers/ICommandHandler.cs b/JwtAuthenticationApi/Handlers/ICommandHandler.cs deleted file mode 100644 index 05d586a..0000000 --- a/JwtAuthenticationApi/Handlers/ICommandHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace JwtAuthenticationApi.Handlers -{ - using Abstraction.Commands; - using Commands.Models; - - /// - /// Define method for handling requests in asynchronous way. - /// - public interface ICommandHandler - { - /// - /// Handles command and returns its result. - /// - /// Defines type that will be returned after handling command. - /// Command that implements interface. - /// Cancellation token. - /// - /// A task that represents the asynchronous password mix operation. The task result of handling provided command. - Task> HandleAsync(ICommand command, CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Identity/User/IUserService.cs b/JwtAuthenticationApi/Identity/User/IUserService.cs deleted file mode 100644 index d89746f..0000000 --- a/JwtAuthenticationApi/Identity/User/IUserService.cs +++ /dev/null @@ -1,34 +0,0 @@ -using JwtAuthenticationApi.Entities; -using Microsoft.EntityFrameworkCore; - -namespace JwtAuthenticationApi.Identity.User; - -/// -/// Defines method for saving user in database. -/// -public interface IUserService -{ - /// - /// Saves provided user in database, and returns its identifier in database. - /// - /// User entity which will be saved in database. - /// Cancellation token. - /// - /// User identifier in database, value is or . - Task SaveUserAsync(UserEntity userEntity, CancellationToken cancellationToken); - - /// - /// Determines if user exists in database. - /// - /// Its more efficient to use overload with userId if you already know user id. - /// Username for which database will be queried. - /// if user exists. Otherwise . - Task UserExistsAsync(string userName); - - /// - /// Determines if user exists in database. - /// - /// User id for which database will be queried. - /// if user exists. Otherwise . - Task UserExistsAsync(int userId); -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Identity/User/UserService.cs b/JwtAuthenticationApi/Identity/User/UserService.cs deleted file mode 100644 index a487cfd..0000000 --- a/JwtAuthenticationApi/Identity/User/UserService.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace JwtAuthenticationApi.Identity.User -{ - using Entities; - using Factories.Polly; - using ILogger = Serilog.ILogger; - using Polly; - using Polly.Retry; - using Microsoft.EntityFrameworkCore; - using Abstraction.DatabaseContext; - - - /// - /// User service that is responsible for saving user in database. Implements . - /// - public class UserService: IUserService - { - private readonly IUserContext _userContext; - private readonly IPollySleepingIntervalsFactory _pollySleepingIntervalsFactory; - private readonly ILogger _logger; - - /// - /// Returns instance of . - /// - /// User context - /// Polly sleeping intervals factory. - /// Logger. - public UserService(IUserContext userContext, IPollySleepingIntervalsFactory pollySleepingIntervalsFactory, ILogger logger) - { - _userContext = userContext; - _pollySleepingIntervalsFactory = pollySleepingIntervalsFactory; - _logger = logger; - } - - public async Task SaveUserAsync(UserEntity userEntity, CancellationToken cancellationToken) - { - AsyncRetryPolicy saveUserInDatabasePolicy = Policy - .Handle() - .WaitAndRetryAsync(_pollySleepingIntervalsFactory.CreateLinearInterval(4, 1, 3), - (exception, span) => - _logger.Error( - $"Error occurred during execution of {nameof(SaveUserAsync)}. Attempting to retry in {span.Seconds} seconds. Error Message: {exception.Exception.Message}.")); - _logger.Information("Attempting to save user in database"); - await _userContext.Users.AddAsync(userEntity, cancellationToken); - await saveUserInDatabasePolicy.ExecuteAsync(async () => await _userContext.SaveChangesAsync(cancellationToken)); - _logger.Information("User saved in database"); - return userEntity.Id; - } - - public async Task UserExistsAsync(string userName) - { - var result = await _userContext.Users.AnyAsync(e => e.Username.Equals(userName)); - return result; - } - public async Task UserExistsAsync(int userId) - { - var result = await _userContext.Users.AnyAsync(e => e.Id.Equals(userId)); - return result; - } - } -} diff --git a/JwtAuthenticationApi/JwtAuthenticationApi.cs b/JwtAuthenticationApi/JwtAuthenticationApi.cs index 2769345..9ac7972 100644 --- a/JwtAuthenticationApi/JwtAuthenticationApi.cs +++ b/JwtAuthenticationApi/JwtAuthenticationApi.cs @@ -1,11 +1,16 @@ namespace JwtAuthenticationApi { - using Container; using System.Diagnostics.CodeAnalysis; - using Container.Logger; + using Common; + using Common.Options; + using Infrastructure; + using Logger; + using Options; + using Security; + using Services; - [ExcludeFromCodeCoverage] + [ExcludeFromCodeCoverage] internal static class JwtAuthenticationApi { public static async Task Main(string[] args) @@ -17,13 +22,12 @@ public static async Task Main(string[] args) }); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); - builder.RegisterOptions(); - builder.RegisterUserIdentityDatabaseContext(); - builder.RegisterPasswordSaltDatabaseContext(); + builder.Services.InstallCommon(); + builder.Services.InstallSecurity(); + builder.Services.InstallServices(); + builder.Services.InstallInfrastructureProject(builder.GetIdentityConnectionString, builder.GetSaltConnectionString); builder.SetupSerilog(); - builder.RegisterServices(); - builder.RegisterSecurityServices(); - builder.RegisterFactories(); + builder.Services.Configure(builder.Configuration.GetSection(PasswordPepper.Position)); var app = builder.Build(); if (app.Environment.IsDevelopment()) { @@ -40,5 +44,19 @@ public static async Task Main(string[] args) await app.RunAsync(); } + + + private static string GetSaltConnectionString(this WebApplicationBuilder webApplicationBuilder) + { + return webApplicationBuilder.Configuration.GetSection( + $"{nameof(DatabaseConnectionStrings)}:{nameof(DatabaseConnectionStrings.SaltDatabaseConnectionString)}").Value; + } + + private static string GetIdentityConnectionString(this WebApplicationBuilder webApplicationBuilder) + { + return webApplicationBuilder.Configuration.GetSection( + $"{nameof(DatabaseConnectionStrings)}:{nameof(DatabaseConnectionStrings.IdentityDatabaseConnectionString)}").Value; + } + } } \ No newline at end of file diff --git a/JwtAuthenticationApi/JwtAuthenticationApi.csproj b/JwtAuthenticationApi/JwtAuthenticationApi.csproj index a3c1c8c..6106fb2 100644 --- a/JwtAuthenticationApi/JwtAuthenticationApi.csproj +++ b/JwtAuthenticationApi/JwtAuthenticationApi.csproj @@ -20,4 +20,11 @@ + + + + + + + \ No newline at end of file diff --git a/JwtAuthenticationApi/Logger/LoggerPath.cs b/JwtAuthenticationApi/Logger/LoggerPath.cs new file mode 100644 index 0000000..62af4e4 --- /dev/null +++ b/JwtAuthenticationApi/Logger/LoggerPath.cs @@ -0,0 +1,19 @@ +namespace JwtAuthenticationApi.Logger +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// Provides paths for JwtAuthenticationApi Web API Project. + /// + [ExcludeFromCodeCoverage] + public static class LoggerPath + { + /// + /// Provides full path for logs storage. + /// + /// + /// value of logs storage full path. + /// + public static string LogsStorageFullPath => Path.Combine(Path.GetTempPath(), "JwtAuthenticationApi", "Logs", "Jaa-Logs-.txt"); + } +} \ No newline at end of file diff --git a/JwtAuthenticationApi/Container/Logger/LoggerSetup.cs b/JwtAuthenticationApi/Logger/LoggerSetup.cs similarity index 92% rename from JwtAuthenticationApi/Container/Logger/LoggerSetup.cs rename to JwtAuthenticationApi/Logger/LoggerSetup.cs index ae3c983..b25acde 100644 --- a/JwtAuthenticationApi/Container/Logger/LoggerSetup.cs +++ b/JwtAuthenticationApi/Logger/LoggerSetup.cs @@ -1,12 +1,11 @@ -namespace JwtAuthenticationApi.Container.Logger +namespace JwtAuthenticationApi.Logger { using System.Diagnostics.CodeAnalysis; using Exceptions; - using Models.Options; using Serilog; using Serilog.Events; using ILogger = Serilog.ILogger; - using Models; + using Options; [ExcludeFromCodeCoverage] @@ -26,7 +25,7 @@ public static void SetupSerilog(this WebApplicationBuilder builder) .Enrich.WithThreadName() .WriteTo.Async(w => w.Console(outputTemplate: consoleOutputTemplate!)) .WriteTo.Async(w => w.File( - path: JaaPaths.LogsStorageFullPath, + path: LoggerPath.LogsStorageFullPath, outputTemplate: fileOutputTemplate!, restrictedToMinimumLevel: LogEventLevel.Warning, rollOnFileSizeLimit: true, diff --git a/JwtAuthenticationApi/Models/JaaPaths.cs b/JwtAuthenticationApi/Models/JaaPaths.cs deleted file mode 100644 index 6f147c6..0000000 --- a/JwtAuthenticationApi/Models/JaaPaths.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace JwtAuthenticationApi.Models -{ - using System.Diagnostics.CodeAnalysis; - - /// - /// Provides paths for JwtAuthenticationApi Web API Project. - /// - [ExcludeFromCodeCoverage] - public static class JaaPaths - { - /// - /// Provides full path for logs storage. - /// - /// - /// value of logs storage full path. - /// - public static string LogsStorageFullPath => Path.Combine(Path.GetTempPath(), "JwtAuthenticationApi", "Logs", "Jaa-Logs-.txt"); - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Models/Password/PasswordSaltModel.cs b/JwtAuthenticationApi/Models/Password/PasswordSaltModel.cs deleted file mode 100644 index bb0f0ab..0000000 --- a/JwtAuthenticationApi/Models/Password/PasswordSaltModel.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace JwtAuthenticationApi.Models.Password -{ - using System.ComponentModel.DataAnnotations; - using Abstraction.Models; - using System.Diagnostics.CodeAnalysis; - - /// - /// Password salt model, that will be saved in database. Inherits . - /// - [ExcludeFromCodeCoverage] - public sealed class PasswordSaltModel : ModelBase - { - /// - /// Gets or sets Password salt. - /// - [Required] - public string Salt { get; set; } - - /// - /// Gets value of user that is associated with this salt. - /// - [Required] - public int UserId { get; init; } - - /// - /// Initializes - /// - /// Identifier. - /// Salt. - /// User identifier associated with password salt. - public PasswordSaltModel(int id, string salt, int userId) : base(id) - { - Salt = salt; - UserId = userId; - } - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Models/Registration/Responses/RegisterUserResponse.cs b/JwtAuthenticationApi/Models/Registration/Responses/RegisterUserResponse.cs deleted file mode 100644 index aef0692..0000000 --- a/JwtAuthenticationApi/Models/Registration/Responses/RegisterUserResponse.cs +++ /dev/null @@ -1,29 +0,0 @@ -using JwtAuthenticationApi.Models.Enums; - -namespace JwtAuthenticationApi.Models.Registration.Responses; - -/// -/// User registration response. -/// -public class RegisterUserResponse -{ - /// - /// User unique identifier. - /// - public int UserId { get; set; } - - /// - /// Describes if service execution was successful - /// - public bool IsSuccessful { get; set; } - - /// - /// Describes what kind of error occurred. Null if no errors occurred. - /// - public ErrorType ErrorType { get; set; } - - /// - /// Error message. Null if no errors occurred. - /// - public string ErrorMessage { get; set; } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Models/UserModel.cs b/JwtAuthenticationApi/Models/UserModel.cs deleted file mode 100644 index 274a607..0000000 --- a/JwtAuthenticationApi/Models/UserModel.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace JwtAuthenticationApi.Models -{ - using Abstraction.Models; - using System.Diagnostics.CodeAnalysis; - - /// - /// User model, that will be saved in database. Inherits . - /// - [ExcludeFromCodeCoverage] - public sealed class UserModel: ModelBase - { - /// - /// Gets or sets username. - /// - public string UserName { get; set; } - - /// - /// Gets or sets user hashed password. - /// - public string HashedPassword { get; set; } - - /// - /// Gets or sets user email. - /// - public string Email { get; set; } - - /// - /// Gets value whether this user was created. - /// - public DateTime CreationDate { get; init; } - - public UserModel(int id, string userName, string hashedPassword, string email) : base(id) - { - UserName = userName; - HashedPassword = hashedPassword; - Email = email; - CreationDate = DateTime.UtcNow; - } - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Models/Options/DatabaseConnectionStrings.cs b/JwtAuthenticationApi/Options/DatabaseConnectionStrings.cs similarity index 93% rename from JwtAuthenticationApi/Models/Options/DatabaseConnectionStrings.cs rename to JwtAuthenticationApi/Options/DatabaseConnectionStrings.cs index bfc6f10..161d408 100644 --- a/JwtAuthenticationApi/Models/Options/DatabaseConnectionStrings.cs +++ b/JwtAuthenticationApi/Options/DatabaseConnectionStrings.cs @@ -1,4 +1,4 @@ -namespace JwtAuthenticationApi.Models.Options +namespace JwtAuthenticationApi.Options { using System.Diagnostics.CodeAnalysis; diff --git a/JwtAuthenticationApi/Models/Options/SerilogSinks.cs b/JwtAuthenticationApi/Options/SerilogSinks.cs similarity index 82% rename from JwtAuthenticationApi/Models/Options/SerilogSinks.cs rename to JwtAuthenticationApi/Options/SerilogSinks.cs index 8c67d89..fda4d8c 100644 --- a/JwtAuthenticationApi/Models/Options/SerilogSinks.cs +++ b/JwtAuthenticationApi/Options/SerilogSinks.cs @@ -1,7 +1,7 @@ -using System.Diagnostics.CodeAnalysis; - -namespace JwtAuthenticationApi.Models.Options +namespace JwtAuthenticationApi.Options { + using System.Diagnostics.CodeAnalysis; + /// /// Stores options for Serilog sinks. /// diff --git a/JwtAuthenticationApi/Registration/IUserRegisterService.cs b/JwtAuthenticationApi/Registration/IUserRegisterService.cs deleted file mode 100644 index fc766f1..0000000 --- a/JwtAuthenticationApi/Registration/IUserRegisterService.cs +++ /dev/null @@ -1,19 +0,0 @@ -using JwtAuthenticationApi.Models.Registration.Requests; -using JwtAuthenticationApi.Models.Registration.Responses; - -namespace JwtAuthenticationApi.Registration; - -/// -/// Defines method for user registration. -/// -public interface IUserRegisterService -{ - /// - /// Register user - create user model, hash password and save this data in databases. - /// - /// user registration request. - /// Cancellation token. - /// A task that represents the asynchronous operation of registration - /// The task result contains ."/> - Task RegisterUserAsync(RegisterUserRequest registerUserRequest, CancellationToken cancellationToken); -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Security/Password/IPasswordHashingService.cs b/JwtAuthenticationApi/Security/Password/IPasswordHashingService.cs deleted file mode 100644 index 785cb5f..0000000 --- a/JwtAuthenticationApi/Security/Password/IPasswordHashingService.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace JwtAuthenticationApi.Security.Password -{ - /// - /// Defines method for password hashing. - /// - public interface IPasswordHashingService - { - /// - /// Hashes provided password with pepper and salt using SHA256. - /// - /// Password. - /// Password salt. - /// Cancellation token. - /// A task that represents the asynchronous password mix operation. - /// The task result contains Base64 hashed password. - Task HashPasswordAsync(string password, string salt, CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Security/Password/Salt/ISaltProvider.cs b/JwtAuthenticationApi/Security/Password/Salt/ISaltProvider.cs deleted file mode 100644 index 7e75a97..0000000 --- a/JwtAuthenticationApi/Security/Password/Salt/ISaltProvider.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace JwtAuthenticationApi.Security.Password.Salt -{ - using Models; - - /// - /// Defines method for providing password salt from database. - /// - public interface ISaltProvider - { - /// - /// Gets or creates password salt associated with . - /// - /// UserId for whom password salt will be queried. - /// Cancellation token. - /// Password salt. - Task GetPasswordSaltAsync(int userId, CancellationToken cancellationToken = new CancellationToken()); - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Security/Password/Salt/ISaltService.cs b/JwtAuthenticationApi/Security/Password/Salt/ISaltService.cs deleted file mode 100644 index e94f305..0000000 --- a/JwtAuthenticationApi/Security/Password/Salt/ISaltService.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace JwtAuthenticationApi.Security.Password.Salt -{ - using Commands.Models; - - /// - /// Defines methods for generating, receiving and saving password salts. - /// - public interface ISaltService - { - /// - /// Saves salt associated with in salt database. - /// - /// User associated salt. - /// UserId for whom password salt will be created. - /// Cancellation token. - /// A task that represents the asynchronous operation of creating and saving password salt. - /// The task result contains id of newly created salt. - Task SaveSaltAsync(string salt, int userId, CancellationToken cancellationToken = new CancellationToken()); - - /// - /// Gets password salt associated with . - /// - /// UserId for whom password salt will be queried. - /// Cancellation token. - /// A task that represents the asynchronous operation of getting password salt from database. - /// The task result contains value of operation result. - Task> GetSaltAsync(int userId, CancellationToken cancellationToken = new CancellationToken()); - - /// - /// Generates new password salt. - /// - /// value of salt. - string GenerateSalt(); - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Validators/Password/IPasswordValidator.cs b/JwtAuthenticationApi/Validators/Password/IPasswordValidator.cs deleted file mode 100644 index 91a3d3d..0000000 --- a/JwtAuthenticationApi/Validators/Password/IPasswordValidator.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace JwtAuthenticationApi.Validators.Password; - -public interface IPasswordValidator -{ - public bool Validate(string password, string passwordConfirmation); -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Wrappers/GuidWrapper.cs b/JwtAuthenticationApi/Wrappers/GuidWrapper.cs deleted file mode 100644 index a26d901..0000000 --- a/JwtAuthenticationApi/Wrappers/GuidWrapper.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace JwtAuthenticationApi.Wrappers -{ - /// - /// wrapper. - /// - public class GuidWrapper: IGuidWrapper - { - /// - public Guid CreateGuid() - { - return Guid.NewGuid(); - } - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Wrappers/IGuidWrapper.cs b/JwtAuthenticationApi/Wrappers/IGuidWrapper.cs deleted file mode 100644 index 21a1c7c..0000000 --- a/JwtAuthenticationApi/Wrappers/IGuidWrapper.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace JwtAuthenticationApi.Wrappers -{ - /// - /// Defines method for wrapper. - /// - public interface IGuidWrapper - { - /// - Guid CreateGuid(); - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Wrappers/Threading/IMutexWrapper.cs b/JwtAuthenticationApi/Wrappers/Threading/IMutexWrapper.cs deleted file mode 100644 index f342204..0000000 --- a/JwtAuthenticationApi/Wrappers/Threading/IMutexWrapper.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace JwtAuthenticationApi.Wrappers.Threading -{ - /// - /// Defines method for wrapping class. - /// - public interface IMutexWrapper : IDisposable - { - /// - void WaitOne(); - /// - void ReleaseMutex(); - } -} \ No newline at end of file diff --git a/JwtAuthenticationApi/Wrappers/Threading/ISemaphoreWrapper.cs b/JwtAuthenticationApi/Wrappers/Threading/ISemaphoreWrapper.cs deleted file mode 100644 index 84ade27..0000000 --- a/JwtAuthenticationApi/Wrappers/Threading/ISemaphoreWrapper.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace JwtAuthenticationApi.Wrappers.Threading; - -/// -/// Defines methods for wrapper. -/// -public interface ISemaphoreWrapper: IDisposable -{ - /// - /// Blocks current thread until the current receives a signal. - /// - /// - /// if the current instance receives a signal. If the current instance is never signaled, WaitOne() never returns. - /// - bool WaitOne(); - /// - int Release(); -} \ No newline at end of file