server cleanup
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Core" />
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Stores" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\Baya.Application\Baya.Application.csproj" />
|
||||
<ProjectReference Include="..\..\Core\Baya.Domain\Baya.Domain.csproj" />
|
||||
<ProjectReference Include="..\Baya.Infrastructure.Persistence\Baya.Infrastructure.Persistence.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,80 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity;
|
||||
|
||||
public class AppErrorDescriber:IdentityErrorDescriber
|
||||
{
|
||||
public override IdentityError DefaultError()
|
||||
{
|
||||
return new IdentityError
|
||||
{
|
||||
Code = "DefaultError",
|
||||
Description = "There was an error"
|
||||
};
|
||||
}
|
||||
|
||||
public override IdentityError DuplicateEmail(string email)
|
||||
{
|
||||
return new IdentityError
|
||||
{
|
||||
Code = nameof(DuplicateEmail),
|
||||
Description = "Specified email already exists"
|
||||
};
|
||||
}
|
||||
|
||||
public override IdentityError DuplicateUserName(string userName)
|
||||
{
|
||||
return new IdentityError
|
||||
{
|
||||
Code = nameof(DuplicateUserName),
|
||||
Description = "specified username already exists"
|
||||
};
|
||||
}
|
||||
|
||||
public override IdentityError PasswordMismatch()
|
||||
{
|
||||
return new IdentityError
|
||||
{
|
||||
Code = nameof(PasswordMismatch),
|
||||
Description = "Incorrect password"
|
||||
};
|
||||
}
|
||||
|
||||
public override IdentityError PasswordTooShort(int length)
|
||||
{
|
||||
return new IdentityError
|
||||
{
|
||||
Code = nameof(PasswordTooShort),
|
||||
Description = "Invalid password. Password is to short"
|
||||
};
|
||||
}
|
||||
|
||||
public override IdentityError InvalidUserName(string userName)
|
||||
{
|
||||
return new IdentityError
|
||||
{
|
||||
Code = nameof(InvalidUserName),
|
||||
Description = "Invalid username"
|
||||
};
|
||||
}
|
||||
|
||||
public override IdentityError InvalidEmail(string email)
|
||||
{
|
||||
return new IdentityError
|
||||
{
|
||||
Code = nameof(InvalidEmail),
|
||||
Description = "Invalid email"
|
||||
};
|
||||
}
|
||||
|
||||
public override IdentityError InvalidToken()
|
||||
{
|
||||
|
||||
return new IdentityError
|
||||
{
|
||||
Code = nameof(InvalidToken),
|
||||
Description = "Invalid given code. Please try again"
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using System.Security.Claims;
|
||||
using Baya.Domain.Entities.User;
|
||||
using Baya.Infrastructure.Identity.Identity.Manager;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity;
|
||||
|
||||
public class AppUserClaimsPrincipleFactory:UserClaimsPrincipalFactory<User,Role>
|
||||
{
|
||||
public AppUserClaimsPrincipleFactory(AppUserManager userManager, AppRoleManager roleManager, IOptions<IdentityOptions> options) : base(userManager, roleManager, options)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(User user)
|
||||
{
|
||||
var userRoles = await UserManager.GetRolesAsync(user);
|
||||
|
||||
var claimsIdentity = await base.GenerateClaimsAsync(user);
|
||||
//claimsIdentity.AddClaim(new Claim(ClaimTypes.Email,user?.Email));
|
||||
// claimsIdentity.AddClaim(new Claim(ClaimTypes.MobilePhone,user.PhoneNumber));
|
||||
claimsIdentity.AddClaim(new Claim(ClaimTypes.UserData,user.GeneratedCode));
|
||||
|
||||
foreach (var roles in userRoles)
|
||||
{
|
||||
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role,roles));
|
||||
}
|
||||
|
||||
//claimsIdentity.AddClaim(new Claim(ClaimTypes.Role,RoleManager.GetRoleNameAsync(user.Roles)));
|
||||
|
||||
return claimsIdentity;
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.DataProtection;
|
||||
|
||||
public class KeyRing : ILookupProtectorKeyRing
|
||||
{
|
||||
private readonly IDictionary<string, string> _keyDictionary = new Dictionary<string, string>();
|
||||
|
||||
public KeyRing(IWebHostEnvironment hostingEnvironment)
|
||||
{
|
||||
// Create the keyring directory if one doesn't exist.
|
||||
var keyRingDirectory = Path.Combine(hostingEnvironment.ContentRootPath, "keyring");
|
||||
Directory.CreateDirectory(keyRingDirectory);
|
||||
|
||||
var directoryInfo = new DirectoryInfo(keyRingDirectory);
|
||||
if (directoryInfo.GetFiles("*.key").Length == 0)
|
||||
{
|
||||
ProtectorAlgorithmHelper.GetAlgorithms(
|
||||
ProtectorAlgorithmHelper.DefaultAlgorithm,
|
||||
out SymmetricAlgorithm encryptionAlgorithm,
|
||||
out KeyedHashAlgorithm signingAlgorithm,
|
||||
out int derivationCount);
|
||||
encryptionAlgorithm.GenerateKey();
|
||||
|
||||
var keyAsString = Convert.ToBase64String(encryptionAlgorithm.Key);
|
||||
var keyId = Guid.NewGuid().ToString();
|
||||
var keyFileName = Path.Combine(keyRingDirectory, keyId+".key");
|
||||
using (var file = File.CreateText(keyFileName))
|
||||
{
|
||||
file.WriteLine(keyAsString);
|
||||
}
|
||||
|
||||
_keyDictionary.Add(keyId, keyAsString);
|
||||
|
||||
CurrentKeyId = keyId;
|
||||
|
||||
encryptionAlgorithm.Clear();
|
||||
encryptionAlgorithm.Dispose();
|
||||
signingAlgorithm.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
var filesOrdered = directoryInfo.EnumerateFiles()
|
||||
.OrderByDescending(d => d.CreationTime)
|
||||
.Select(d => d.Name)
|
||||
.ToList();
|
||||
|
||||
foreach (var fileName in filesOrdered)
|
||||
{
|
||||
var keyFileName = Path.Combine(keyRingDirectory, fileName);
|
||||
var key = File.ReadAllText(keyFileName);
|
||||
var keyId = Path.GetFileNameWithoutExtension(fileName);
|
||||
_keyDictionary.Add(keyId, key);
|
||||
CurrentKeyId = keyId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string this[string keyId]
|
||||
{
|
||||
get
|
||||
{
|
||||
return _keyDictionary[keyId];
|
||||
}
|
||||
}
|
||||
|
||||
public string CurrentKeyId
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetAllKeyIds()
|
||||
{
|
||||
return _keyDictionary.Keys;
|
||||
}
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.DataProtection;
|
||||
|
||||
public class LookupProtector : ILookupProtector
|
||||
{
|
||||
private readonly ProtectorAlgorithm _defaultAlgorithm = ProtectorAlgorithm.Aes256Hmac512;
|
||||
private readonly ILookupProtectorKeyRing _keyRing;
|
||||
|
||||
public LookupProtector(ILookupProtectorKeyRing keyRing)
|
||||
{
|
||||
_keyRing = keyRing;
|
||||
}
|
||||
|
||||
public string Protect(string keyId, string data)
|
||||
{
|
||||
// Get the default algorithms.
|
||||
// We does this so we can embed the algorithm details used in the cipher text so we can
|
||||
// change algorithms as yet another collision appears in a hashing algorithm.
|
||||
// See https://media.blackhat.com/bh-us-10/whitepapers/Sullivan/BlackHat-USA-2010-Sullivan-Cryptographic-Agility-wp.pdf
|
||||
ProtectorAlgorithmHelper.GetAlgorithms(
|
||||
_defaultAlgorithm,
|
||||
out SymmetricAlgorithm encryptingAlgorithm,
|
||||
out KeyedHashAlgorithm signingAlgorithm,
|
||||
out int keyDerivationIterationCount);
|
||||
|
||||
var masterKey = MasterKey(keyId);
|
||||
|
||||
// Convert the string to bytes, because encryption works on bytes, not strings.
|
||||
var plainText = Encoding.UTF8.GetBytes(data);
|
||||
byte[] cipherTextAndIV;
|
||||
|
||||
// Derive a key for encryption from the master key
|
||||
encryptingAlgorithm.Key = DerivedEncryptionKey(
|
||||
masterKey,
|
||||
encryptingAlgorithm,
|
||||
keyDerivationIterationCount);
|
||||
|
||||
// As we need this to be deterministic, we need to force an IV that is derived from the plain text.
|
||||
encryptingAlgorithm.IV = DerivedInitializationVector(
|
||||
masterKey,
|
||||
data,
|
||||
encryptingAlgorithm,
|
||||
keyDerivationIterationCount);
|
||||
|
||||
// And encrypt
|
||||
using (var ms = new MemoryStream())
|
||||
using (var cs = new CryptoStream(
|
||||
ms,
|
||||
encryptingAlgorithm.CreateEncryptor(),
|
||||
CryptoStreamMode.Write))
|
||||
{
|
||||
cs.Write(plainText);
|
||||
cs.FlushFinalBlock();
|
||||
var encryptedData = ms.ToArray();
|
||||
|
||||
cipherTextAndIV = CombineByteArrays(encryptingAlgorithm.IV, encryptedData);
|
||||
}
|
||||
|
||||
// Now get a signature for the data so we can detect tampering in situ.
|
||||
byte[] signature = SignData(
|
||||
cipherTextAndIV,
|
||||
masterKey,
|
||||
encryptingAlgorithm,
|
||||
signingAlgorithm,
|
||||
keyDerivationIterationCount);
|
||||
|
||||
// Add the signature to the cipher text.
|
||||
var signedData = CombineByteArrays(signature, cipherTextAndIV);
|
||||
|
||||
// Add our algorithm identifier to the combined signature and cipher text.
|
||||
var algorithmIdentifier = BitConverter.GetBytes((int)_defaultAlgorithm);
|
||||
byte[] output = CombineByteArrays(algorithmIdentifier, signedData);
|
||||
|
||||
// Clean everything up.
|
||||
encryptingAlgorithm.Clear();
|
||||
signingAlgorithm.Clear();
|
||||
encryptingAlgorithm.Dispose();
|
||||
signingAlgorithm.Dispose();
|
||||
|
||||
Array.Clear(plainText, 0, plainText.Length);
|
||||
|
||||
// Return the results as a string.
|
||||
return Convert.ToBase64String(output);
|
||||
}
|
||||
|
||||
public string Unprotect(string keyId, string data)
|
||||
{
|
||||
var masterKey = MasterKey(keyId);
|
||||
byte[] plainText;
|
||||
|
||||
// Take our string and convert it back to bytes.
|
||||
var payload = Convert.FromBase64String(data);
|
||||
|
||||
// Read the saved algorithm details and create instances of those algorithms.
|
||||
byte[] algorithmIdentifierAsBytes = new byte[4];
|
||||
Buffer.BlockCopy(payload, 0, algorithmIdentifierAsBytes, 0, 4);
|
||||
var algorithmIdentifier = (ProtectorAlgorithm)(BitConverter.ToInt32(algorithmIdentifierAsBytes, 0));
|
||||
ProtectorAlgorithmHelper.GetAlgorithms(
|
||||
_defaultAlgorithm,
|
||||
out SymmetricAlgorithm encryptingAlgorithm,
|
||||
out KeyedHashAlgorithm signingAlgorithm,
|
||||
out int keyDerivationIterationCount);
|
||||
|
||||
// Now extract the signature
|
||||
byte[] signature = new byte[signingAlgorithm.HashSize / 8];
|
||||
Buffer.BlockCopy(payload, 4, signature, 0, signingAlgorithm.HashSize / 8);
|
||||
|
||||
// And finally grab the rest of the data
|
||||
var dataLength = payload.Length - 4 - signature.Length;
|
||||
byte[] cipherTextAndIV = new byte[dataLength];
|
||||
Buffer.BlockCopy(payload, 4 + signature.Length, cipherTextAndIV, 0, dataLength);
|
||||
|
||||
// Check the signature before anything else is done to detect tampering and avoid
|
||||
// oracles.
|
||||
byte[] computedSignature = SignData(
|
||||
cipherTextAndIV,
|
||||
masterKey,
|
||||
encryptingAlgorithm,
|
||||
signingAlgorithm,
|
||||
keyDerivationIterationCount);
|
||||
if (!ByteArraysEqual(computedSignature, signature))
|
||||
{
|
||||
throw new CryptographicException(@"Invalid Signature.");
|
||||
}
|
||||
signingAlgorithm.Clear();
|
||||
signingAlgorithm.Dispose();
|
||||
|
||||
// The signature is valid, so now we can work on decrypting the data.
|
||||
var ivLength = encryptingAlgorithm.BlockSize / 8;
|
||||
byte[] initializationVector = new byte[ivLength];
|
||||
byte[] cipherText = new byte[cipherTextAndIV.Length - ivLength];
|
||||
// The IV is embedded in the cipher text, so we extract it out.
|
||||
Buffer.BlockCopy(cipherTextAndIV, 0, initializationVector, 0, ivLength);
|
||||
// Then we get the encrypted data.
|
||||
Buffer.BlockCopy(cipherTextAndIV, ivLength, cipherText, 0, cipherTextAndIV.Length - ivLength);
|
||||
|
||||
encryptingAlgorithm.Key = DerivedEncryptionKey(
|
||||
masterKey,
|
||||
encryptingAlgorithm,
|
||||
keyDerivationIterationCount);
|
||||
encryptingAlgorithm.IV = initializationVector;
|
||||
|
||||
// Decrypt
|
||||
using (var ms = new MemoryStream())
|
||||
using (var cs = new CryptoStream(ms, encryptingAlgorithm.CreateDecryptor(), CryptoStreamMode.Write))
|
||||
{
|
||||
cs.Write(cipherText);
|
||||
cs.FlushFinalBlock();
|
||||
plainText = ms.ToArray();
|
||||
}
|
||||
encryptingAlgorithm.Clear();
|
||||
encryptingAlgorithm.Dispose();
|
||||
|
||||
// And convert from the bytes back to a string.
|
||||
return Encoding.UTF8.GetString(plainText);
|
||||
}
|
||||
|
||||
public byte[] MasterKey(string keyId)
|
||||
{
|
||||
return Convert.FromBase64String(_keyRing[keyId]);
|
||||
}
|
||||
|
||||
private byte[] SignData(byte[] cipherText, byte[] masterKey, SymmetricAlgorithm symmetricAlgorithm, KeyedHashAlgorithm hashAlgorithm, int keyDerivationIterationCount)
|
||||
{
|
||||
hashAlgorithm.Key = DerivedSigningKey(masterKey, symmetricAlgorithm, keyDerivationIterationCount);
|
||||
byte[] signature = hashAlgorithm.ComputeHash(cipherText);
|
||||
hashAlgorithm.Clear();
|
||||
return signature;
|
||||
}
|
||||
|
||||
private byte[] DerivedSigningKey(byte[] key, SymmetricAlgorithm algorithm, int keyDerivationIterationCount)
|
||||
{
|
||||
return KeyDerivation.Pbkdf2(
|
||||
@"IdentityLookupDataSigning",
|
||||
key,
|
||||
KeyDerivationPrf.HMACSHA512,
|
||||
keyDerivationIterationCount,
|
||||
algorithm.KeySize / 8);
|
||||
}
|
||||
|
||||
private byte[] DerivedEncryptionKey(byte[] key, SymmetricAlgorithm algorithm, int keyDerivationIterationCount)
|
||||
{
|
||||
return KeyDerivation.Pbkdf2(
|
||||
@"IdentityLookupEncryption",
|
||||
key,
|
||||
KeyDerivationPrf.HMACSHA512,
|
||||
keyDerivationIterationCount,
|
||||
algorithm.KeySize / 8);
|
||||
}
|
||||
|
||||
private byte[] DerivedInitializationVector(byte[] key, string plainText, SymmetricAlgorithm algorithm, int keyDerivationIterationCount)
|
||||
{
|
||||
return KeyDerivation.Pbkdf2(
|
||||
plainText,
|
||||
key,
|
||||
KeyDerivationPrf.HMACSHA512,
|
||||
keyDerivationIterationCount,
|
||||
algorithm.BlockSize / 8);
|
||||
}
|
||||
|
||||
private byte[] CombineByteArrays(byte[] left, byte[] right)
|
||||
{
|
||||
byte[] output = new byte[left.Length + right.Length];
|
||||
Buffer.BlockCopy(left, 0, output, 0, left.Length);
|
||||
Buffer.BlockCopy(right, 0, output, left.Length, right.Length);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoOptimization)]
|
||||
private static bool ByteArraysEqual(byte[] a, byte[] b)
|
||||
{
|
||||
if (ReferenceEquals(a, b))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a == null || b == null || a.Length != b.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool areSame = true;
|
||||
for (int i = 0; i < a.Length; i++)
|
||||
{
|
||||
areSame &= (a[i] == b[i]);
|
||||
}
|
||||
return areSame;
|
||||
}
|
||||
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.DataProtection;
|
||||
|
||||
public class PersonalDataProtector : IPersonalDataProtector
|
||||
{
|
||||
private readonly ProtectorAlgorithm _defaultAlgorithm = ProtectorAlgorithm.Aes256Hmac512;
|
||||
private readonly ILookupProtectorKeyRing _keyRing;
|
||||
|
||||
public PersonalDataProtector(ILookupProtectorKeyRing keyRing)
|
||||
{
|
||||
_keyRing = keyRing;
|
||||
}
|
||||
|
||||
public string Protect(string data)
|
||||
{
|
||||
// Get the default algorithms.
|
||||
// We does this so we can embed the algorithm details used in the cipher text so we can
|
||||
// change algorithms as yet another collision appears in a hashing algorithm.
|
||||
// See https://media.blackhat.com/bh-us-10/whitepapers/Sullivan/BlackHat-USA-2010-Sullivan-Cryptographic-Agility-wp.pdf
|
||||
ProtectorAlgorithmHelper.GetAlgorithms(
|
||||
_defaultAlgorithm,
|
||||
out SymmetricAlgorithm encryptingAlgorithm,
|
||||
out KeyedHashAlgorithm signingAlgorithm,
|
||||
out int keyDerivationIterationCount);
|
||||
|
||||
// Use the newest key from the keyring.
|
||||
// We know this is a guid, because that's how our keyring works underneath,
|
||||
// so we can prepend this later to the result.
|
||||
string keyId = _keyRing.CurrentKeyId;
|
||||
var masterKey = Key(keyId);
|
||||
|
||||
// Convert the string to bytes, because encryption works on bytes, not strings.
|
||||
var plainText = Encoding.UTF8.GetBytes(data);
|
||||
byte[] cipherTextAndIV;
|
||||
|
||||
// Derive a key for encryption from the master key
|
||||
encryptingAlgorithm.Key = DerivedEncryptionKey(
|
||||
masterKey,
|
||||
encryptingAlgorithm,
|
||||
keyDerivationIterationCount);
|
||||
|
||||
// When the underlying encryption class is created it has a random IV by default
|
||||
// So we don't need to do anything IV wise.
|
||||
|
||||
// And encrypt
|
||||
using (var ms = new MemoryStream())
|
||||
using (var cs = new CryptoStream(
|
||||
ms,
|
||||
encryptingAlgorithm.CreateEncryptor(),
|
||||
CryptoStreamMode.Write))
|
||||
{
|
||||
cs.Write(plainText);
|
||||
cs.FlushFinalBlock();
|
||||
var encryptedData = ms.ToArray();
|
||||
|
||||
cipherTextAndIV = CombineByteArrays(encryptingAlgorithm.IV, encryptedData);
|
||||
}
|
||||
|
||||
// Now get a signature for the data so we can detect tampering in situ.
|
||||
byte[] signature = SignData(
|
||||
cipherTextAndIV,
|
||||
masterKey,
|
||||
encryptingAlgorithm,
|
||||
signingAlgorithm,
|
||||
keyDerivationIterationCount);
|
||||
|
||||
// Add the signature to the cipher text.
|
||||
var signedData = CombineByteArrays(signature, cipherTextAndIV);
|
||||
|
||||
// Add our algorithm identifier to the combined signature and cipher text.
|
||||
var algorithmIdentifier = BitConverter.GetBytes((int)_defaultAlgorithm);
|
||||
byte[] dataPlusAlgorithmId = CombineByteArrays(algorithmIdentifier, signedData);
|
||||
|
||||
// Now we need to put our key identifier in. In our implementation this is a GUID
|
||||
// so let's convert it back to one, then turn it to bytes.
|
||||
byte[] output = CombineByteArrays(Guid.Parse(keyId).ToByteArray(), dataPlusAlgorithmId);
|
||||
|
||||
// Clean everything up.
|
||||
encryptingAlgorithm.Clear();
|
||||
signingAlgorithm.Clear();
|
||||
encryptingAlgorithm.Dispose();
|
||||
signingAlgorithm.Dispose();
|
||||
|
||||
Array.Clear(plainText, 0, plainText.Length);
|
||||
|
||||
// Return the results as a string.
|
||||
return Convert.ToBase64String(output);
|
||||
}
|
||||
|
||||
public string Unprotect(string data)
|
||||
{
|
||||
byte[] plainText;
|
||||
|
||||
// Take our string and convert it back to bytes.
|
||||
var payload = Convert.FromBase64String(data);
|
||||
|
||||
var offset = 0;
|
||||
|
||||
// First we extract our key ID and then the appropriate key.
|
||||
byte[] keyIdAsBytes = new byte[16];
|
||||
Buffer.BlockCopy(payload, offset, keyIdAsBytes, 0, 16);
|
||||
var keyIdAsGuid = new Guid(keyIdAsBytes);
|
||||
var keyId = keyIdAsGuid.ToString();
|
||||
var masterKey = Key(keyId);
|
||||
offset = 16;
|
||||
|
||||
// Next read the saved algorithm details and create instances of those algorithms.
|
||||
byte[] algorithmIdentifierAsBytes = new byte[4];
|
||||
Buffer.BlockCopy(payload, offset, algorithmIdentifierAsBytes, 0, 4);
|
||||
var algorithmIdentifier = (ProtectorAlgorithm)(BitConverter.ToInt32(algorithmIdentifierAsBytes, 0));
|
||||
ProtectorAlgorithmHelper.GetAlgorithms(
|
||||
_defaultAlgorithm,
|
||||
out SymmetricAlgorithm encryptingAlgorithm,
|
||||
out KeyedHashAlgorithm signingAlgorithm,
|
||||
out int keyDerivationIterationCount);
|
||||
offset += 4;
|
||||
|
||||
// Now extract the signature
|
||||
byte[] signature = new byte[signingAlgorithm.HashSize / 8];
|
||||
Buffer.BlockCopy(payload, offset, signature, 0, signingAlgorithm.HashSize / 8);
|
||||
offset += signature.Length;
|
||||
|
||||
// And finally grab the rest of the data
|
||||
var dataLength = payload.Length - offset;
|
||||
byte[] cipherTextAndIV = new byte[dataLength];
|
||||
Buffer.BlockCopy(payload, offset, cipherTextAndIV, 0, dataLength);
|
||||
|
||||
// Check the signature before anything else is done to detect tampering and avoid
|
||||
// oracles.
|
||||
byte[] computedSignature = SignData(
|
||||
cipherTextAndIV,
|
||||
masterKey,
|
||||
encryptingAlgorithm,
|
||||
signingAlgorithm,
|
||||
keyDerivationIterationCount);
|
||||
if (!ByteArraysEqual(computedSignature, signature))
|
||||
{
|
||||
throw new CryptographicException(@"Invalid Signature.");
|
||||
}
|
||||
signingAlgorithm.Clear();
|
||||
signingAlgorithm.Dispose();
|
||||
|
||||
// The signature is valid, so now we can work on decrypting the data.
|
||||
var ivLength = encryptingAlgorithm.BlockSize / 8;
|
||||
byte[] initializationVector = new byte[ivLength];
|
||||
byte[] cipherText = new byte[cipherTextAndIV.Length - ivLength];
|
||||
// The IV is embedded in the cipher text, so we extract it out.
|
||||
Buffer.BlockCopy(cipherTextAndIV, 0, initializationVector, 0, ivLength);
|
||||
// Then we get the encrypted data.
|
||||
Buffer.BlockCopy(cipherTextAndIV, ivLength, cipherText, 0, cipherTextAndIV.Length - ivLength);
|
||||
|
||||
encryptingAlgorithm.Key = DerivedEncryptionKey(
|
||||
masterKey,
|
||||
encryptingAlgorithm,
|
||||
keyDerivationIterationCount);
|
||||
encryptingAlgorithm.IV = initializationVector;
|
||||
|
||||
// Decrypt
|
||||
using (var ms = new MemoryStream())
|
||||
using (var cs = new CryptoStream(ms, encryptingAlgorithm.CreateDecryptor(), CryptoStreamMode.Write))
|
||||
{
|
||||
cs.Write(cipherText);
|
||||
cs.FlushFinalBlock();
|
||||
plainText = ms.ToArray();
|
||||
}
|
||||
encryptingAlgorithm.Clear();
|
||||
encryptingAlgorithm.Dispose();
|
||||
|
||||
// And convert from the bytes back to a string.
|
||||
return Encoding.UTF8.GetString(plainText);
|
||||
}
|
||||
|
||||
public byte[] Key(string keyId)
|
||||
{
|
||||
return Convert.FromBase64String(_keyRing[keyId]);
|
||||
}
|
||||
|
||||
private byte[] SignData(byte[] cipherText, byte[] masterKey, SymmetricAlgorithm symmetricAlgorithm, KeyedHashAlgorithm hashAlgorithm, int keyDerivationIterationCount)
|
||||
{
|
||||
hashAlgorithm.Key = DerivedSigningKey(masterKey, symmetricAlgorithm, keyDerivationIterationCount);
|
||||
byte[] signature = hashAlgorithm.ComputeHash(cipherText);
|
||||
hashAlgorithm.Clear();
|
||||
return signature;
|
||||
}
|
||||
|
||||
private byte[] DerivedSigningKey(byte[] key, SymmetricAlgorithm algorithm, int keyDerivationIterationCount)
|
||||
{
|
||||
return KeyDerivation.Pbkdf2(
|
||||
@"PersonalDataSigning",
|
||||
key,
|
||||
KeyDerivationPrf.HMACSHA512,
|
||||
keyDerivationIterationCount,
|
||||
algorithm.KeySize / 8);
|
||||
}
|
||||
|
||||
private byte[] DerivedEncryptionKey(byte[] key, SymmetricAlgorithm algorithm, int keyDerivationIterationCount)
|
||||
{
|
||||
return KeyDerivation.Pbkdf2(
|
||||
@"PersonalDataEncryption",
|
||||
key,
|
||||
KeyDerivationPrf.HMACSHA512,
|
||||
keyDerivationIterationCount,
|
||||
algorithm.KeySize / 8);
|
||||
}
|
||||
|
||||
private byte[] CombineByteArrays(byte[] left, byte[] right)
|
||||
{
|
||||
byte[] output = new byte[left.Length + right.Length];
|
||||
Buffer.BlockCopy(left, 0, output, 0, left.Length);
|
||||
Buffer.BlockCopy(right, 0, output, left.Length, right.Length);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoOptimization)]
|
||||
private static bool ByteArraysEqual(byte[] a, byte[] b)
|
||||
{
|
||||
if (ReferenceEquals(a, b))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a == null || b == null || a.Length != b.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool areSame = true;
|
||||
for (int i = 0; i < a.Length; i++)
|
||||
{
|
||||
areSame &= (a[i] == b[i]);
|
||||
}
|
||||
return areSame;
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.DataProtection;
|
||||
|
||||
public enum ProtectorAlgorithm
|
||||
{
|
||||
Aes256Hmac512 = 1
|
||||
}
|
||||
|
||||
public static class ProtectorAlgorithmHelper
|
||||
{
|
||||
public static ProtectorAlgorithm DefaultAlgorithm
|
||||
{
|
||||
get { return ProtectorAlgorithm.Aes256Hmac512; }
|
||||
}
|
||||
|
||||
public static void GetAlgorithms(
|
||||
ProtectorAlgorithm algorithmId,
|
||||
out SymmetricAlgorithm encryptionAlgorithm,
|
||||
out KeyedHashAlgorithm signingAlgorithm,
|
||||
out int keyDerivationIterationCount)
|
||||
{
|
||||
switch (algorithmId)
|
||||
{
|
||||
case ProtectorAlgorithm.Aes256Hmac512:
|
||||
encryptionAlgorithm = Aes.Create();
|
||||
encryptionAlgorithm.KeySize = 256;
|
||||
signingAlgorithm = new HMACSHA512();
|
||||
keyDerivationIterationCount = 10000;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(algorithmId));
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
namespace Baya.Infrastructure.Identity.Identity.Dtos
|
||||
{
|
||||
internal class CustomIdentityConstants
|
||||
{
|
||||
public const string OtpPasswordLessLoginProvider = "PasswordlessLoginTotpProvider";
|
||||
public const string OtpPasswordLessLoginPurpose = "passwordless-auth";
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
namespace Baya.Infrastructure.Identity.Identity.Dtos;
|
||||
|
||||
public class IdentitySettings
|
||||
{
|
||||
public string SecretKey { get; set; }
|
||||
public string Encryptkey { get; set; }
|
||||
public string Issuer { get; set; }
|
||||
public string Audience { get; set; }
|
||||
public int NotBeforeMinutes { get; set; }
|
||||
public int ExpirationMinutes { get; set; }
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.Extensions;
|
||||
|
||||
public class DataProtectionTokenProviderOptions
|
||||
{
|
||||
public string Name { get; set; } = "DataProtectorTokenProvider";
|
||||
public TimeSpan TokenLifespan { get; set; }
|
||||
}
|
||||
|
||||
public static class CustomIdentityExtensions
|
||||
{
|
||||
public static IdentityBuilder AddPasswordlessLoginTotpTokenProvider(this IdentityBuilder builder)
|
||||
{
|
||||
var userType = builder.UserType;
|
||||
var totpProvider = typeof(PasswordlessLoginTotpTokenProvider<>).MakeGenericType(userType);
|
||||
return builder.AddTokenProvider("PasswordlessLoginTotpProvider", totpProvider);
|
||||
}
|
||||
}
|
||||
|
||||
public class PasswordlessLoginTotpTokenProvider<TUser> : TotpSecurityStampBasedTokenProvider<TUser>
|
||||
where TUser : class
|
||||
{
|
||||
public override Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<TUser> manager, TUser user)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
public override async Task<string> GetUserModifierAsync(string purpose, UserManager<TUser> manager, TUser user)
|
||||
{
|
||||
var phone = await manager.GetPhoneNumberAsync(user);
|
||||
return "PasswordlessLogin:" + purpose + ":" + phone;
|
||||
}
|
||||
}
|
||||
|
||||
public class PasswordlessLoginTokenProviderOptions : DataProtectionTokenProviderOptions
|
||||
{
|
||||
public PasswordlessLoginTokenProviderOptions()
|
||||
{
|
||||
// update the defaults
|
||||
Name = "PasswordlessLoginTokenProvider";
|
||||
TokenLifespan = TimeSpan.FromMinutes(1);
|
||||
}
|
||||
}
|
||||
|
||||
public class PasswordlessLoginTokenProvider<TUser> : DataProtectorTokenProvider<TUser>
|
||||
where TUser : class
|
||||
{
|
||||
public PasswordlessLoginTokenProvider(IDataProtectionProvider dataProtectionProvider, IOptions<Microsoft.AspNetCore.Identity.DataProtectionTokenProviderOptions> options, ILogger<DataProtectorTokenProvider<TUser>> logger) : base(dataProtectionProvider, options,logger)
|
||||
{
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using Baya.Domain.Entities.User;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.Manager;
|
||||
|
||||
public class AppRoleManager:RoleManager<Role>
|
||||
{
|
||||
public AppRoleManager(IRoleStore<Role> store, IEnumerable<IRoleValidator<Role>> roleValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, ILogger<RoleManager<Role>> logger) : base(store, roleValidators, keyNormalizer, errors, logger)
|
||||
{
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
using Baya.Domain.Entities.User;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.Manager;
|
||||
|
||||
public class AppSignInManager : SignInManager<User>
|
||||
{
|
||||
public AppSignInManager(UserManager<User> userManager, IHttpContextAccessor contextAccessor, IUserClaimsPrincipalFactory<User> claimsFactory, IOptions<IdentityOptions> optionsAccessor, ILogger<SignInManager<User>> logger, IAuthenticationSchemeProvider schemes, IUserConfirmation<User> confirmation) : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
|
||||
{
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
using Baya.Domain.Entities.User;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.Manager;
|
||||
|
||||
public class AppUserManager:UserManager<User>
|
||||
{
|
||||
public AppUserManager(IUserStore<User> store, IOptions<IdentityOptions> optionsAccessor, IPasswordHasher<User> passwordHasher, IEnumerable<IUserValidator<User>> userValidators, IEnumerable<IPasswordValidator<User>> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<User>> logger) : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
|
||||
{
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace Baya.Infrastructure.Identity.Identity.PermissionManager;
|
||||
|
||||
public static class ConstantPolicies
|
||||
{
|
||||
public const string DynamicPermission = nameof(DynamicPermission);
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.PermissionManager;
|
||||
|
||||
public class DynamicPermissionRequirement : IAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
|
||||
public class DynamicPermissionHandler : AuthorizationHandler<DynamicPermissionRequirement>
|
||||
{
|
||||
private readonly IDynamicPermissionService _dynamicPermissionService;
|
||||
private readonly IHttpContextAccessor _contextAccessor;
|
||||
|
||||
public DynamicPermissionHandler(
|
||||
IDynamicPermissionService dynamicPermissionService,
|
||||
IHttpContextAccessor contextAccessor
|
||||
)
|
||||
{
|
||||
_dynamicPermissionService = dynamicPermissionService;
|
||||
_contextAccessor = contextAccessor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override Task HandleRequirementAsync(
|
||||
AuthorizationHandlerContext context,
|
||||
DynamicPermissionRequirement requirement)
|
||||
{
|
||||
|
||||
var user = _contextAccessor.HttpContext.User;
|
||||
|
||||
var routeData = _contextAccessor.HttpContext.GetRouteData().Values;
|
||||
|
||||
var controller = routeData["controller"].ToString();
|
||||
|
||||
var action = routeData["action"].ToString();
|
||||
|
||||
var area = routeData["area"]?.ToString();
|
||||
|
||||
if (_dynamicPermissionService.CanAccess(user, area, controller, action))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.PermissionManager;
|
||||
|
||||
public class DynamicPermissionService : IDynamicPermissionService
|
||||
{
|
||||
public bool CanAccess(ClaimsPrincipal user, string area, string controller, string action)
|
||||
{
|
||||
if (user.IsInRole("admin"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
var key = $"{area}:{controller}:";
|
||||
|
||||
var userClaims = user.FindAll(ConstantPolicies.DynamicPermission);
|
||||
|
||||
return userClaims.Any(item => item.Value.Equals(key, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.PermissionManager;
|
||||
|
||||
public interface IDynamicPermissionService
|
||||
{
|
||||
bool CanAccess(ClaimsPrincipal user, string area, string controller, string action);
|
||||
}
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Reflection;
|
||||
using Baya.Application.Contracts.Identity;
|
||||
using Baya.Application.Models.Identity;
|
||||
using Baya.Domain.Entities.User;
|
||||
using Baya.Infrastructure.Identity.Identity.Manager;
|
||||
using Baya.Infrastructure.Persistence;
|
||||
using MapsterMapper;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.PermissionManager;
|
||||
|
||||
internal class RoleManagerService : IRoleManagerService
|
||||
{
|
||||
private readonly AppRoleManager _roleManger;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IActionDescriptorCollectionProvider _actionDescriptor;
|
||||
private readonly EndpointDataSource _endpointDataSource;
|
||||
private readonly AppUserManager _userManager;
|
||||
private readonly ILogger<RoleManagerService> _logger;
|
||||
private readonly ApplicationDbContext _db;
|
||||
|
||||
public RoleManagerService(AppRoleManager roleManger, IMapper mapper, IActionDescriptorCollectionProvider actionDescriptor, AppUserManager userManager, ILogger<RoleManagerService> logger, ApplicationDbContext db, EndpointDataSource endpointDataSource)
|
||||
{
|
||||
_roleManger = roleManger;
|
||||
_mapper = mapper;
|
||||
_actionDescriptor = actionDescriptor;
|
||||
_userManager = userManager;
|
||||
_logger = logger;
|
||||
_db = db;
|
||||
_endpointDataSource = endpointDataSource;
|
||||
}
|
||||
|
||||
public async Task<List<GetRolesDto>> GetRolesAsync()
|
||||
{
|
||||
var result = await _roleManger.Roles.Where(c => !c.Name.Equals("admin")).Select(r => _mapper.Map<Role, GetRolesDto>(r)).ToListAsync();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> CreateRoleAsync(CreateRoleDto model)
|
||||
{
|
||||
var role = new Role
|
||||
{
|
||||
Name = model.RoleName,
|
||||
};
|
||||
|
||||
var result = await _roleManger.CreateAsync(role);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteRoleAsync(int roleId)
|
||||
{
|
||||
var role = await _roleManger.Roles.Include(r => r.Claims)
|
||||
.Include(r => r.Users).FirstOrDefaultAsync(r => r.Id == roleId);
|
||||
|
||||
if (role == null)
|
||||
return false;
|
||||
|
||||
var users = await _userManager.GetUsersInRoleAsync(role.Name);
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
await _userManager.RemoveFromRoleAsync(user, role.Name);
|
||||
await _userManager.UpdateSecurityStampAsync(user);
|
||||
}
|
||||
|
||||
_db.RemoveRange(role.Claims);
|
||||
_db.RemoveRange(role.Users);
|
||||
_db.Remove(role);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task<List<ActionDescriptionDto>> GetPermissionActionsAsync()
|
||||
{
|
||||
|
||||
var actions = new List<ActionDescriptionDto>();
|
||||
|
||||
var actionDescriptors = _actionDescriptor.ActionDescriptors.Items;
|
||||
string controllerName = "";
|
||||
foreach (var actionDescriptor in actionDescriptors)
|
||||
{
|
||||
try
|
||||
{
|
||||
var descriptor = (ControllerActionDescriptor)actionDescriptor;
|
||||
|
||||
var hasPermission = descriptor.ControllerTypeInfo.GetCustomAttribute<AuthorizeAttribute>()?
|
||||
.Policy == ConstantPolicies.DynamicPermission; //||
|
||||
// descriptor.MethodInfo.GetCustomAttribute<AuthorizeAttribute>()?
|
||||
// .Policy == ConstantPolicies.DynamicPermission;
|
||||
|
||||
|
||||
|
||||
if (hasPermission && (controllerName != descriptor.ControllerName))
|
||||
{
|
||||
actions.Add(new ActionDescriptionDto
|
||||
{
|
||||
// ActionName = descriptor.ActionName,
|
||||
ControllerName = descriptor.ControllerName,
|
||||
ActionDisplayName = descriptor.MethodInfo.GetCustomAttribute<DisplayAttribute>()?.Name,
|
||||
AreaName = descriptor.ControllerTypeInfo.GetCustomAttribute<AreaAttribute>()?.RouteValue,
|
||||
ControllerDisplayName = descriptor.ControllerTypeInfo.GetCustomAttribute<DisplayAttribute>()
|
||||
?.Name,
|
||||
ControllerDescription = descriptor.ControllerTypeInfo.GetCustomAttribute<DisplayAttribute>()
|
||||
?.Description
|
||||
});
|
||||
controllerName = descriptor.ControllerName;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(actions);
|
||||
}
|
||||
|
||||
public async Task<RolePermissionDto> GetRolePermissionsAsync(int roleId)
|
||||
{
|
||||
var role = await _roleManger.Roles
|
||||
.Include(x => x.Claims)
|
||||
.SingleOrDefaultAsync(x => x.Id == roleId);
|
||||
|
||||
if (role == null)
|
||||
return null;
|
||||
|
||||
var dynamicActions = await this.GetPermissionActionsAsync();
|
||||
return new RolePermissionDto
|
||||
{
|
||||
Role = role,
|
||||
Actions = dynamicActions
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<bool> ChangeRolePermissionsAsync(EditRolePermissionsDto model)
|
||||
{
|
||||
var role = await _roleManger.Roles
|
||||
.Include(x => x.Claims)
|
||||
.SingleOrDefaultAsync(x => x.Id == model.RoleId);
|
||||
|
||||
if (role == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var selectedPermissions = model.Permissions;
|
||||
|
||||
var roleClaims = role.Claims
|
||||
.Where(x => x.ClaimType == ConstantPolicies.DynamicPermission)
|
||||
.Select(x => x.ClaimValue)
|
||||
.ToList();
|
||||
|
||||
|
||||
// add new permissions
|
||||
var newPermissions = selectedPermissions.Except(roleClaims).ToList();
|
||||
foreach (var permission in newPermissions)
|
||||
{
|
||||
role.Claims.Add(new RoleClaim
|
||||
{
|
||||
ClaimType = ConstantPolicies.DynamicPermission,
|
||||
ClaimValue = permission,
|
||||
CreatedClaim = DateTime.Now,
|
||||
RoleId = role.Id
|
||||
});
|
||||
}
|
||||
|
||||
// remove deleted permissions
|
||||
var removedPermissions = roleClaims.Except(selectedPermissions).ToList();
|
||||
foreach (var permission in removedPermissions)
|
||||
{
|
||||
var roleClaim = role.Claims
|
||||
.SingleOrDefault(x =>
|
||||
x.ClaimType == ConstantPolicies.DynamicPermission &&
|
||||
x.ClaimValue == permission);
|
||||
|
||||
if (roleClaim != null)
|
||||
{
|
||||
_db.RemoveRange(roleClaim);
|
||||
}
|
||||
}
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
var result = await _roleManger.UpdateAsync(role);
|
||||
|
||||
if (result.Succeeded)
|
||||
{
|
||||
var users = await _userManager.GetUsersInRoleAsync(role.Name);
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
await _userManager.UpdateSecurityStampAsync(user);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<Role> GetRoleByIdAsync(int roleId)
|
||||
{
|
||||
return await _roleManger.FindByIdAsync(roleId.ToString());
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
using Baya.Domain.Entities.User;
|
||||
using Baya.Infrastructure.Identity.Identity.Manager;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.SeedDatabaseService;
|
||||
|
||||
public interface ISeedDataBase
|
||||
{
|
||||
Task Seed();
|
||||
}
|
||||
|
||||
public class SeedDataBase : ISeedDataBase
|
||||
{
|
||||
private readonly AppUserManager _userManager;
|
||||
private readonly AppRoleManager _roleManager;
|
||||
|
||||
public SeedDataBase(AppUserManager userManager, AppRoleManager roleManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_roleManager = roleManager;
|
||||
}
|
||||
|
||||
public async Task Seed()
|
||||
{
|
||||
if (!_roleManager.Roles.AsNoTracking().Any(r => r.Name.Equals("admin")))
|
||||
{
|
||||
var role=new Role
|
||||
{
|
||||
Name = "admin",
|
||||
};
|
||||
await _roleManager.CreateAsync(role);
|
||||
}
|
||||
|
||||
if (!_userManager.Users.AsNoTracking().Any(u => u.UserName.Equals("admin")))
|
||||
{
|
||||
var user = new User
|
||||
{
|
||||
UserName = "admin",
|
||||
Email = "admin@site.com",
|
||||
PhoneNumberConfirmed = true
|
||||
};
|
||||
|
||||
await _userManager.CreateAsync(user, "qw123321");
|
||||
await _userManager.AddToRoleAsync(user,"admin");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Baya.Domain.Entities.User;
|
||||
using Baya.Infrastructure.Persistence;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.Store;
|
||||
|
||||
public class AppUserStore:UserStore<User,Role,ApplicationDbContext,int,UserClaim,UserRole,UserLogin,UserToken,RoleClaim>
|
||||
{
|
||||
public AppUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Baya.Domain.Entities.User;
|
||||
using Baya.Infrastructure.Persistence;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.Store;
|
||||
|
||||
public class RoleStore:RoleStore<Role,ApplicationDbContext,int,UserRole,RoleClaim>
|
||||
{
|
||||
public RoleStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
|
||||
{
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using Baya.Domain.Entities.User;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.validator;
|
||||
|
||||
public class AppRoleValidator:RoleValidator<Role>
|
||||
{
|
||||
public AppRoleValidator(IdentityErrorDescriber errors):base(errors)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override Task<IdentityResult> ValidateAsync(RoleManager<Role> manager, Role role)
|
||||
{
|
||||
return base.ValidateAsync(manager, role);
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using Baya.Domain.Entities.User;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.validator;
|
||||
|
||||
public class AppUserValidator:UserValidator<User>
|
||||
{
|
||||
public AppUserValidator(IdentityErrorDescriber errors):base(errors)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override async Task<IdentityResult> ValidateAsync(UserManager<User> manager, User user)
|
||||
{
|
||||
var result=await base.ValidateAsync(manager, user);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Baya.Application.Contracts;
|
||||
using Baya.Application.Contracts.Persistence;
|
||||
using Baya.Application.Models.Jwt;
|
||||
using Baya.Domain.Entities.User;
|
||||
using Baya.Infrastructure.Identity.Identity.Dtos;
|
||||
using Baya.Infrastructure.Identity.Identity.Manager;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Jwt;
|
||||
|
||||
public class JwtService : IJwtService
|
||||
{
|
||||
private readonly IdentitySettings _siteSetting;
|
||||
private readonly AppUserManager _userManager;
|
||||
private IUserClaimsPrincipalFactory<User> _claimsPrincipal;
|
||||
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
//private readonly AppUserClaimsPrincipleFactory claimsPrincipleFactory;
|
||||
|
||||
public JwtService(IOptions<IdentitySettings> siteSetting, AppUserManager userManager, IUserClaimsPrincipalFactory<User> claimsPrincipal, IUnitOfWork unitOfWork)
|
||||
{
|
||||
_siteSetting = siteSetting.Value;
|
||||
_userManager = userManager;
|
||||
_claimsPrincipal = claimsPrincipal;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
public async Task<AccessToken> GenerateAsync(User user)
|
||||
{
|
||||
var secretKey = Encoding.UTF8.GetBytes(_siteSetting.SecretKey); // longer that 16 character
|
||||
var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature);
|
||||
|
||||
var encryptionkey = Encoding.UTF8.GetBytes(_siteSetting.Encryptkey); //must be 16 character
|
||||
var encryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(encryptionkey), SecurityAlgorithms.Aes128KW, SecurityAlgorithms.Aes128CbcHmacSha256);
|
||||
|
||||
|
||||
var claims = await _getClaimsAsync(user);
|
||||
|
||||
var descriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Issuer = _siteSetting.Issuer,
|
||||
Audience = _siteSetting.Audience,
|
||||
IssuedAt = DateTime.Now,
|
||||
NotBefore = DateTime.Now.AddMinutes(0),
|
||||
Expires = DateTime.Now.AddMinutes(_siteSetting.ExpirationMinutes),
|
||||
SigningCredentials = signingCredentials,
|
||||
EncryptingCredentials = encryptingCredentials,
|
||||
Subject = new ClaimsIdentity(claims)
|
||||
};
|
||||
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
|
||||
var securityToken = tokenHandler.CreateJwtSecurityToken(descriptor);
|
||||
|
||||
|
||||
var refreshToken = await _unitOfWork.UserRefreshTokenRepository.CreateToken(user.Id);
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
return new AccessToken(securityToken,refreshToken.ToString());
|
||||
}
|
||||
|
||||
public Task<ClaimsPrincipal> GetPrincipalFromExpiredToken(string token)
|
||||
{
|
||||
var tokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateAudience = false, //you might want to validate the audience and issuer depending on your use case
|
||||
ValidateIssuer = false,
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_siteSetting.SecretKey)),
|
||||
ValidateLifetime = false,
|
||||
TokenDecryptionKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_siteSetting.Encryptkey))
|
||||
};
|
||||
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
SecurityToken securityToken;
|
||||
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
|
||||
var jwtSecurityToken = securityToken as JwtSecurityToken;
|
||||
if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
|
||||
throw new SecurityTokenException("Invalid token");
|
||||
|
||||
return Task.FromResult(principal);
|
||||
}
|
||||
|
||||
public async Task<AccessToken> GenerateByPhoneNumberAsync(string phoneNumber)
|
||||
{
|
||||
var user = await _userManager.Users.AsNoTracking().FirstOrDefaultAsync(u => u.PhoneNumber == phoneNumber);
|
||||
var result = await this.GenerateAsync(user);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<AccessToken> RefreshToken(Guid refreshTokenId)
|
||||
{
|
||||
var refreshToken = await _unitOfWork.UserRefreshTokenRepository.GetTokenWithInvalidation(refreshTokenId);
|
||||
|
||||
if (refreshToken is null)
|
||||
return null;
|
||||
|
||||
refreshToken.IsValid = false;
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
var user = await _unitOfWork.UserRefreshTokenRepository.GetUserByRefreshToken(refreshTokenId);
|
||||
|
||||
if (user is null)
|
||||
return null;
|
||||
|
||||
var result = await this.GenerateAsync(user);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<Claim>> _getClaimsAsync(User user)
|
||||
{
|
||||
var result = await _claimsPrincipal.CreateAsync(user);
|
||||
return result.Claims;
|
||||
}
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Baya.Application.Contracts;
|
||||
using Baya.Application.Contracts.Identity;
|
||||
using Baya.Application.Models.ApiResult;
|
||||
using Baya.Domain.Entities.User;
|
||||
using Baya.Infrastructure.Identity.Identity;
|
||||
using Baya.Infrastructure.Identity.Identity.Dtos;
|
||||
using Baya.Infrastructure.Identity.Identity.Extensions;
|
||||
using Baya.Infrastructure.Identity.Identity.Manager;
|
||||
using Baya.Infrastructure.Identity.Identity.PermissionManager;
|
||||
using Baya.Infrastructure.Identity.Identity.SeedDatabaseService;
|
||||
using Baya.Infrastructure.Identity.Identity.Store;
|
||||
using Baya.Infrastructure.Identity.Identity.validator;
|
||||
using Baya.Infrastructure.Identity.Jwt;
|
||||
using Baya.Infrastructure.Identity.UserManager;
|
||||
using Baya.SharedKernel.Extensions;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.ServiceConfiguration;
|
||||
|
||||
public static class ServiceCollectionExtension
|
||||
{
|
||||
public static IServiceCollection RegisterIdentityServices(this IServiceCollection services,IdentitySettings identitySettings)
|
||||
{
|
||||
services.AddScoped<IJwtService, JwtService>();
|
||||
services.AddScoped<IAppUserManager, AppUserManagerImplementation>();
|
||||
services.AddScoped<ISeedDataBase, SeedDataBase>();
|
||||
|
||||
services.AddScoped<IUserValidator<User>, AppUserValidator>();
|
||||
services.AddScoped<UserValidator<User>, AppUserValidator>();
|
||||
|
||||
services.AddScoped<IUserClaimsPrincipalFactory<User>, AppUserClaimsPrincipleFactory>();
|
||||
|
||||
services.AddScoped<IRoleValidator<Role>, AppRoleValidator>();
|
||||
services.AddScoped<RoleValidator<Role>, AppRoleValidator>();
|
||||
|
||||
services.AddScoped<IAuthorizationHandler, DynamicPermissionHandler>();
|
||||
services.AddScoped<IDynamicPermissionService, DynamicPermissionService>();
|
||||
services.AddScoped<IRoleStore<Role>, RoleStore>();
|
||||
services.AddScoped<IUserStore<User>, AppUserStore>();
|
||||
services.AddScoped<IRoleManagerService, RoleManagerService>();
|
||||
|
||||
|
||||
services.AddIdentity<User, Role>(options =>
|
||||
{
|
||||
options.Stores.ProtectPersonalData = false;
|
||||
|
||||
options.Password.RequireDigit = false;
|
||||
options.Password.RequireLowercase = false;
|
||||
options.Password.RequireNonAlphanumeric = false;
|
||||
options.Password.RequiredUniqueChars = 0;
|
||||
options.Password.RequireUppercase = false;
|
||||
|
||||
options.SignIn.RequireConfirmedEmail = false;
|
||||
options.SignIn.RequireConfirmedPhoneNumber = true;
|
||||
|
||||
options.Lockout.MaxFailedAccessAttempts = 5;
|
||||
options.Lockout.AllowedForNewUsers = false;
|
||||
options.User.RequireUniqueEmail = false;
|
||||
|
||||
//options.Stores.ProtectPersonalData = true;
|
||||
|
||||
|
||||
}).AddUserStore<AppUserStore>()
|
||||
.AddRoleStore<RoleStore>().
|
||||
//.AddUserValidator<AppUserValidator>().
|
||||
//AddRoleValidator<AppRoleValidator>().
|
||||
AddUserManager<AppUserManager>().
|
||||
AddRoleManager<AppRoleManager>().
|
||||
AddErrorDescriber<AppErrorDescriber>()
|
||||
//.AddClaimsPrincipalFactory<AppUserClaimsPrincipleFactory>()
|
||||
.AddDefaultTokenProviders().
|
||||
AddSignInManager<AppSignInManager>()
|
||||
.AddDefaultTokenProviders()
|
||||
.AddPasswordlessLoginTotpTokenProvider();
|
||||
|
||||
|
||||
//For [ProtectPersonalData] Attribute In Identity
|
||||
|
||||
//services.AddScoped<ILookupProtectorKeyRing, KeyRing>();
|
||||
|
||||
//services.AddScoped<ILookupProtector, LookupProtector>();
|
||||
|
||||
//services.AddScoped<IPersonalDataProtector, PersonalDataProtector>();
|
||||
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy(ConstantPolicies.DynamicPermission, policy =>
|
||||
{
|
||||
policy.RequireAuthenticatedUser();
|
||||
policy.Requirements.Add(new DynamicPermissionRequirement());
|
||||
});
|
||||
});
|
||||
|
||||
services.AddAuthentication( options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
|
||||
}).AddJwtBearer(options =>
|
||||
{
|
||||
var secretkey = Encoding.UTF8.GetBytes(identitySettings.SecretKey);
|
||||
var encryptionkey = Encoding.UTF8.GetBytes(identitySettings.Encryptkey);
|
||||
|
||||
var validationParameters = new TokenValidationParameters
|
||||
{
|
||||
ClockSkew = TimeSpan.Zero, // default: 5 min
|
||||
RequireSignedTokens = true,
|
||||
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(secretkey),
|
||||
|
||||
RequireExpirationTime = true,
|
||||
ValidateLifetime = true,
|
||||
|
||||
ValidateAudience = true, //default : false
|
||||
ValidAudience = identitySettings.Audience,
|
||||
|
||||
ValidateIssuer = true, //default : false
|
||||
ValidIssuer = identitySettings.Issuer,
|
||||
|
||||
TokenDecryptionKey = new SymmetricSecurityKey(encryptionkey),
|
||||
|
||||
};
|
||||
|
||||
options.RequireHttpsMetadata = false;
|
||||
options.SaveToken = true;
|
||||
options.TokenValidationParameters = validationParameters;
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
OnAuthenticationFailed = context =>
|
||||
{
|
||||
//var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(JwtBearerEvents));
|
||||
//logger.LogError("Authentication failed.", context.Exception);
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnTokenValidated = async context =>
|
||||
{
|
||||
var signInManager = context.HttpContext.RequestServices.GetRequiredService<AppSignInManager>();
|
||||
|
||||
var claimsIdentity = context.Principal.Identity as ClaimsIdentity;
|
||||
if (claimsIdentity.Claims?.Any() != true)
|
||||
context.Fail("This token has no claims.");
|
||||
|
||||
var securityStamp =
|
||||
claimsIdentity.FindFirstValue(new ClaimsIdentityOptions().SecurityStampClaimType);
|
||||
if (!securityStamp.HasValue())
|
||||
context.Fail("This token has no secuirty stamp");
|
||||
|
||||
//Find user and token from database and perform your custom validation
|
||||
var userId = claimsIdentity.GetUserId<int>();
|
||||
// var user = await userRepository.GetByIdAsync(context.HttpContext.RequestAborted, userId);
|
||||
|
||||
//if (user.SecurityStamp != Guid.Parse(securityStamp))
|
||||
// context.Fail("Token secuirty stamp is not valid.");
|
||||
|
||||
var validatedUser = await signInManager.ValidateSecurityStampAsync(context.Principal);
|
||||
if (validatedUser == null)
|
||||
context.Fail("Token secuirty stamp is not valid.");
|
||||
|
||||
},
|
||||
OnChallenge = async context =>
|
||||
{
|
||||
//var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(JwtBearerEvents));
|
||||
//logger.LogError("OnChallenge error", context.Error, context.ErrorDescription);
|
||||
if (context.AuthenticateFailure is SecurityTokenExpiredException)
|
||||
{
|
||||
context.HandleResponse();
|
||||
|
||||
var response = new ApiResult(false,
|
||||
ApiResultStatusCode.UnAuthorized, "Token is expired. refresh your token");
|
||||
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
await context.Response.WriteAsJsonAsync(response);
|
||||
}
|
||||
|
||||
else if (context.AuthenticateFailure != null)
|
||||
{
|
||||
context.HandleResponse();
|
||||
|
||||
var response = new ApiResult(false,
|
||||
ApiResultStatusCode.UnAuthorized, "Token is Not Valid");
|
||||
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
await context.Response.WriteAsJsonAsync(response);
|
||||
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
context.HandleResponse();
|
||||
|
||||
context.Response.StatusCode = (int)StatusCodes.Status401Unauthorized;
|
||||
await context.Response.WriteAsJsonAsync(new ApiResult(false, ApiResultStatusCode.UnAuthorized, "Invalid access token. Please login"));
|
||||
}
|
||||
|
||||
},
|
||||
OnForbidden =async context =>
|
||||
{
|
||||
context.Response.StatusCode = (int)StatusCodes.Status403Forbidden;
|
||||
await context.Response.WriteAsJsonAsync(new ApiResult(false,
|
||||
ApiResultStatusCode.Forbidden, ApiResultStatusCode.Forbidden.ToDisplay()));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static async Task SeedDefaultUsersAsync(this WebApplication app)
|
||||
{
|
||||
await using var scope = app.Services.CreateAsyncScope();
|
||||
|
||||
var seedService = scope.ServiceProvider.GetRequiredService<ISeedDataBase>();
|
||||
await seedService.Seed();
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
using Baya.Application.Contracts.Identity;
|
||||
using Baya.Domain.Entities.User;
|
||||
using Baya.Infrastructure.Identity.Identity.Dtos;
|
||||
using Baya.Infrastructure.Identity.Identity.Manager;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.UserManager;
|
||||
|
||||
public class AppUserManagerImplementation : IAppUserManager
|
||||
{
|
||||
private readonly AppUserManager _userManager;
|
||||
public AppUserManagerImplementation(AppUserManager userManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public Task<IdentityResult> CreateUser(User user)
|
||||
{
|
||||
return _userManager.CreateAsync(user);
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> CreateUser(User user, string password)
|
||||
{
|
||||
return await _userManager.CreateAsync(user, password);
|
||||
}
|
||||
|
||||
public Task<bool> IsExistUser(string phoneNumber)
|
||||
{
|
||||
return _userManager.Users.AnyAsync(c => c.PhoneNumber == phoneNumber);
|
||||
}
|
||||
|
||||
public Task<bool> IsExistUserName(string userName)
|
||||
{
|
||||
return _userManager.Users.AnyAsync(c => c.UserName.Equals(userName));
|
||||
}
|
||||
|
||||
public async Task<string> GeneratePhoneNumberConfirmationToken(User user, string phoneNumber)
|
||||
{
|
||||
return await _userManager.GenerateChangePhoneNumberTokenAsync(user, phoneNumber);
|
||||
}
|
||||
|
||||
public Task<User> GetUserByCode(string code)
|
||||
{
|
||||
return _userManager.Users.FirstOrDefaultAsync(c => c.GeneratedCode.Equals(code));
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> ChangePhoneNumber(User user, string phoneNumber, string code)
|
||||
{
|
||||
return await _userManager.ChangePhoneNumberAsync(user, phoneNumber, code);
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> VerifyUserCode(User user, string code)
|
||||
{
|
||||
var confirmationResult = await _userManager.VerifyUserTokenAsync(
|
||||
user, CustomIdentityConstants.OtpPasswordLessLoginProvider, CustomIdentityConstants.OtpPasswordLessLoginPurpose, code);
|
||||
|
||||
if (confirmationResult)
|
||||
await _userManager.UpdateSecurityStampAsync(user);
|
||||
|
||||
return confirmationResult
|
||||
? IdentityResult.Success
|
||||
: IdentityResult.Failed(new IdentityError() { Description = "Incorrect Code" });
|
||||
}
|
||||
|
||||
public Task<string> GenerateOtpCode(User user)
|
||||
{
|
||||
return _userManager.GenerateUserTokenAsync(
|
||||
user, CustomIdentityConstants.OtpPasswordLessLoginProvider, CustomIdentityConstants.OtpPasswordLessLoginPurpose);
|
||||
}
|
||||
|
||||
public Task<User> GetUserByPhoneNumber(string phoneNumber)
|
||||
{
|
||||
return _userManager.Users.FirstOrDefaultAsync(c => c.PhoneNumber.Equals(phoneNumber));
|
||||
}
|
||||
|
||||
|
||||
public Task<User> GetByUserName(string userName)
|
||||
{
|
||||
return _userManager.FindByNameAsync(userName);
|
||||
}
|
||||
|
||||
public async Task<User> GetUserByIdAsync(int userId)
|
||||
{
|
||||
return await _userManager.FindByIdAsync(userId.ToString());
|
||||
}
|
||||
|
||||
public async Task<List<User>> GetAllUsersAsync()
|
||||
{
|
||||
return await _userManager.Users.AsNoTracking().ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> CreateUserWithPasswordAsync(User user, string password)
|
||||
{
|
||||
return await _userManager.CreateAsync(user, password);
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> AddUserToRoleAsync(User user, Role role)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(role, nameof(role));
|
||||
ArgumentNullException.ThrowIfNull(role.Name, nameof(role.Name));
|
||||
|
||||
return await _userManager.AddToRoleAsync(user, role.Name);
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> IncrementAccessFailedCountAsync(User user)
|
||||
{
|
||||
|
||||
|
||||
return await _userManager.AccessFailedAsync(user);
|
||||
}
|
||||
|
||||
public async Task<bool> IsUserLockedOutAsync(User user)
|
||||
{
|
||||
var lockoutEndDate = await _userManager.GetLockoutEndDateAsync(user);
|
||||
|
||||
return (lockoutEndDate.HasValue && lockoutEndDate.Value > DateTimeOffset.Now);
|
||||
}
|
||||
|
||||
public async Task ResetUserLockoutAsync(User user)
|
||||
{
|
||||
await _userManager.SetLockoutEndDateAsync(user, null);
|
||||
await _userManager.ResetAccessFailedCountAsync(user);
|
||||
}
|
||||
|
||||
public async Task UpdateUserAsync(User user)
|
||||
{
|
||||
await _userManager.UpdateAsync(user);
|
||||
}
|
||||
|
||||
public async Task UpdateSecurityStampAsync(User user)
|
||||
{
|
||||
await _userManager.UpdateSecurityStampAsync(user);
|
||||
}
|
||||
|
||||
public async Task<bool> IsPasswordValidAsync(User user, string password)
|
||||
{
|
||||
return await _userManager.CheckPasswordAsync(user, password);
|
||||
}
|
||||
|
||||
public async Task<string[]> GetRoleAsync(User user)
|
||||
{
|
||||
return (await _userManager.GetRolesAsync(user)).ToArray();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user