Add auth module
This commit is contained in:
parent
3e1555d052
commit
4e4a720aa4
|
@ -0,0 +1,47 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>10</LangVersion>
|
||||
<RootNamespace>BlueWest.WebApi</RootNamespace>
|
||||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<DocumentationFile>bin\$(Configuration)\$(AssemblyName).xml</DocumentationFile>
|
||||
<PublishDependencyDocumentationFiles>true</PublishDependencyDocumentationFiles>
|
||||
<AnalysisLevel>preview</AnalysisLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.49.0-pre1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization.Policy" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.2-mauipre.1.22102.15" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="6.0.2-mauipre.1.22054.8" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BlueWest.Data.Application\BlueWest.Data.Application.csproj" />
|
||||
<ProjectReference Include="..\BlueWest.Domain\BlueWest.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,5 @@
|
|||
namespace BlueWest.Data.Auth;
|
||||
|
||||
public class Class1
|
||||
{
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
using System.Threading.Tasks;
|
||||
using BlueWest.Data.Application;
|
||||
using BlueWest.WebApi.Context.Users;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace BlueWest.WebApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods for handling session cache data.
|
||||
/// </summary>
|
||||
public interface ISessionCache : IHostedService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a Session Token by Id.
|
||||
/// </summary>
|
||||
/// <param name="tokenId"></param>
|
||||
/// <returns></returns>
|
||||
Task<SessionToken> GetSessionTokenByIdAsync(string tokenId);
|
||||
/// <summary>
|
||||
/// Create a new session token
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
Task AddSessionToken(SessionToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Check for validity of the session
|
||||
/// </summary>
|
||||
/// <param name="sessionTokenId"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> IsSessionValidAsync(string sessionTokenId);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the session is valid
|
||||
/// </summary>
|
||||
/// <param name="sessionTokenId"></param>
|
||||
/// <returns></returns>
|
||||
bool IsSessionValid(string sessionTokenId);
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Save Cache
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task SaveAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Save Cache
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
void Save();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users
|
||||
{
|
||||
// from: https://github.com/dotnet/aspnetcore/tree/main/src/Identity/samples/IdentitySample.Mvc/Models/AccountViewModels
|
||||
/// <summary>
|
||||
/// Login Request adata
|
||||
/// </summary>
|
||||
public class LoginRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Email
|
||||
/// </summary>
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Password
|
||||
/// </summary>
|
||||
[Required]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Uuid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets Uuid for this login request
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetUuid()
|
||||
{
|
||||
return $"{Uuid}|{Email}";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class RegisterViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Email
|
||||
/// </summary>
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
[Display(Name = "Email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Password
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Password")]
|
||||
public string Password { get; set; }
|
||||
/// <summary>
|
||||
/// Username
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ConfirmPassword
|
||||
/// </summary>
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm password")]
|
||||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ConfirmPassword
|
||||
/// </summary>
|
||||
[DataType(DataType.PhoneNumber)]
|
||||
[Display(Name = "Phone Number")]
|
||||
public string PhoneNumber { get; set; }
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Convert RegisterViewModel to ApplicationUser
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ApplicationUser ToUser()
|
||||
{
|
||||
var newUser = new ApplicationUser();
|
||||
newUser.Email = Email;
|
||||
newUser.PasswordHash = Password;
|
||||
newUser.UserName = Username;
|
||||
newUser.PhoneNumber = PhoneNumber;
|
||||
return newUser;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
|
||||
namespace BlueWest.WebApi
|
||||
{
|
||||
internal static class SessionConstants
|
||||
{
|
||||
|
||||
public static TimeSpan DefaultSessionMaxAge = TimeSpan.FromHours(24);
|
||||
public const string ApiNamePolicy = "ApiUser";
|
||||
public const string SessionTokenHeaderName = "x-bw2-auth";
|
||||
public const string CookieDomain = "http://localhost:5173";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BlueWest.Cryptography;
|
||||
using BlueWest.Data.Application;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <summary>
|
||||
/// User Manager Object
|
||||
/// </summary>
|
||||
internal class ApplicationUserManager : UserManager<ApplicationUser>, IUserManager
|
||||
{
|
||||
private readonly IHasher _hasher;
|
||||
private readonly UserRepository _usersRepo;
|
||||
public ApplicationUserManager(
|
||||
UserRepository store,
|
||||
IOptions<IdentityOptions> optionsAccessor,
|
||||
IHasher passwordHasher,
|
||||
IEnumerable<IUserValidator<ApplicationUser>> userValidators,
|
||||
IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators,
|
||||
ILookupNormalizer keyNormalizer,
|
||||
IdentityErrorDescriber errors,
|
||||
IServiceProvider services,
|
||||
ILogger<UserManager<ApplicationUser>> logger) : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
|
||||
{
|
||||
_hasher = passwordHasher;
|
||||
_usersRepo = store;
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckPasswordAsync(ApplicationUser user, string password)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var passwordStore = GetPasswordStore();
|
||||
|
||||
var result = await VerifyPasswordAsync(passwordStore, user, password);
|
||||
if (result == PasswordVerificationResult.SuccessRehashNeeded)
|
||||
{
|
||||
//Remove the IPasswordStore parameter so we can call the protected, not private, method
|
||||
await UpdatePasswordHash(user, password, validatePassword: false);
|
||||
await UpdateUserAsync(user);
|
||||
}
|
||||
|
||||
var success = result != PasswordVerificationResult.Failed;
|
||||
if (!success)
|
||||
{
|
||||
var userId = user != null ? GetUserIdAsync(user).Result : "(null)";
|
||||
Logger.LogWarning(0, "Invalid password for user {userId}.", userId);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
protected override async Task<PasswordVerificationResult> VerifyPasswordAsync(IUserPasswordStore<ApplicationUser> store, ApplicationUser user, string password)
|
||||
{
|
||||
string existingHash;
|
||||
|
||||
if (user != null)
|
||||
existingHash = await store.GetPasswordHashAsync(user, CancellationToken);
|
||||
else
|
||||
existingHash = "not a real hash";
|
||||
|
||||
if (existingHash == null)
|
||||
{
|
||||
return PasswordVerificationResult.Failed;
|
||||
}
|
||||
return PasswordHasher.VerifyHashedPassword(user, existingHash, password);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override async Task<IdentityResult> ChangePasswordAsync(ApplicationUser user, string currentPassword, string newPassword)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var passwordStore = GetPasswordStore();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
if (await VerifyPasswordAsync(passwordStore, user, currentPassword) != PasswordVerificationResult.Failed)
|
||||
{
|
||||
var result = await UpdatePasswordHash(user, newPassword, validatePassword: false);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return await UpdateUserAsync(user);
|
||||
}
|
||||
Logger.LogWarning(2, "Change password failed for user {userId}.", await GetUserIdAsync(user));
|
||||
return IdentityResult.Failed(ErrorDescriber.PasswordMismatch());
|
||||
}
|
||||
|
||||
public override Task<IdentityResult> SetAuthenticationTokenAsync(ApplicationUser user, string loginProvider, string tokenName, string tokenValue)
|
||||
{
|
||||
return base.SetAuthenticationTokenAsync(user, loginProvider, tokenName, tokenValue);
|
||||
}
|
||||
|
||||
private IUserPasswordStore<ApplicationUser> GetPasswordStore()
|
||||
{
|
||||
if (Store is IUserPasswordStore<ApplicationUser> passwordStore)
|
||||
{
|
||||
return passwordStore;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using BlueWest.Data.Application;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public static class AuthConsts
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper object to return a negative callback
|
||||
/// </summary>
|
||||
public static (bool, SessionTokenUnique, ClaimsIdentity) NegativeToken => (false, null, null);
|
||||
|
||||
public static (bool, SessionTokenUnique, ClaimsIdentity) OkAuth(SessionTokenUnique sessionTokenUnique, ClaimsIdentity claimsIdentity, bool success = true) => (success, sessionTokenUnique, claimsIdentity);
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using BlueWest.Cryptography;
|
||||
using BlueWest.Data.Application;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using static BlueWest.WebApi.Context.Users.AuthConsts;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users
|
||||
{
|
||||
internal class AuthManager : IAuthManager
|
||||
{
|
||||
private readonly ApplicationUserManager _userManager;
|
||||
private readonly IHasher _hasher;
|
||||
private readonly IJwtFactory _jwtFactory;
|
||||
private readonly ISessionCache _sessionCache;
|
||||
|
||||
/// <summary>
|
||||
/// Auth manager constructor
|
||||
/// </summary>
|
||||
/// <param name="userManager"></param>
|
||||
/// <param name="hasher"></param>
|
||||
/// <param name="jwtFactory"></param>
|
||||
/// <param name="sessionCache"></param>
|
||||
public AuthManager(
|
||||
ApplicationUserManager userManager,
|
||||
IHasher hasher,
|
||||
IJwtFactory jwtFactory,
|
||||
ISessionCache sessionCache)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_hasher = hasher;
|
||||
_jwtFactory = jwtFactory;
|
||||
_sessionCache = sessionCache;
|
||||
}
|
||||
|
||||
private async Task<SessionToken> GetSessionToken(LoginRequest loginRequest)
|
||||
{
|
||||
var uuid = loginRequest.GetUuid();
|
||||
var hashUuid = GetHashFromUuid(uuid);
|
||||
var sessionToken = await _sessionCache.GetSessionTokenByIdAsync(hashUuid);
|
||||
return sessionToken;
|
||||
}
|
||||
|
||||
private string GetHashFromUuid(string uuid)
|
||||
{
|
||||
return _hasher.CreateHash(uuid, BaseCryptoItem.HashAlgorithm.SHA2_512);
|
||||
}
|
||||
|
||||
private SessionToken GetNewSessionToken(LoginRequest loginRequest, ApplicationUser user, string token)
|
||||
{
|
||||
long timeNow = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||
|
||||
var newToken = new SessionToken
|
||||
{
|
||||
Id = GetHashFromUuid(loginRequest.GetUuid()),
|
||||
UserId = user.Id,
|
||||
CreatedDate = timeNow,
|
||||
IsValid = true,
|
||||
ValidFor = SessionConstants.DefaultSessionMaxAge.Milliseconds,
|
||||
AccessToken = token
|
||||
};
|
||||
|
||||
return newToken;
|
||||
}
|
||||
|
||||
public bool SessionTokenIsValid(SessionToken token)
|
||||
{
|
||||
var hasChanges = token.Validate();
|
||||
|
||||
if (hasChanges)
|
||||
{
|
||||
_sessionCache.SaveAsync();
|
||||
}
|
||||
|
||||
return token.IsValid;
|
||||
}
|
||||
|
||||
public async Task<(bool, SessionTokenUnique, ClaimsIdentity)> GetSessionTokenIdByLoginRequest(LoginRequest loginRequest)
|
||||
{
|
||||
var user = await _userManager.FindByEmailAsync(loginRequest.Email);
|
||||
|
||||
if (user == null) return NegativeToken;
|
||||
|
||||
if (!await _userManager.CheckPasswordAsync(user, loginRequest.Password)) return NegativeToken;
|
||||
|
||||
var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme);
|
||||
identity.AddClaim(new Claim(ClaimTypes.Email, user.Email));
|
||||
|
||||
var sessionToken = await GetSessionToken(loginRequest);
|
||||
|
||||
if (sessionToken == null || !SessionTokenIsValid(sessionToken))
|
||||
{
|
||||
var (success, bearerToken) = await GenerateBearerToken(identity, user);
|
||||
var newSessionToken = GetNewSessionToken(loginRequest, user, bearerToken);
|
||||
await _sessionCache.AddSessionToken(newSessionToken);
|
||||
var tokenUnique = new SessionTokenUnique(newSessionToken);
|
||||
|
||||
return OkAuth(tokenUnique, identity, success);
|
||||
}
|
||||
|
||||
var response = new SessionTokenUnique(sessionToken);
|
||||
return OkAuth(response, identity);
|
||||
}
|
||||
|
||||
private async Task<(bool, string)> GenerateBearerToken(ClaimsIdentity identity, ApplicationUser user)
|
||||
{
|
||||
var jwtToken = await _jwtFactory.GenerateEncodedToken(user.Id, user.UserName);
|
||||
var completed = await _userManager.SetAuthenticationTokenAsync(user, SessionConstants.ApiNamePolicy,
|
||||
SessionConstants.ApiNamePolicy, jwtToken.Token);
|
||||
return (completed == IdentityResult.Success, jwtToken.Token);
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> VerifyLoginByEmailAsync(string email, string password)
|
||||
{
|
||||
var user = await _userManager.FindByEmailAsync(email);
|
||||
return user != null && await _userManager.CheckPasswordAsync(user, password);
|
||||
}
|
||||
|
||||
|
||||
public async Task<IdentityResult> CreateUserAsync(RegisterViewModel userSignupDto)
|
||||
{
|
||||
userSignupDto.Password = _hasher.CreateHash(userSignupDto.Password, BaseCryptoItem.HashAlgorithm.SHA3_512);;
|
||||
var newUser = userSignupDto.ToUser();
|
||||
return await _userManager.CreateAsync(newUser);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace BlueWest.Cryptography
|
||||
{
|
||||
internal abstract class BaseCryptoItem
|
||||
{
|
||||
public enum HashAlgorithm
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
SHA2_512 = 1,
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
PBKDF2_SHA512 = 2,
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
SHA3_512 = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HexStringToByteArray
|
||||
/// </summary>
|
||||
/// <param name="stringInHexFormat"></param>
|
||||
/// <returns></returns>
|
||||
protected byte[] HexStringToByteArray(string stringInHexFormat)
|
||||
{
|
||||
var converted = Enumerable.Range(0, stringInHexFormat.Length)
|
||||
.Where(x => x % 2 == 0)
|
||||
.Select(x => Convert.ToByte(stringInHexFormat.Substring(x, 2), 16))
|
||||
.ToArray();
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ByteArrayToString
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <returns></returns>
|
||||
protected string ByteArrayToString(byte[] bytes)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
sb.Append(bytes[i].ToString("X2"));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random string
|
||||
/// </summary>
|
||||
/// <param name="length">The length of the random string</param>
|
||||
/// <returns></returns>
|
||||
protected string CreateRandomString(int length)
|
||||
{
|
||||
var rng = RandomNumberGenerator.Create();
|
||||
var buffer = new byte[length / 2];
|
||||
rng.GetBytes(buffer);
|
||||
var randomString = BitConverter.ToString(buffer).Replace("-", "");
|
||||
return randomString;
|
||||
}
|
||||
|
||||
// TODO Refactor me
|
||||
/// <summary>
|
||||
/// Get Cryptographic algorithm
|
||||
/// </summary>
|
||||
/// <param name="cipherText"></param>
|
||||
/// <param name="algorithm"></param>
|
||||
/// <param name="keyIndex"></param>
|
||||
/// <param name="trimmedCipherText"></param>
|
||||
protected void GetAlgorithm(string cipherText, out int? algorithm, out int? keyIndex,
|
||||
out string trimmedCipherText)
|
||||
{
|
||||
algorithm = null;
|
||||
keyIndex = null;
|
||||
trimmedCipherText = cipherText;
|
||||
|
||||
if (cipherText.Length <= 5 ) return;
|
||||
|
||||
var cipherInfo = cipherText[0].ToString();
|
||||
|
||||
if (int.TryParse(cipherInfo, out int foundAlgorithm))
|
||||
{
|
||||
algorithm = foundAlgorithm;
|
||||
}
|
||||
|
||||
if (int.TryParse(cipherInfo, out int foundKeyIndex))
|
||||
keyIndex = foundKeyIndex;
|
||||
|
||||
trimmedCipherText = cipherText.Substring(cipherText.IndexOf(cipherInfo, StringComparison.Ordinal) + 1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
|
||||
using System;
|
||||
using BlueWest.WebApi.Context.Users;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
|
||||
namespace BlueWest.Cryptography
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Hasher
|
||||
/// </summary>
|
||||
internal class Hasher : BaseCryptoItem, IHasher
|
||||
{
|
||||
private const int SaltLength = 64;
|
||||
|
||||
/// <summary>
|
||||
/// CreateHash
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="algorithm"></param>
|
||||
/// <returns></returns>
|
||||
public string CreateHash(string text, HashAlgorithm algorithm)
|
||||
{
|
||||
var salt = CreateRandomString(SaltLength);
|
||||
return CreateHash(text, salt, algorithm, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CreateHash
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="saltName"></param>
|
||||
/// <param name="algorithm"></param>
|
||||
/// <returns></returns>
|
||||
public string CreateHash(string text, string saltName, BaseCryptoItem.HashAlgorithm algorithm)
|
||||
{
|
||||
return CreateHash(text, saltName, algorithm, false);
|
||||
}
|
||||
|
||||
private string CreateHash(string text, string salt, HashAlgorithm algorithm, bool storeSalt)
|
||||
{
|
||||
string hash;
|
||||
|
||||
switch (algorithm)
|
||||
{
|
||||
case HashAlgorithm.SHA2_512:
|
||||
var sha2 = new SHA2_512();
|
||||
hash = sha2.Hash(text, salt, storeSalt);
|
||||
break;
|
||||
case HashAlgorithm.SHA3_512:
|
||||
var sha3 = new SHA2_512();
|
||||
hash = sha3.Hash(text, salt, storeSalt);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for a matching hash.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="hash"></param>
|
||||
/// <returns></returns>
|
||||
public bool MatchesHash(string text, string hash)
|
||||
{
|
||||
string salt = "";
|
||||
|
||||
GetAlgorithm(hash, out var algoAsInt, out _, out _, out salt);
|
||||
|
||||
if (!algoAsInt.HasValue) return false;
|
||||
|
||||
var hashAlgorithm = (HashAlgorithm) algoAsInt.Value;
|
||||
var hashed = CreateHash(text, salt, hashAlgorithm, true);
|
||||
return hashed == hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash password
|
||||
/// </summary>
|
||||
/// <param name="ApplicationUser"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
public string HashPassword(ApplicationUser ApplicationUser, string password)
|
||||
{
|
||||
return CreateHash(password, HashAlgorithm.SHA3_512);
|
||||
}
|
||||
|
||||
public PasswordVerificationResult VerifyHashedPassword(ApplicationUser ApplicationUser, string hashedPassword,
|
||||
string providedPassword)
|
||||
{
|
||||
var match = MatchesHash(providedPassword, hashedPassword);
|
||||
return match ? PasswordVerificationResult.Success : PasswordVerificationResult.Failed;
|
||||
}
|
||||
|
||||
private void GetAlgorithm(string cipherText, out int? algorithm, out int? keyIndex,
|
||||
out string trimmedCipherText, out string salt)
|
||||
{
|
||||
GetAlgorithm(cipherText, out algorithm, out keyIndex, out trimmedCipherText);
|
||||
if (algorithm.HasValue && trimmedCipherText.Length > SaltLength)
|
||||
{
|
||||
salt = trimmedCipherText.Substring(0, SaltLength);
|
||||
trimmedCipherText = trimmedCipherText.Substring(SaltLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
salt = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using BlueWest.WebApi.Context.Users;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
/// IHasher contract
|
||||
/// </summary>
|
||||
internal interface IHasher : IPasswordHasher<ApplicationUser>
|
||||
{
|
||||
/// <summary>
|
||||
/// Create hash
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="algorithm"></param>
|
||||
/// <returns></returns>
|
||||
string CreateHash(string text, BaseCryptoItem.HashAlgorithm algorithm);
|
||||
/// <summary>
|
||||
/// Create hash
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="salt"></param>
|
||||
/// <param name="algorithm"></param>
|
||||
/// <returns></returns>
|
||||
string CreateHash(string text, string salt, BaseCryptoItem.HashAlgorithm algorithm);
|
||||
/// <summary>
|
||||
/// MatchesHash
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="hash"></param>
|
||||
/// <returns></returns>
|
||||
bool MatchesHash(string text, string hash);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public interface IJwtFactory
|
||||
{
|
||||
Task<AccessToken> GenerateEncodedToken(string id, string userName);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users
|
||||
{
|
||||
public interface IJwtTokenHandler
|
||||
{
|
||||
string WriteToken(JwtSecurityToken jwt);
|
||||
ClaimsPrincipal ValidateToken(string token, TokenValidationParameters tokenValidationParameters);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
namespace BlueWest.Cryptography
|
||||
{
|
||||
public interface ISessionHasher
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a token for the current session
|
||||
/// </summary>
|
||||
void GenerateSessionToken();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
internal interface ITokenFactory
|
||||
{
|
||||
string GenerateToken(int size= 32);
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
using System;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Options;
|
||||
using static BlueWest.WebApi.Context.Users.Constants;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
internal class JwtFactory : IJwtFactory
|
||||
{
|
||||
private readonly IJwtTokenHandler _jwtTokenHandler;
|
||||
private readonly JwtIssuerOptions _jwtOptions;
|
||||
|
||||
public JwtFactory(IJwtTokenHandler jwtTokenHandler, IOptions<JwtIssuerOptions> jwtOptions)
|
||||
{
|
||||
_jwtTokenHandler = jwtTokenHandler;
|
||||
_jwtOptions = jwtOptions.Value;
|
||||
ThrowIfInvalidOptions(_jwtOptions);
|
||||
}
|
||||
|
||||
public async Task<AccessToken> GenerateEncodedToken(string id, string userName)
|
||||
{
|
||||
var identity = GenerateClaimsIdentity(id, userName);
|
||||
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, userName),
|
||||
new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
|
||||
new Claim(JwtRegisteredClaimNames.Aud, _jwtOptions.Audience),
|
||||
|
||||
new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(),
|
||||
ClaimValueTypes.Integer64),
|
||||
identity.FindFirst(JwtClaimIdentifiers.Rol),
|
||||
identity.FindFirst(JwtClaimIdentifiers.Id)
|
||||
};
|
||||
|
||||
// Create the JWT security token and encode it.
|
||||
var jwt = new JwtSecurityToken(
|
||||
_jwtOptions.Issuer,
|
||||
_jwtOptions.Audience,
|
||||
claims,
|
||||
_jwtOptions.NotBefore,
|
||||
_jwtOptions.Expiration,
|
||||
_jwtOptions.SigningCredentials);
|
||||
|
||||
return new AccessToken(_jwtTokenHandler.WriteToken(jwt), (int)_jwtOptions.ValidFor.TotalSeconds);
|
||||
}
|
||||
|
||||
private static ClaimsIdentity GenerateClaimsIdentity(string id, string userName)
|
||||
{
|
||||
return new ClaimsIdentity(new GenericIdentity(userName, "Token"), new[]
|
||||
{
|
||||
new Claim(JwtClaimIdentifiers.Id, id),
|
||||
new Claim(JwtClaimIdentifiers.Rol, JwtClaims.ApiAccess)
|
||||
});
|
||||
}
|
||||
|
||||
/// <returns>Date converted to seconds since Unix epoch (Jan 1, 1970, midnight UTC).</returns>
|
||||
private static long ToUnixEpochDate(DateTime date)
|
||||
=> (long)Math.Round((date.ToUniversalTime() -
|
||||
new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero))
|
||||
.TotalSeconds);
|
||||
|
||||
private static void ThrowIfInvalidOptions(JwtIssuerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (options.ValidFor <= TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentException("Must be a non-zero TimeSpan.", nameof(JwtIssuerOptions.ValidFor));
|
||||
}
|
||||
|
||||
if (options.SigningCredentials == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(JwtIssuerOptions.SigningCredentials));
|
||||
}
|
||||
|
||||
if (options.JtiGenerator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(JwtIssuerOptions.JtiGenerator));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
internal class JwtIssuerOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 4.1.1. "iss" (Issuer) Claim - The "iss" (issuer) claim identifies the principal that issued the JWT.
|
||||
/// </summary>
|
||||
public string Issuer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 4.1.2. "sub" (Subject) Claim - The "sub" (subject) claim identifies the principal that is the subject of the JWT.
|
||||
/// </summary>
|
||||
public string Subject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 4.1.3. "aud" (Audience) Claim - The "aud" (audience) claim identifies the recipients that the JWT is intended for.
|
||||
/// </summary>
|
||||
public string Audience { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 4.1.4. "exp" (Expiration Time) Claim - The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing.
|
||||
/// </summary>
|
||||
public DateTime Expiration => IssuedAt.Add(ValidFor);
|
||||
|
||||
/// <summary>
|
||||
/// 4.1.5. "nbf" (Not Before) Claim - The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing.
|
||||
/// </summary>
|
||||
public DateTime NotBefore => DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// 4.1.6. "iat" (Issued At) Claim - The "iat" (issued at) claim identifies the time at which the JWT was issued.
|
||||
/// </summary>
|
||||
public DateTime IssuedAt => DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// Set the timespan the token will be valid for (default is 120 min)
|
||||
/// </summary>
|
||||
public TimeSpan ValidFor { get; set; } = SessionConstants.DefaultSessionMaxAge;
|
||||
|
||||
/// <summary>
|
||||
/// "jti" (JWT ID) Claim (default ID is a GUID)
|
||||
/// </summary>
|
||||
public Func<Task<string>> JtiGenerator =>
|
||||
() => Task.FromResult(Guid.NewGuid().ToString());
|
||||
|
||||
/// <summary>
|
||||
/// The signing key to use when generating tokens.
|
||||
/// </summary>
|
||||
public SigningCredentials SigningCredentials { get; set; }
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public class JwtTokenHandler : IJwtTokenHandler
|
||||
{
|
||||
private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler;
|
||||
|
||||
/// <summary>
|
||||
/// JwtTokenHandler
|
||||
/// </summary>
|
||||
public JwtTokenHandler()
|
||||
{
|
||||
_jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write token
|
||||
/// </summary>
|
||||
/// <param name="jwt"></param>
|
||||
/// <returns></returns>
|
||||
public string WriteToken(JwtSecurityToken jwt)
|
||||
{
|
||||
return _jwtSecurityTokenHandler.WriteToken(jwt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate Token
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <param name="tokenValidationParameters"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="SecurityTokenException"></exception>
|
||||
public ClaimsPrincipal ValidateToken(string token, TokenValidationParameters tokenValidationParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
var principal = _jwtSecurityTokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken);
|
||||
|
||||
if (!(securityToken is JwtSecurityToken jwtSecurityToken) || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
|
||||
throw new SecurityTokenException("Invalid token");
|
||||
|
||||
return principal;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
namespace BlueWest.Cryptography
|
||||
{
|
||||
/// <summary>
|
||||
/// SHA2_512 : BaseCryptoItem
|
||||
/// </summary>
|
||||
internal class SHA2_512 : BaseCryptoItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Hash with the provided salt
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="salt"></param>
|
||||
/// <param name="storeSalt"></param>
|
||||
/// <returns></returns>
|
||||
public string Hash(string text, string salt, bool storeSalt)
|
||||
{
|
||||
var fullText = string.Concat(text, salt);
|
||||
var data = Encoding.UTF8.GetBytes(fullText);
|
||||
string hash;
|
||||
using SHA512 sha = SHA512.Create();
|
||||
var hashBytes = sha.ComputeHash(data);
|
||||
var asString = ByteArrayToString(hashBytes);
|
||||
|
||||
if (storeSalt)
|
||||
{
|
||||
hash = $"{(int)HashAlgorithm.SHA3_512}{salt}{asString}";
|
||||
return hash;
|
||||
}
|
||||
|
||||
hash = $"{(int)HashAlgorithm.SHA3_512}{asString}";
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Hash_PBKDF2 algorithm.
|
||||
/// </summary>
|
||||
/// <param name="plainText"></param>
|
||||
/// <param name="salt"></param>
|
||||
/// <param name="saveSaltInResult"></param>
|
||||
/// <returns></returns>
|
||||
public string Hash_PBKDF2(string plainText, string salt, bool saveSaltInResult)
|
||||
{
|
||||
var saltAsBytes = Encoding.ASCII.GetBytes(salt);
|
||||
|
||||
string hashed = ByteArrayToString(KeyDerivation.Pbkdf2(
|
||||
password: plainText,
|
||||
salt: saltAsBytes,
|
||||
prf: KeyDerivationPrf.HMACSHA512, //.NET 3.1 uses HMACSHA256 here
|
||||
iterationCount: 100000, //.NET 3.1 uses 10,000 iterations here
|
||||
numBytesRequested: 64)); //.NET 3.1 uses 32 bytes here
|
||||
|
||||
if (saveSaltInResult)
|
||||
return string.Format("[{0}]{1}{2}", (int)HashAlgorithm.PBKDF2_SHA512, salt, hashed);
|
||||
else
|
||||
return string.Format("[{0}]{1}", (int)HashAlgorithm.PBKDF2_SHA512, hashed);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using BlueWest.Data.Application;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <summary>
|
||||
/// Auth manager contract interface.
|
||||
/// </summary>
|
||||
public interface IAuthManager
|
||||
{
|
||||
/// <summary>
|
||||
/// CreateUserAsync
|
||||
/// </summary>
|
||||
/// <param name="registerViewModel"></param>
|
||||
/// <returns></returns>
|
||||
Task<IdentityResult> CreateUserAsync(RegisterViewModel registerViewModel);
|
||||
|
||||
/// <summary>
|
||||
/// Does Login
|
||||
/// </summary>
|
||||
/// <param name="loginRequest"></param>
|
||||
/// <returns></returns>
|
||||
public Task<(bool, SessionTokenUnique, ClaimsIdentity)> GetSessionTokenIdByLoginRequest(LoginRequest loginRequest);
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <summary>
|
||||
/// SignInManager
|
||||
/// </summary>
|
||||
internal class SignInManager : SignInManager<ApplicationUser>
|
||||
{
|
||||
public SignInManager(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IHttpContextAccessor contextAccessor,
|
||||
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory,
|
||||
IOptions<IdentityOptions> optionsAccessor,
|
||||
ILogger<SignInManager<ApplicationUser>> logger,
|
||||
IAuthenticationSchemeProvider schemes,
|
||||
IUserConfirmation<ApplicationUser> confirmation) :
|
||||
base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// JwtClaimIdentifiers
|
||||
/// </summary>
|
||||
public static class JwtClaimIdentifiers
|
||||
{
|
||||
public const string Rol = "rol", Id = "id";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JwtClaims
|
||||
/// </summary>
|
||||
public static class JwtClaims
|
||||
{
|
||||
/// <summary>
|
||||
/// JwtClaims.ApiAccess
|
||||
/// </summary>
|
||||
public const string ApiAccess = "api_access";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System.Threading.Tasks;
|
||||
using BlueWest.Data.Application;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users
|
||||
{
|
||||
public interface IUserManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Create user.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
Task<IdentityResult> CreateAsync(ApplicationUser user);
|
||||
|
||||
/// <summary>
|
||||
/// Checks for user password
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> CheckPasswordAsync(ApplicationUser user, string password);
|
||||
|
||||
/// <summary>
|
||||
/// Find by email
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <returns></returns>
|
||||
Task<ApplicationUser> FindByEmailAsync(string email);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BlueWest.Domain;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// Users Repository
|
||||
/// </summary>
|
||||
public class UserRepository : UserStore<ApplicationUser,
|
||||
ApplicationRole,
|
||||
ApplicationUserDbContext,
|
||||
string,
|
||||
ApplicationUserClaim,
|
||||
ApplicationUserRole,
|
||||
ApplicationUserLogin,
|
||||
ApplicationUserToken,
|
||||
ApplicationRoleClaim>
|
||||
{
|
||||
private readonly ApplicationUserDbContext _context;
|
||||
|
||||
/// <summary>
|
||||
/// User repository
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="describer"></param>
|
||||
public UserRepository(ApplicationUserDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Application Users
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<ApplicationUser>> GetUsers()
|
||||
{
|
||||
var users = await _context.Users.ToListAsync();
|
||||
return users;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<string> GetPasswordHashAsync(ApplicationUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<string>(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.FromResult(user.PasswordHash);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<bool> HasPasswordAsync(ApplicationUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<bool>(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.FromResult(!string.IsNullOrEmpty(user.PasswordHash));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -43,6 +43,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlueWest.Domain", "BlueWest
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlueWest.Razor.Library", "BlueWest.Razor.Library\BlueWest.Razor.Library.csproj", "{CA6DF60F-B33E-4688-A4ED-4427B446E852}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlueWest.Data.Auth", "BlueWest.Data.Auth\BlueWest.Data.Auth.csproj", "{2998FE17-18AD-4888-A696-7F6340F8A543}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -97,6 +99,10 @@ Global
|
|||
{CA6DF60F-B33E-4688-A4ED-4427B446E852}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CA6DF60F-B33E-4688-A4ED-4427B446E852}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CA6DF60F-B33E-4688-A4ED-4427B446E852}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2998FE17-18AD-4888-A696-7F6340F8A543}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2998FE17-18AD-4888-A696-7F6340F8A543}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2998FE17-18AD-4888-A696-7F6340F8A543}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2998FE17-18AD-4888-A696-7F6340F8A543}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
Loading…
Reference in New Issue