Use separate project for auth
This commit is contained in:
parent
4e4a720aa4
commit
81abab2cbe
|
@ -34,6 +34,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\BlueWest.Data.Auth\BlueWest.Data.Auth.csproj" />
|
||||||
<ProjectReference Include="..\BlueWest.Domain\BlueWest.Domain.csproj" />
|
<ProjectReference Include="..\BlueWest.Domain\BlueWest.Domain.csproj" />
|
||||||
<ProjectReference Include="..\BlueWest\BlueWest.csproj" />
|
<ProjectReference Include="..\BlueWest\BlueWest.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
using BlueWest.Data.Application.Users;
|
||||||
using BlueWest.Domain;
|
using BlueWest.Domain;
|
||||||
using BlueWest.Data;
|
|
||||||
using BlueWest.WebApi.Context;
|
|
||||||
using BlueWest.WebApi.Context.Users;
|
using BlueWest.WebApi.Context.Users;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BlueWest.WebApi.Context.Users;
|
using BlueWest.Data.Auth;
|
||||||
|
using BlueWest.Data.Auth.Context.Users;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using BlueWest.Domain;
|
using BlueWest.Domain;
|
||||||
using BlueWest.Data;
|
using BlueWest.Data;
|
||||||
|
using BlueWest.Data.Auth;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
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();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
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}";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BlueWest.Data.Auth;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using BlueWest.Data.Auth;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Controllers
|
namespace BlueWest.WebApi.Controllers
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BlueWest.Cryptography;
|
using BlueWest.Cryptography;
|
||||||
using BlueWest.Data.Application;
|
using BlueWest.Data.Application;
|
||||||
|
using BlueWest.Data.Auth;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Redis.OM;
|
using Redis.OM;
|
||||||
using Redis.OM.Searching;
|
using Redis.OM.Searching;
|
||||||
|
|
|
@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using BlueWest.Data.Auth;
|
||||||
using BlueWest.Tools;
|
using BlueWest.Tools;
|
||||||
using BlueWest.WebApi.Interceptors;
|
using BlueWest.WebApi.Interceptors;
|
||||||
using BlueWest.WebApi.Interfaces;
|
using BlueWest.WebApi.Interfaces;
|
||||||
|
|
|
@ -4,6 +4,9 @@ using System.Threading.Tasks;
|
||||||
using BlueWest.Domain;
|
using BlueWest.Domain;
|
||||||
using BlueWest.Domain;
|
using BlueWest.Domain;
|
||||||
using BlueWest.Cryptography;
|
using BlueWest.Cryptography;
|
||||||
|
using BlueWest.Data.Application.Users;
|
||||||
|
using BlueWest.Data.Auth;
|
||||||
|
using BlueWest.Data.Auth.Context.Users;
|
||||||
using BlueWest.WebApi.Configuration;
|
using BlueWest.WebApi.Configuration;
|
||||||
using BlueWest.WebApi.Context.Users;
|
using BlueWest.WebApi.Context.Users;
|
||||||
using BlueWest.WebApi.Session;
|
using BlueWest.WebApi.Session;
|
||||||
|
@ -241,8 +244,8 @@ namespace BlueWest.WebApi
|
||||||
services.AddAuthorization(options =>
|
services.AddAuthorization(options =>
|
||||||
{
|
{
|
||||||
options.AddPolicy(SessionConstants.ApiNamePolicy,
|
options.AddPolicy(SessionConstants.ApiNamePolicy,
|
||||||
policy => policy.RequireClaim(Context.Users.Constants.JwtClaimIdentifiers.Rol,
|
policy => policy.RequireClaim(Data.Auth.Context.Users.Constants.JwtClaimIdentifiers.Rol,
|
||||||
Context.Users.Constants.JwtClaims.ApiAccess));
|
Data.Auth.Context.Users.Constants.JwtClaims.ApiAccess));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,114 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,130 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
|
||||||
|
|
||||||
public interface IJwtFactory
|
|
||||||
{
|
|
||||||
Task<AccessToken> GenerateEncodedToken(string id, string userName);
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
namespace BlueWest.Cryptography
|
|
||||||
{
|
|
||||||
public interface ISessionHasher
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Generates a token for the current session
|
|
||||||
/// </summary>
|
|
||||||
void GenerateSessionToken();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
|
||||||
|
|
||||||
internal interface ITokenFactory
|
|
||||||
{
|
|
||||||
string GenerateToken(int size= 32);
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
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; }
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
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)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
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";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Application.Users
|
||||||
{
|
{
|
||||||
public class AccessToken
|
public class AccessToken
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Application.Users
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
[MapFrom(typeof(ApplicationRoleUnique))]
|
[MapFrom(typeof(ApplicationRoleUnique))]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Application.Users
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(ApplicationRole))]
|
[MapFrom(typeof(ApplicationRole))]
|
||||||
public partial class ApplicationRoleUnique
|
public partial class ApplicationRoleUnique
|
||||||
|
|
|
@ -2,7 +2,7 @@ using System;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Application.Users
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Application.Users
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(ApplicationRoleClaim))]
|
[MapFrom(typeof(ApplicationRoleClaim))]
|
||||||
public partial class ApplicationRoleClaimUnique
|
public partial class ApplicationRoleClaimUnique
|
||||||
|
|
|
@ -7,7 +7,7 @@ using BlueWest.Data.Application;
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Application.Users
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Application User in the Identity System.
|
/// Application User in the Identity System.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Application.Users
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(ApplicationUser))]
|
[MapFrom(typeof(ApplicationUser))]
|
||||||
public partial class ApplicationUserUnique
|
public partial class ApplicationUserUnique
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Application.Users
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(ApplicationUserClaimUnique))]
|
[MapFrom(typeof(ApplicationUserClaimUnique))]
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Application.Users
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(ApplicationUserClaim))]
|
[MapFrom(typeof(ApplicationUserClaim))]
|
||||||
public partial class ApplicationUserClaimUnique
|
public partial class ApplicationUserClaimUnique
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
namespace BlueWest.Data.Application.Users;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public class ApplicationUserLogin : IdentityUserLogin<string> { }
|
public class ApplicationUserLogin : IdentityUserLogin<string> { }
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Application.Users
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using MapTo;
|
using MapTo;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Application.Users
|
||||||
{
|
{
|
||||||
[MapFrom(typeof(ApplicationUserRole))]
|
[MapFrom(typeof(ApplicationUserRole))]
|
||||||
public partial class ApplicationUserRoleUnique
|
public partial class ApplicationUserRoleUnique
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
namespace BlueWest.Data.Application.Users;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public class ApplicationUserToken : IdentityUserToken<string>
|
public class ApplicationUserToken : IdentityUserToken<string>
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
|
||||||
|
|
||||||
internal interface ITokenFactory
|
|
||||||
{
|
|
||||||
string GenerateToken(int size= 32);
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
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; } = TimeSpan.FromMinutes(120);
|
|
||||||
|
|
||||||
/// <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; }
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
using BlueWest.WebApi.Context.Users;
|
using BlueWest.Data.Application.Users;
|
||||||
|
|
||||||
namespace BlueWest.Data.Application
|
namespace BlueWest.Data.Application
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using BlueWest.WebApi.Context.Users;
|
using BlueWest.Data.Application.Users;
|
||||||
using MapTo;
|
using MapTo;
|
||||||
using Redis.OM.Modeling;
|
using Redis.OM.Modeling;
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BlueWest.Data.Application;
|
using BlueWest.Data.Application;
|
||||||
using BlueWest.WebApi.Context.Users;
|
using BlueWest.Data.Auth.Context.Users;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
namespace BlueWest.WebApi
|
namespace BlueWest.Data.Auth
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Methods for handling session cache data.
|
/// Methods for handling session cache data.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Auth.Context.Users
|
||||||
{
|
{
|
||||||
// from: https://github.com/dotnet/aspnetcore/tree/main/src/Identity/samples/IdentitySample.Mvc/Models/AccountViewModels
|
// from: https://github.com/dotnet/aspnetcore/tree/main/src/Identity/samples/IdentitySample.Mvc/Models/AccountViewModels
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using BlueWest.Data.Application.Users;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
namespace BlueWest.Data.Auth.Context.Users;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace BlueWest.WebApi
|
namespace BlueWest.Data.Auth
|
||||||
{
|
{
|
||||||
internal static class SessionConstants
|
public static class SessionConstants
|
||||||
{
|
{
|
||||||
|
|
||||||
public static TimeSpan DefaultSessionMaxAge = TimeSpan.FromHours(24);
|
public static TimeSpan DefaultSessionMaxAge = TimeSpan.FromHours(24);
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BlueWest.Cryptography;
|
using BlueWest.Cryptography;
|
||||||
using BlueWest.Data.Application;
|
using BlueWest.Data.Application.Users;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
namespace BlueWest.Data.Auth.Context.Users;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// User Manager Object
|
/// User Manager Object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class ApplicationUserManager : UserManager<ApplicationUser>, IUserManager
|
public class ApplicationUserManager : UserManager<ApplicationUser>, IUserManager
|
||||||
{
|
{
|
||||||
private readonly IHasher _hasher;
|
private readonly IHasher _hasher;
|
||||||
private readonly UserRepository _usersRepo;
|
private readonly UserRepository _usersRepo;
|
||||||
|
|
|
@ -2,7 +2,7 @@ using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BlueWest.Data.Application;
|
using BlueWest.Data.Application;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
namespace BlueWest.Data.Auth.Context.Users;
|
||||||
|
|
||||||
public static class AuthConsts
|
public static class AuthConsts
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
using System;
|
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BlueWest.Cryptography;
|
using BlueWest.Cryptography;
|
||||||
using BlueWest.Data.Application;
|
using BlueWest.Data.Application;
|
||||||
|
using BlueWest.Data.Application.Users;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using static BlueWest.WebApi.Context.Users.AuthConsts;
|
using static BlueWest.Data.Auth.Context.Users.AuthConsts;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Auth.Context.Users
|
||||||
{
|
{
|
||||||
internal class AuthManager : IAuthManager
|
public class AuthManager : IAuthManager
|
||||||
{
|
{
|
||||||
private readonly ApplicationUserManager _userManager;
|
private readonly ApplicationUserManager _userManager;
|
||||||
private readonly IHasher _hasher;
|
private readonly IHasher _hasher;
|
||||||
|
|
|
@ -5,7 +5,7 @@ using System.Text;
|
||||||
|
|
||||||
namespace BlueWest.Cryptography
|
namespace BlueWest.Cryptography
|
||||||
{
|
{
|
||||||
internal abstract class BaseCryptoItem
|
public abstract class BaseCryptoItem
|
||||||
{
|
{
|
||||||
public enum HashAlgorithm
|
public enum HashAlgorithm
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using BlueWest.WebApi.Context.Users;
|
using BlueWest.Data.Application.Users;
|
||||||
|
using BlueWest.Data.Auth.Context.Users;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ namespace BlueWest.Cryptography
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hasher
|
/// Hasher
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class Hasher : BaseCryptoItem, IHasher
|
public class Hasher : BaseCryptoItem, IHasher
|
||||||
{
|
{
|
||||||
private const int SaltLength = 64;
|
private const int SaltLength = 64;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using BlueWest.WebApi.Context.Users;
|
using BlueWest.Data.Application.Users;
|
||||||
|
using BlueWest.Data.Auth.Context.Users;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
namespace BlueWest.Cryptography;
|
namespace BlueWest.Cryptography;
|
||||||
|
@ -6,7 +7,7 @@ namespace BlueWest.Cryptography;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// IHasher contract
|
/// IHasher contract
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal interface IHasher : IPasswordHasher<ApplicationUser>
|
public interface IHasher : IPasswordHasher<ApplicationUser>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create hash
|
/// Create hash
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BlueWest.Data.Application.Users;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
namespace BlueWest.Data.Auth.Context.Users;
|
||||||
|
|
||||||
public interface IJwtFactory
|
public interface IJwtFactory
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,7 @@ using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Auth.Context.Users
|
||||||
{
|
{
|
||||||
public interface IJwtTokenHandler
|
public interface IJwtTokenHandler
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
namespace BlueWest.Data.Auth.Context.Users;
|
||||||
|
|
||||||
internal interface ITokenFactory
|
internal interface ITokenFactory
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,13 +2,13 @@ using System;
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using System.Threading.Tasks;
|
using BlueWest.Data.Application.Users;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using static BlueWest.WebApi.Context.Users.Constants;
|
using static BlueWest.Data.Auth.Context.Users.Constants;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
namespace BlueWest.Data.Auth.Context.Users;
|
||||||
|
|
||||||
internal class JwtFactory : IJwtFactory
|
public class JwtFactory : IJwtFactory
|
||||||
{
|
{
|
||||||
private readonly IJwtTokenHandler _jwtTokenHandler;
|
private readonly IJwtTokenHandler _jwtTokenHandler;
|
||||||
private readonly JwtIssuerOptions _jwtOptions;
|
private readonly JwtIssuerOptions _jwtOptions;
|
||||||
|
|
|
@ -2,9 +2,9 @@ using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
namespace BlueWest.Data.Auth.Context.Users;
|
||||||
|
|
||||||
internal class JwtIssuerOptions
|
public class JwtIssuerOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 4.1.1. "iss" (Issuer) Claim - The "iss" (issuer) claim identifies the principal that issued the JWT.
|
/// 4.1.1. "iss" (Issuer) Claim - The "iss" (issuer) claim identifies the principal that issued the JWT.
|
||||||
|
|
|
@ -3,7 +3,7 @@ using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
namespace BlueWest.Data.Auth.Context.Users;
|
||||||
|
|
||||||
public class JwtTokenHandler : IJwtTokenHandler
|
public class JwtTokenHandler : IJwtTokenHandler
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,7 @@ using System.Threading.Tasks;
|
||||||
using BlueWest.Data.Application;
|
using BlueWest.Data.Application;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
namespace BlueWest.Data.Auth.Context.Users;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Auth manager contract interface.
|
/// Auth manager contract interface.
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using BlueWest.Data.Application.Users;
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
namespace BlueWest.Data.Auth.Context.Users;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SignInManager
|
/// SignInManager
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
namespace BlueWest.WebApi.Context.Users;
|
namespace BlueWest.Data.Auth.Context.Users;
|
||||||
|
|
||||||
public static class Constants
|
public static class Constants
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BlueWest.Data.Application;
|
using BlueWest.Data.Application.Users;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Auth.Context.Users
|
||||||
{
|
{
|
||||||
public interface IUserManager
|
public interface IUserManager
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
using System.Collections.Generic;
|
using BlueWest.Data.Application.Users;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BlueWest.Domain;
|
using BlueWest.Domain;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace BlueWest.WebApi.Context.Users
|
namespace BlueWest.Data.Auth.Context.Users
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Users Repository
|
/// Users Repository
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
using BlueWest.Data.Application.Users;
|
||||||
using BlueWest.EfMethods;
|
using BlueWest.EfMethods;
|
||||||
using BlueWest.Domain.Model;
|
using BlueWest.Domain.Model;
|
||||||
using BlueWest.WebApi.Context.Users;
|
|
||||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using BlueWest.Data;
|
using BlueWest.Data;
|
||||||
using BlueWest.Data.Application;
|
using BlueWest.Data.Application;
|
||||||
using BlueWest.WebApi.Context.Users;
|
using BlueWest.Data.Application.Users;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace BlueWest.Domain.Model
|
namespace BlueWest.Domain.Model
|
||||||
|
|
|
@ -11,8 +11,10 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNet.Mvc" Version="5.2.9" />
|
<PackageReference Include="Microsoft.AspNet.Mvc" Version="5.2.9" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.8" />
|
||||||
|
<PackageReference Include="Redis.OM" Version="0.2.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\BlueWest.Data.Auth\BlueWest.Data.Auth.csproj" />
|
||||||
<ProjectReference Include="..\BlueWest.Domain\BlueWest.Domain.csproj" />
|
<ProjectReference Include="..\BlueWest.Domain\BlueWest.Domain.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using BlueWest.Views.Utils;
|
using BlueWest.Views.Utils;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using BlueWest.Data.Auth;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.Localization;
|
using Microsoft.AspNetCore.Localization;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
@ -21,6 +22,16 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
|
||||||
cookieOptions.LoginPath = "/";
|
cookieOptions.LoginPath = "/";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
builder.Services.AddLogging(builder =>
|
||||||
|
{
|
||||||
|
builder.AddSimpleConsole();
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddSession(options =>
|
||||||
|
{
|
||||||
|
options.Cookie.Domain = SessionConstants.CookieDomain;
|
||||||
|
options.Cookie.HttpOnly = true;
|
||||||
|
});
|
||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BlueWest.Data.Application;
|
||||||
|
using BlueWest.Data.Auth;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Redis.OM;
|
||||||
|
using Redis.OM.Searching;
|
||||||
|
|
||||||
|
namespace BlueWest.Views;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Session Provider Context
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SessionManager : ISessionCache
|
||||||
|
{
|
||||||
|
private readonly RedisConnectionProvider _provider;
|
||||||
|
private readonly RedisCollection<SessionToken> _sessionTokens;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Index Creation Device
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="provider">Redis connection</param>
|
||||||
|
public SessionManager(
|
||||||
|
RedisConnectionProvider provider)
|
||||||
|
{
|
||||||
|
_provider = provider;
|
||||||
|
_sessionTokens = (RedisCollection<SessionToken>) provider.RedisCollection<SessionToken>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Empty constructor
|
||||||
|
/// </summary>
|
||||||
|
public SessionManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a session token by the respective Id.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tokenId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<SessionToken> GetSessionTokenByIdAsync(string tokenId)
|
||||||
|
{
|
||||||
|
return await _sessionTokens.Where(x => x.Id == tokenId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new session token
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
public async Task AddSessionToken(SessionToken token)
|
||||||
|
{
|
||||||
|
await _sessionTokens.InsertAsync(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task SaveAsync()
|
||||||
|
{
|
||||||
|
await _sessionTokens.SaveAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save session data
|
||||||
|
/// </summary>
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
_sessionTokens.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a Bearer By Access Token Id
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sessionTokenId"></param>
|
||||||
|
public async Task<bool> IsSessionValidAsync(string sessionTokenId)
|
||||||
|
{
|
||||||
|
var accessToken = await _sessionTokens
|
||||||
|
.Where(t => t.Id == sessionTokenId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (accessToken == null) return false;
|
||||||
|
|
||||||
|
if (accessToken.IsValid)
|
||||||
|
{
|
||||||
|
// Check if it's not expired
|
||||||
|
var expirationDate = accessToken.CreatedDate + accessToken.ValidFor;
|
||||||
|
var timeNow = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||||
|
if (expirationDate >= timeNow) return accessToken.IsValid;
|
||||||
|
accessToken.IsValid = false;
|
||||||
|
await _sessionTokens.SaveAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessToken.IsValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a session is valid
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sessionTokenId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool IsSessionValid(string sessionTokenId)
|
||||||
|
{
|
||||||
|
var accessToken = _sessionTokens
|
||||||
|
.FirstOrDefault(t => t.Id == sessionTokenId);
|
||||||
|
|
||||||
|
if (accessToken == null) return false;
|
||||||
|
|
||||||
|
if (!accessToken.IsValid) return accessToken.IsValid;
|
||||||
|
|
||||||
|
accessToken.Validate();
|
||||||
|
_sessionTokens.Save();
|
||||||
|
|
||||||
|
return accessToken.IsValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await _provider.Connection.CreateIndexAsync(typeof(SessionToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue