(feat): white list tokens policy completed

This commit is contained in:
Maksym Sadovnychyy 2022-10-27 00:06:41 +02:00
parent 8fc1611d9b
commit c1e343346d
65 changed files with 376 additions and 176 deletions

View File

@ -7,7 +7,7 @@
"created": {
"$date": "2022-01-01T00:00:00.000Z"
},
"nickaName": "admin",
"username": "admin",
"passwords": {
"password": {
"hash": "pznndK3nv9bftf/qQxqBy4VjH7Ow9vx2Kd6376oJuqQ=",

View File

@ -3,7 +3,7 @@ using Core.Enumerations;
namespace Core.DomainObjects {
public class Contact : DomainObjectBase<Contact> {
public ContactTypes ContactType { get; set; }
public ContactTypes Type { get; set; }
public string Value { get; set; }
public bool Confirmed { get; set; }

View File

@ -8,7 +8,7 @@ namespace Core.DomainObjects {
public DateTime Created { get; set; }
public string NickName { get; set; }
public string Username { get; set; }
public Passwords Passwords { get; set; }

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CryptoProvider {
public interface IAesKey {
public string? IV { get; set; }
public string? Key { get; set; }
}
public class AesKey : IAesKey {
public string? IV { get; set; }
public string? Key { get; set; }
}
}

View File

@ -0,0 +1,77 @@
using System.Text;
using System.Security.Cryptography;
namespace CryptoProvider {
public static class AesService {
public static IAesKey GenerateKey() {
using var aes = Aes.Create();
aes.GenerateIV();
aes.GenerateKey();
return new AesKey {
IV = Convert.ToBase64String(aes.IV),
Key = Convert.ToBase64String(aes.Key)
};
}
public static string EncryptString(string key, string plainText) =>
EncryptStringCore(new byte[16], Convert.FromBase64String(key), plainText);
public static string EncryptString(string iv, string key, string plainText) =>
EncryptStringCore(Convert.FromBase64String(iv), Convert.FromBase64String(key), plainText);
public static string DecryptString(string key, string cipherText) =>
DecryptStringCore(new byte[16], Convert.FromBase64String(key), cipherText);
public static string DecryptString(string iv, string key, string cipherText) =>
DecryptStringCore(Convert.FromBase64String(iv), Convert.FromBase64String(key), cipherText);
#region Core methods
private static string EncryptStringCore(byte[] iv, byte[] key, string plainText) {
byte[] array;
using var aes = Aes.Create();
aes.Key = key;
aes.IV = iv;
var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using var memoryStream = new MemoryStream();
using var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
using var streamWriter = new StreamWriter(cryptoStream);
streamWriter.Write(plainText);
streamWriter.Flush();
cryptoStream.FlushFinalBlock();
array = memoryStream.ToArray();
return Convert.ToBase64String(array);
}
private static string DecryptStringCore(byte [] iv, byte [] key, string cipherText) {
byte[] buffer = Convert.FromBase64String(cipherText);
using var aes = Aes.Create();
aes.Key = key;
aes.IV = iv;
var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using var memoryStream = new MemoryStream(buffer);
using var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
using var streamReader = new StreamReader(cryptoStream);
return streamReader.ReadToEnd();
}
#endregion
}
}

View File

@ -7,10 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DomainResult.Common" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="6.0.8" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="6.0.10" />
</ItemGroup>

View File

@ -2,14 +2,10 @@
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
namespace HashService {
namespace CryptoProvider {
public interface IHashService {
(string, string) CreateSaltedHash(string value);
bool ValidateHash(string value, string salt, string hash);
}
public class HashService : IHashService {
private string CreateSalt() {
public static class HashService {
private static string CreateSalt() {
byte[] randomBytes = new byte[128 / 8];
using (var generator = RandomNumberGenerator.Create()) {
generator.GetBytes(randomBytes);
@ -17,7 +13,7 @@ namespace HashService {
}
}
private string CreateHash(string value, string salt) {
private static string CreateHash(string value, string salt) {
var valueBytes = KeyDerivation.Pbkdf2(
password: value,
salt: Encoding.UTF8.GetBytes(salt),
@ -28,14 +24,14 @@ namespace HashService {
return Convert.ToBase64String(valueBytes);
}
public (string, string) CreateSaltedHash(string value) {
public static (string, string) CreateSaltedHash(string value) {
var salt = CreateSalt();
var hash = CreateHash(value, salt);
return (salt, hash);
}
public bool ValidateHash(string value, string salt, string hash) =>
public static bool ValidateHash(string value, string salt, string hash) =>
CreateHash(value, salt) == hash;
}
}

View File

@ -15,7 +15,9 @@ namespace DataProviders.Collections {
public interface IUserDataProvider {
(User?, IDomainResult) Get(Guid userId);
(User?, IDomainResult) GetByNickName(string nickName);
(User?, IDomainResult) GetByUsername(string nickName);
(Guid?, IDomainResult) Update(User user);
}
public class UserDataProvider : CollectionDataProviderBase<User>, IUserDataProvider {
@ -29,7 +31,7 @@ namespace DataProviders.Collections {
ISessionService sessionService) : base(logger, client, idGenerator, sessionService) {
}
public (User?, IDomainResult) Get( Guid userId) {
public (User?, IDomainResult) Get(Guid userId) {
var (list, result) = GetWithPredicate(x => x.Id == userId, _collectionName);
if (!result.IsSuccess || list == null)
@ -38,13 +40,17 @@ namespace DataProviders.Collections {
return (list.First(), result);
}
public (User?, IDomainResult) GetByNickName(string nickName) {
var (list, result) = GetWithPredicate(x => x.NickName == nickName, _collectionName);
public (User?, IDomainResult) GetByUsername(string username) {
var (list, result) = GetWithPredicate(x => x.Username == username, _collectionName);
if (!result.IsSuccess || list == null)
return (null, result);
return (list.First(), result);
}
public (Guid?, IDomainResult) Update(User user) =>
UpdateWithPredicate(user, x => x.Id == user.Id, _collectionName);
}
}

View File

@ -0,0 +1,55 @@
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using Core.Abstractions;
namespace DataProviders.Converters {
public class EnumerationListSerializer<T> : IBsonSerializer<List<T>>, IBsonArraySerializer where T : Enumeration {
private List<T> Deserialize(IBsonReader reader) {
var type = reader.GetCurrentBsonType();
var response = new List<T>();
switch (type) {
case BsonType.Array:
reader.ReadStartArray();
while (reader.ReadBsonType() != BsonType.EndOfDocument) {
response.Add(Enumeration.FromValue<T>(BsonSerializer.Deserialize<int>(reader)));
}
reader.ReadEndArray();
return response;
default:
throw new NotImplementedException($"No implementation to deserialize {type}");
}
}
private void Serialize(IBsonWriter writer, List<T> values) {
if (values != null) {
writer.WriteStartArray();
foreach (var value in values) {
writer.WriteInt32(value.Id);
}
writer.WriteEndArray();
}
}
public Type ValueType { get => typeof(List<T>); }
public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => Deserialize(context.Reader);
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) => Serialize(context.Writer, (List<T>)value);
List<T> IBsonSerializer<List<T>>.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => Deserialize(context.Reader);
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, List<T> value) => Serialize(context.Writer, value);
public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationInfo) {
string elementName = null;
var serializer = BsonSerializer.LookupSerializer(typeof(string));
var nominalType = typeof(string);
serializationInfo = new BsonSerializationInfo(elementName, serializer, nominalType);
return true;
}
}
}

View File

@ -6,65 +6,22 @@ using Core.Abstractions;
namespace DataProviders.Converters {
public class EnumerationSerializer<T> : IBsonSerializer<T> where T : Enumeration {
private T Deserialize(IBsonReader reader) => Enumeration.FromValue<T>(BsonSerializer.Deserialize<int>(reader));
private void Serialize(IBsonWriter writer, T value) => BsonSerializer.Serialize(writer, value.Id);
public Type ValueType { get => typeof(T); }
public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => Deserialize(context.Reader);
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) => Serialize(context.Writer, (T)value);
public T Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) =>
Deserialize(context.Reader);
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, T value) =>
Serialize(context.Writer, value);
T IBsonSerializer<T>.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => Deserialize(context.Reader);
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, T value) => Serialize(context.Writer, value);
}
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) =>
Serialize(context.Writer, (T)value);
public class EnumerationListSerializer<T> : IBsonSerializer<List<T>>, IBsonArraySerializer where T : Enumeration {
private List<T> Deserialize(IBsonReader reader) {
var type = reader.GetCurrentBsonType();
var response = new List<T>();
switch (type) {
case BsonType.Array:
reader.ReadStartArray();
while (reader.ReadBsonType() != BsonType.EndOfDocument) {
response.Add(Enumeration.FromValue<T>(BsonSerializer.Deserialize<int>(reader)));
}
reader.ReadEndArray();
return response;
default:
throw new NotImplementedException($"No implementation to deserialize {type}");
}
}
private void Serialize(IBsonWriter writer, List<T> values) {
if (values != null) {
writer.WriteStartArray();
foreach (var value in values) {
writer.WriteInt32(value.Id);
}
writer.WriteEndArray();
}
}
public Type ValueType { get => typeof(List<T>); }
public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => Deserialize(context.Reader);
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) => Serialize(context.Writer, (List<T>)value);
List<T> IBsonSerializer<List<T>>.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) => Deserialize(context.Reader);
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, List<T> value) => Serialize(context.Writer, value);
public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationInfo) {
string elementName = null;
var serializer = BsonSerializer.LookupSerializer(typeof(string));
var nominalType = typeof(string);
serializationInfo = new BsonSerializationInfo(elementName, serializer, nominalType);
return true;
}
object IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) =>
Deserialize(context.Reader);
}
}

View File

@ -8,10 +8,10 @@
<ItemGroup>
<PackageReference Include="DomainResult.Common" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="MongoDB.Driver" Version="2.17.1" />
<PackageReference Include="MongoDB.Driver.GridFS" Version="2.17.1" />
<PackageReference Include="MongoDB.Driver" Version="2.18.0" />
<PackageReference Include="MongoDB.Driver.GridFS" Version="2.18.0" />
</ItemGroup>
<ItemGroup>

View File

@ -13,6 +13,8 @@ namespace DataProviders.Extensions
public static void RegisterDataproviders(this IServiceCollection services, IDataProvidersConfig appSettings) {
var config = appSettings.Database;
if (config == null) throw new NullReferenceException();
services.AddSingleton<IMongoClient>(x => new MongoClient(config.ConnectionString));
services.AddSingleton<IIdGenerator, GuidGenerator>();

View File

@ -87,7 +87,7 @@ namespace DataProviders {
BsonClassMap.RegisterClassMap<Contact>(cm => {
cm.AutoMap();
cm.GetMemberMap(c => c.ContactType)
cm.GetMemberMap(c => c.Type)
.SetSerializer(new EnumerationSerializer<ContactTypes>());
});
}

View File

@ -2,11 +2,6 @@
using ImageProvider.Fonts;
using Microsoft.Extensions.Logging;
using SixLabors.Fonts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ImageProvider {

View File

@ -8,14 +8,15 @@
<ItemGroup>
<PackageReference Include="DomainResult.Common" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.2" />
<PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta0013" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\DataProviders\DataProviders.csproj" />
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.14.0" />
<PackageReference Include="Yarp.ReverseProxy" Version="1.1.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageReference Include="Yarp.ReverseProxy" Version="1.1.1" />
</ItemGroup>
</Project>

View File

@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="DomainResult.Common" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="nClam" Version="7.0.0" />
</ItemGroup>

View File

@ -1,11 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
namespace HashService.Extensions {
public static class ServiceCollectionExtensions {
public static void RegisterHashService(this IServiceCollection services) {
services.AddSingleton<IHashService, HashService>();
}
}
}

View File

@ -8,9 +8,9 @@
<ItemGroup>
<PackageReference Include="DomainResult.Common" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.18.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.24.0" />
</ItemGroup>
</Project>

View File

@ -12,11 +12,11 @@
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="MongoDB.Driver" Version="2.17.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="MongoDB.Driver" Version="2.18.0" />
<PackageReference Include="xunit" Version="2.4.2" />
</ItemGroup>
<ItemGroup>

View File

@ -9,10 +9,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>

View File

@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WeatherForecast", "WeatherF
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{113EE574-E047-4727-AA36-841F845504D5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HashService", "Services\HashService\HashService.csproj", "{B8F84A37-B54B-4606-9BC3-6FEB96A5A34B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JWTService", "Services\JWTService\JWTService.csproj", "{B717D8BD-BCCA-4515-9A62-CA3BE802D0F7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "Core\Core.csproj", "{BCDED8EB-97B0-4067-BB0A-23F94D1A1288}"
@ -25,7 +23,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExtensionsTests", "Tests\Ex
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreTests", "Tests\Core\CoreTests.csproj", "{04CB9827-AA6D-4708-A26D-8420C842506D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageProvider", "Services\ImageProvider\ImageProvider.csproj", "{16552644-D7EE-4B4A-A725-79909A8103DE}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageProvider", "ImageProvider\ImageProvider.csproj", "{16552644-D7EE-4B4A-A725-79909A8103DE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileSecurityService", "Services\FileSecurityService\FileSecurityService.csproj", "{AD515653-9145-4894-9017-0ABA5A5892F4}"
EndProject
@ -33,6 +31,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReverseProxy", "ReverseProx
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{7FC6F0BA-2DCB-4B53-A3B3-61CEEF42B9D0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CryptoProvider", "CryptoProvider\CryptoProvider.csproj", "{E90379CB-BC2D-452F-9F92-24A7B46195D4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -43,10 +43,6 @@ Global
{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}.Release|Any CPU.Build.0 = Release|Any CPU
{B8F84A37-B54B-4606-9BC3-6FEB96A5A34B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8F84A37-B54B-4606-9BC3-6FEB96A5A34B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8F84A37-B54B-4606-9BC3-6FEB96A5A34B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B8F84A37-B54B-4606-9BC3-6FEB96A5A34B}.Release|Any CPU.Build.0 = Release|Any CPU
{B717D8BD-BCCA-4515-9A62-CA3BE802D0F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B717D8BD-BCCA-4515-9A62-CA3BE802D0F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B717D8BD-BCCA-4515-9A62-CA3BE802D0F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -91,16 +87,18 @@ Global
{7FC6F0BA-2DCB-4B53-A3B3-61CEEF42B9D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FC6F0BA-2DCB-4B53-A3B3-61CEEF42B9D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7FC6F0BA-2DCB-4B53-A3B3-61CEEF42B9D0}.Release|Any CPU.Build.0 = Release|Any CPU
{E90379CB-BC2D-452F-9F92-24A7B46195D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E90379CB-BC2D-452F-9F92-24A7B46195D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E90379CB-BC2D-452F-9F92-24A7B46195D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E90379CB-BC2D-452F-9F92-24A7B46195D4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{B8F84A37-B54B-4606-9BC3-6FEB96A5A34B} = {113EE574-E047-4727-AA36-841F845504D5}
{B717D8BD-BCCA-4515-9A62-CA3BE802D0F7} = {113EE574-E047-4727-AA36-841F845504D5}
{43315A1D-9E09-4398-84B9-A9D9D623AE5A} = {216302C2-64DE-4AE7-BC14-BDAC5B732472}
{04CB9827-AA6D-4708-A26D-8420C842506D} = {216302C2-64DE-4AE7-BC14-BDAC5B732472}
{16552644-D7EE-4B4A-A725-79909A8103DE} = {113EE574-E047-4727-AA36-841F845504D5}
{AD515653-9145-4894-9017-0ABA5A5892F4} = {113EE574-E047-4727-AA36-841F845504D5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution

View File

@ -1,4 +1,5 @@
using DataProviders;
using CryptoProvider;
using DataProviders;
using JWTService;
namespace WeatherForecast {
@ -13,6 +14,11 @@ namespace WeatherForecast {
/// </summary>
public JwtConfig? JwtConfig { get; set; }
/// <summary>
///
/// </summary>
public AesKey? JwtTokenEncryption { get; set; }
/// <summary>
///
/// </summary>

View File

@ -11,7 +11,7 @@ namespace WeatherForecast.Controllers;
/// <summary>
///
/// </summary>
[AllowAnonymous]
[ApiController]
[Route("api/[controller]")]
public class AuthenticationController : ControllerBase {
@ -29,15 +29,26 @@ public class AuthenticationController : ControllerBase {
}
/// <summary>
///
/// By providing username and password user obtains jwt token
/// </summary>
/// <param name="siteId"></param>
/// <param name="requestData"></param>
/// <returns></returns>
[HttpPost("{siteId}")]
public IActionResult Post([FromRoute] Guid siteId, [FromBody] AuthenticationRequestModel requestData) {
var result = _authenticationService.Post(siteId, requestData);
[AllowAnonymous]
[HttpPost()]
public IActionResult Post([FromBody] AuthenticationRequestModel requestData) {
var result = _authenticationService.Post(requestData);
return result.ToActionResult();
}
/// <summary>
///
/// </summary>
/// <returns></returns>
[Authorize(Policy = "WhitelistToken")]
[HttpGet()]
public IActionResult Get() {
return Ok();
}
}

View File

@ -11,7 +11,7 @@ namespace WeatherForecast.Controllers;
/// <summary>
///
/// </summary>
[Authorize(Policy = "")]
[Authorize(Policy = "WhitelistToken")]
[ApiController]
[Route("api/[controller]")]
public class BlogItemController : ControllerBase {

View File

@ -10,7 +10,7 @@ namespace WeatherForecast.Controllers;
/// <summary>
///
/// </summary>
[Authorize(Policy = "")]
[Authorize(Policy = "WhitelistToken")]
[ApiController]
[Route("api/[controller]")]
public class BlogItemsController : ControllerBase {

View File

@ -11,7 +11,7 @@ namespace WeatherForecast.Controllers;
/// <summary>
///
/// </summary>
[Authorize]
[Authorize(Policy = "WhitelistToken")]
[ApiController]
[Route("api/[controller]")]
public class CategoryItemController : ControllerBase {

View File

@ -10,7 +10,7 @@ namespace WeatherForecast.Controllers;
/// <summary>
///
/// </summary>
[Authorize]
[Authorize(Policy = "WhitelistToken")]
[ApiController]
[Route("api/[controller]")]
public class CategoryItemsController : ControllerBase {

View File

@ -9,7 +9,7 @@ namespace WeatherForecast.Controllers;
/// <summary>
///
/// </summary>
[Authorize]
[Authorize(Policy = "WhitelistToken")]
[AllowAnonymous]
[Route("api/[controller]")]
public class FileController : Controller {

View File

@ -8,7 +8,7 @@ namespace WeatherForecast.Controllers;
/// <summary>
///
/// </summary>
[Authorize]
[Authorize(Policy = "WhitelistToken")]
[AllowAnonymous]
[Route("api/[controller]")]
public class FilesController : Controller {

View File

@ -10,7 +10,7 @@ namespace WeatherForecast.Controllers;
/// <summary>
///
/// </summary>
[Authorize]
[Authorize(Policy = "WhitelistToken")]
[Route("api/[controller]")]
[ApiController]
public class PasswordController : ControllerBase {

View File

@ -12,7 +12,7 @@ namespace WeatherForecast.Controllers;
/// <summary>
///
/// </summary>
[Authorize]
[Authorize(Policy = "WhitelistToken")]
[AllowAnonymous]
[Route("api/[controller]")]
public class ShopCartItemController : ControllerBase {

View File

@ -11,7 +11,7 @@ namespace WeatherForecast.Controllers;
/// <summary>
///
/// </summary>
[Authorize]
[Authorize(Policy = "WhitelistToken")]
[AllowAnonymous]
[Route("api/[controller]")]
public class ShopCartItemsController : ControllerBase {

View File

@ -11,7 +11,7 @@ namespace WeatherForecast.Controllers;
/// <summary>
///
/// </summary>
[Authorize]
[Authorize(Policy = "WhitelistToken")]
[ApiController]
[Route("api/[controller]")]
public class ShopItemController : ControllerBase {

View File

@ -10,7 +10,7 @@ namespace WeatherForecast.Controllers;
/// <summary>
///
/// </summary>
[Authorize]
[Authorize(Policy = "WhitelistToken")]
[ApiController]
[Route("api/[controller]")]
public class ShopItemsController : ControllerBase {

View File

@ -6,7 +6,7 @@ namespace WeatherForecast.Controllers;
/// <summary>
///
/// </summary>
[Authorize]
[Authorize(Policy = "WhitelistToken")]
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase {

View File

@ -0,0 +1,55 @@
using Extensions;
using Microsoft.AspNetCore.Authorization;
using WeatherForecast.Services;
namespace WeatherForecast.Policies {
/// <summary>
///
/// </summary>
public class WhitelistTokenRequirement : IAuthorizationRequirement {
// public string WhiteListToken { get; }
/// <summary>
///
/// </summary>
public WhitelistTokenRequirement() {
// WhiteListToken = whiteListToken;
}
}
/// <summary>
///
/// </summary>
public class WhitelistTokenHandler : AuthorizationHandler<WhitelistTokenRequirement> {
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAuthenticationService _athenticationService;
/// <summary>
///
/// </summary>
/// <param name="httpContextAccessor"></param>
/// <param name="athenticationService"></param>
public WhitelistTokenHandler(
IHttpContextAccessor httpContextAccessor,
IAuthenticationService athenticationService
) {
_httpContextAccessor = httpContextAccessor;
_athenticationService = athenticationService;
}
/// <summary>
///
/// </summary>
/// <param name="context"></param>
/// <param name="requirement"></param>
/// <returns></returns>
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, WhitelistTokenRequirement requirement) {
var request = _httpContextAccessor?.HttpContext?.Request;
if (request != null && _athenticationService.Get(request.GeBearerToken()).IsSuccess)
context.Succeed(requirement);
return Task.CompletedTask;
}
}
}

View File

@ -3,9 +3,10 @@ using Core.DomainObjects;
using DataProviders.Collections;
using DomainResults.Common;
using ExtensionMethods;
using HashService;
using CryptoProvider;
using JWTService;
using WeatherForecast.Models.Requests;
using Microsoft.Extensions.Options;
namespace WeatherForecast.Services {
@ -17,10 +18,16 @@ namespace WeatherForecast.Services {
/// <summary>
///
/// </summary>
/// <param name="siteId"></param>
/// <param name="requestData"></param>
/// <returns></returns>
(string?, IDomainResult) Post(Guid siteId, AuthenticationRequestModel requestData);
(string?, IDomainResult) Post(AuthenticationRequestModel requestData);
/// <summary>
///
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
IDomainResult Get(string? token);
}
/// <summary>
@ -28,56 +35,59 @@ namespace WeatherForecast.Services {
/// </summary>
public class AutheticationService : ServiceBase<AutheticationService>, IAuthenticationService {
private readonly IAesKey? _aesKey;
private readonly IUserDataProvider _userDataProvider;
private readonly IHashService _hashService;
private readonly IJWTService _jwtService;
/// <summary>
///
/// </summary>
/// <param name="logger"></param>
/// <param name="options"></param>
/// <param name="userDataProvider"></param>
/// <param name="hashService"></param>
/// <param name="jwtService"></param>
public AutheticationService (
ILogger<AutheticationService> logger,
IOptions<Configuration> options,
IUserDataProvider userDataProvider,
IHashService hashService,
IJWTService jwtService
) : base(logger) {
_aesKey = options.Value.JwtTokenEncryption;
_userDataProvider = userDataProvider;
_hashService = hashService;
_jwtService = jwtService;
}
/// <summary>
///
/// </summary>
/// <param name="siteId"></param>
/// <param name="requestData"></param>
/// <returns></returns>
public (string?, IDomainResult) Post(Guid siteId, AuthenticationRequestModel requestData) {
var opId = Guid.NewGuid();
public (string?, IDomainResult) Post(AuthenticationRequestModel requestData) {
if (_aesKey?.IV == null || _aesKey?.Key == null)
return IDomainResult.Failed<string?>("IV or Key are not set");
// Retrieve user from database by userName
var (user, getUserResult) = _userDataProvider.GetByNickName(requestData.Username);
var (user, getUserResult) = _userDataProvider.GetByUsername(requestData.Username);
if (!getUserResult.IsSuccess || user == null)
return IDomainResult.NotFound<string?>();
return (null, getUserResult);
if (user.Passwords.Password == null)
return IDomainResult.Failed<string?>($"Opid = [{opId}] Password is not set, create new password.");
return IDomainResult.Failed<string?>("Password is not set, create new password.");
// Check provided password hash with the stored one
var (salt, hash) = _hashService.CreateSaltedHash(requestData.Password);
if (!_hashService.ValidateHash(requestData.Password, salt, hash))
var (salt, hash) = HashService.CreateSaltedHash(requestData.Password);
if (!HashService.ValidateHash(requestData.Password, salt, hash))
return IDomainResult.Unauthorized<string?>();
// Check password expiration if enabled
if (user.Passwords.Expiration.Enabled && DateTime.UtcNow > user.Passwords.Password.Created.AddDays(user.Passwords.Expiration.Days)) {
user.Passwords.Expired.Add(user.Passwords.Password.Prototype());
user.Passwords.Password = null;
user.Tokens = new List<Token>();
return IDomainResult.Failed<string?>();
return IDomainResult.Failed<string?>("Password is expired, create new password.");
}
// Creating JWT token
@ -91,11 +101,15 @@ namespace WeatherForecast.Services {
var token = _jwtService.CreateJwtToken(expires, claims);
user.Tokens.Add(new Token {
Value = token,
Value = AesService.EncryptString(_aesKey.IV, _aesKey.Key, token),
Created = created,
Expires = expires,
});
var (_, usdateUserResult) = _userDataProvider.Update(user);
if (!usdateUserResult.IsSuccess)
return IDomainResult.Failed<string?>();
return IDomainResult.Success(token);
}
@ -104,7 +118,12 @@ namespace WeatherForecast.Services {
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public IDomainResult Get(string token) {
public IDomainResult Get(string? token) {
if (_aesKey?.IV == null || _aesKey?.Key == null)
return IDomainResult.Failed("IV or Key are not set");
if (token == null)
return IDomainResult.Failed();
#region Retrieve user id from token claim
var (claims, getClaimsResult) = _jwtService.JwtTokenClaims(token);
@ -112,21 +131,27 @@ namespace WeatherForecast.Services {
return IDomainResult.Failed();
var userId = claims.SingleOrDefault(x => x.Key == "UserId").Value.ToGuid();
if(userId == Guid.Empty)
if (userId == Guid.Empty)
return IDomainResult.Failed();
#endregion
var (user, getUserRersult) = _userDataProvider.Get(userId);
if(!getUserRersult.IsSuccess || user == null)
var (user, getUserResult) = _userDataProvider.Get(userId);
if (!getUserResult.IsSuccess || user == null)
return IDomainResult.Failed();
// remove expired tokens
user.Tokens = user.Tokens.Where(x => x.Expires < DateTime.UtcNow).ToList();
#region Tokens cleanup
var userTokens = user.Tokens.Where(x => x.Expires > DateTime.UtcNow).ToList();
if (!user.Tokens.Any(x => x.Value == token))
return IDomainResult.Failed();
if (user.Tokens.Count != userTokens.Count) {
user.Tokens = userTokens;
_userDataProvider.Update(user);
}
#endregion
return IDomainResult.Success();
return userTokens.Select(x => AesService.DecryptString(_aesKey.IV, _aesKey.Key, x.Value)).Any(x => string.Compare(x, token) == 0)
? IDomainResult.Success()
: IDomainResult.Failed();
}
}
}

View File

@ -2,7 +2,7 @@
using DataProviders.Collections;
using HashService;
using CryptoProvider;
using JWTService;
using WeatherForecast.Models.Requests;
using Core.Abstractions;
@ -27,7 +27,7 @@ namespace WeatherForecast.Services {
/// </summary>
public class PasswordService : ServiceBase<PasswordService>, IPasswordService {
private readonly IHashService _hashService;
private readonly IUserDataProvider _userDataProvider;
/// <summary>
@ -39,11 +39,9 @@ namespace WeatherForecast.Services {
/// <param name="userDataProvider"></param>
public PasswordService(
ILogger<PasswordService> logger,
IHashService hashService,
IUserDataProvider userDataProvider
) : base(logger) {
_hashService = hashService;
_userDataProvider = userDataProvider;
}
@ -58,7 +56,7 @@ namespace WeatherForecast.Services {
try {
var (salt, hash) = _hashService.CreateSaltedHash(requestData.Password);
var (salt, hash) = HashService.CreateSaltedHash(requestData.Password);

View File

@ -9,8 +9,9 @@ using System.Text.Json.Serialization;
using FileSecurityService.Extensions;
using ImageProvider.Extensions;
using JWTService.Extensions;
using HashService.Extensions;
using Core.Middlewares;
using Microsoft.AspNetCore.Authorization;
using WeatherForecast.Policies;
namespace WeatherForecast {
@ -75,6 +76,16 @@ namespace WeatherForecast {
// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-3.1#use-httpcontext-from-custom-components
services.AddHttpContextAccessor();
#region Policy Authorizations https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-6.0
services.AddScoped<IAuthorizationHandler, WhitelistTokenHandler>();
// services.AddScoped<IAuthorizationHandler, RecaptchaTokenHandler>();
services.AddAuthorization(options => {
options.AddPolicy("WhitelistToken", policy => policy.Requirements.Add(new WhitelistTokenRequirement()));
// options.AddPolicy("RecaptchaToken", policy => policy.Requirements.Add(new RecaptchaTokenRequirement("/swagger/index.html")));
});
#endregion
services.AddScoped<IContentService, ContentService>();
services.AddScoped<IShopItemService, ShopItemService>();
services.AddScoped<IShopItemsService, ShopItemsService>();
@ -96,7 +107,6 @@ namespace WeatherForecast {
services.RegisterFileSecurityService();
services.RegisterImageProvider();
services.RegisterJWTService(appSettings);
services.RegisterHashService();
#region Swagger
services.ConfigureSwaggerGen(options => {

View File

@ -14,24 +14,24 @@
<ItemGroup>
<PackageReference Include="DomainResult" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.5" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.14.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.10" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageReference Include="Serilog.Enrichers.Span" Version="2.3.0" />
<PackageReference Include="Serilog.Expressions" Version="3.4.0" />
<PackageReference Include="Serilog.Expressions" Version="3.4.1" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\CryptoProvider\CryptoProvider.csproj" />
<ProjectReference Include="..\DataProviders\DataProviders.csproj" />
<ProjectReference Include="..\Extensions\Extensions.csproj" />
<ProjectReference Include="..\ImageProvider\ImageProvider.csproj" />
<ProjectReference Include="..\Services\FileSecurityService\FileSecurityService.csproj" />
<ProjectReference Include="..\Services\HashService\HashService.csproj" />
<ProjectReference Include="..\Services\ImageProvider\ImageProvider.csproj" />
<ProjectReference Include="..\Services\JWTService\JWTService.csproj" />
</ItemGroup>

View File

@ -20,6 +20,11 @@
"Expires": "365"
},
"JwtTokenEncryption": {
"IV": "2YJIwoV2C7QpgN8dquznmw==",
"Key": "KBsLNL2/pju3uX6Wtjkf2zUS1TbJ0YiD84zusIyPVUM="
},
"Database": {
"ConnectionString": "mongodb://root:example@mongo:27017"