Redis working

This commit is contained in:
Wvader 2022-09-18 02:00:24 +01:00
parent 3b8d82049f
commit b697a4b357
27 changed files with 452 additions and 394 deletions

View File

@ -87,11 +87,6 @@ namespace BlueWest.WebApi.EF.Model
builder.Entity<ApplicationRoleClaim>().ToTable("RoleClaims");
builder.Entity<ApplicationUserRole>().ToTable("UserRole");
// Session Token
builder.Entity<SessionToken>()
.HasOne(b => b.User)
.WithMany(x => x.SessionToken)
.HasForeignKey(x => x.UserId);
// Session Token Primary Key
builder.Entity<SessionToken>(b =>
@ -105,12 +100,6 @@ namespace BlueWest.WebApi.EF.Model
.WithMany(x => x.SessionDatas)
.HasForeignKey(x => x.UserId);
// Session Data
builder.Entity<SessionData>()
.HasOne(b => b.SessionToken)
.WithOne(x => x.SessionData);
// Session Data Primary Key
builder.Entity<SessionData>(b =>

View File

@ -1,30 +0,0 @@
using BlueWest.Data.Application;
using BlueWest.WebApi.EF.Model;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.InMemory;
namespace BlueWest.WebApi.Context
{
public class SessionDbContext : DbContext
{
/// <summary>
/// CountryDbContext Constructor.
/// </summary>
/// <param name="options"></param>
public SessionDbContext(DbContextOptions<SessionDbContext> options) : base(options)
{
Database.EnsureCreated();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ConfigureCurrentDbModel();
}
public DbSet<SessionToken> SessionTokens { get; set; }
}
}

View File

@ -1,14 +1,10 @@
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using BlueWest.Cryptography;
using BlueWest.Data.Application;
using BlueWest.WebApi.Context.Users;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
@ -19,29 +15,24 @@ namespace BlueWest.WebApi.Controllers;
/// </summary>
[ApiController]
[Route("api/[controller]")]
[Authorize(Policy = "ApiUser")]
[Authorize(Policy = SessionConstants.ApiNamePolicy)]
/*[EnableCors(Constants.CorsPolicyName)]*/
public class AuthController : Controller
{
private readonly IAuthManager _authManager;
private readonly IUserManager _userManager;
private readonly ISessionManager _sessionManager;
/// <summary>
///
/// </summary>
/// <param name="authManager"></param>
/// <param name="userManager"></param>
public AuthController( IAuthManager authManager, IUserManager userManager, ISessionManager sessionManager)
public AuthController( IAuthManager authManager, IUserManager userManager)
{
_authManager = authManager;
_userManager = userManager;
_sessionManager = sessionManager;
}
/// <summary>
/// Signup user
/// </summary>
@ -62,64 +53,49 @@ namespace BlueWest.WebApi.Controllers;
/// <param name="loginViewModel"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost("token")]
public async Task<ActionResult<IdentityResult>> GetTokenAsync(LoginRequest loginViewModel)
[HttpPost("login")]
public async Task<ActionResult<IdentityResult>> GetSessionToken(LoginRequest loginViewModel)
{
var (success, sessionToken, token) = await _authManager.GetToken(loginViewModel);
var (success, sessionToken, identity) = await _authManager.GetSessionTokenId(loginViewModel);
if (success)
{
return Ok(new {sessionToken, token});
return Ok(new {sessionToken});
}
return Problem();
}
/// <summary>
/// Check if user is logged in
/// Gets a bearer token
/// </summary>
/// <param name="loginViewModel"></param>
/// <returns></returns>
[HttpGet("isLoggedIn")]
public ActionResult<bool> IsLoggedIn()
[AllowAnonymous]
[HttpPost("bearer")]
public async Task<ActionResult<IdentityResult>> GetBearerBySessionId(string sessionId)
{
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
var (success, bearer) = await _authManager.GetBearerTokenBySessionTokenId(sessionId);
if (identity.IsAuthenticated)
if (success)
{
return Ok(true);
return Ok(new {bearer});
}
return Ok(false);
return new UnauthorizedObjectResult(new {message = "The provided sessionId didn't return a valid Token."});
}
/// <summary>
/// Checks if the session is authorized
/// </summary>
/// <param name="hash"></param>
/// <returns></returns>
[HttpGet("isAuthorized")]
public ActionResult IsAuthorized(string hash)
{
var isAuthorized = _sessionManager.IsAuthorized(hash);
return Ok(isAuthorized ? new {authenticated = true} : new {authenticated = false});
}
/// <summary>
/// Do Cookie based login.
/// </summary>
/// <param name="loginDto"></param>
/// <returns></returns>
[AllowAnonymous]
/*[AllowAnonymous]
[HttpPost("login")]
public async Task<ActionResult> DoLoginAsync(LoginRequest loginDto)
public async Task<ActionResult> DoLoginByCookie(LoginRequest loginDto)
{
var (success, identity, sessionToken) = await _authManager.DoLogin(loginDto);
var (success, sessionToken, identity) = await _authManager.GetSessionTokenId(loginDto);
if (success)
{
@ -129,23 +105,22 @@ namespace BlueWest.WebApi.Controllers;
new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddDays(1)
ExpiresUtc = DateTime.UtcNow.Add(SessionConstants.DefaultValidForSpan)
});
return Ok(new {authenticated = true, sessionToken});
}
return new ForbidResult(CookieAuthenticationDefaults.AuthenticationScheme);
}
}*/
/// <summary>
/// Do Cookie based logout
/// </summary>
/// <param name="loginDto"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost("logout")]
public async Task DoLogoutAsync()
public async Task DoCookieLogoutAsync()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}

View File

@ -18,7 +18,7 @@ namespace BlueWest.WebApi.Controllers
/// </summary>
[ApiController]
[Route("[controller]")]
[Authorize(Policy = "ApiUser")]
[Authorize(Policy = SessionConstants.ApiNamePolicy)]
[EnableCors(Constants.CorsPolicyName)]
// [Authorize(Roles = "Administrator")]
public class CountryController : ControllerBase

View File

@ -1,33 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
using BlueWest.Data.Application;
using Microsoft.Extensions.Hosting;
using Redis.OM;
namespace BlueWest.WebApi
{
public class IndexCreationDevice : IHostedService
{
private readonly RedisConnectionProvider _provider;
/// <summary>
/// Index Creation Device
/// </summary>
/// <param name="provider"></param>
public IndexCreationDevice(RedisConnectionProvider provider)
{
_provider = provider;
}
/// <inheritdoc />
public async Task StartAsync(CancellationToken cancellationToken)
{
await _provider.Connection.CreateIndexAsync(typeof(SessionToken)); }
/// <inheritdoc />
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask; }
}
}

View File

@ -0,0 +1,48 @@
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 Bearer By Access Token Id
/// </summary>
/// <param name="sessionTokenId"></param>
/// <returns></returns>
Task<string> GetBearerByAccessTokenId(string sessionTokenId);
/// <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>
/// Save Cache
/// </summary>
/// <returns></returns>
Task SaveAsync();
/// <summary>
/// Save Cache
/// </summary>
/// <returns></returns>
void Save();
}
}

View File

@ -1,16 +0,0 @@
using BlueWest.Data.Application;
using BlueWest.WebApi.Context.Users;
namespace BlueWest.WebApi
{
public interface ISessionManager
{
bool IsAuthorized(string tokenHash);
SessionToken GetSessionToken(string hash, ApplicationUser applicationUser);
SessionToken GetSessionToken(LoginRequest loginRequest, ApplicationUser user);
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace BlueWest.WebApi
{
internal static class SessionConstants
{
public static TimeSpan DefaultValidForSpan = TimeSpan.FromHours(24);
public const string ApiNamePolicy = "ApiUser";
}
}

View File

@ -0,0 +1,108 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using BlueWest.Cryptography;
using BlueWest.Data.Application;
using Microsoft.Extensions.Hosting;
using Redis.OM;
using Redis.OM.Searching;
namespace BlueWest.WebApi.Session
{
/// <summary>
/// Session Provider Context
/// </summary>
public sealed class SessionDataService : IHostedService, ISessionCache
{
private readonly RedisConnectionProvider _provider;
private RedisCollection<SessionToken> _sessionTokens;
/// <summary>
/// Index Creation Device
/// </summary>
/// <param name="provider">Redis connection</param>
public SessionDataService(
RedisConnectionProvider provider)
{
_provider = provider;
_sessionTokens = (RedisCollection<SessionToken>)provider.RedisCollection<SessionToken>();
}
/// <summary>
/// Empty constructor
/// </summary>
public SessionDataService() { }
/// <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<string> GetBearerByAccessTokenId(string sessionTokenId)
{
var accessToken = await _sessionTokens.Where(t => t.Id == sessionTokenId)
.FirstOrDefaultAsync();
if (accessToken == null) return string.Empty;
if (accessToken.IsValid)
{
var createdDate = DateTime.UnixEpoch.AddMilliseconds(accessToken.CreatedDate);
if (createdDate.AddMilliseconds(accessToken.ValidFor) < DateTime.Now)
{
accessToken.IsValid = false;
}
}
await _sessionTokens.SaveAsync();
return accessToken.IsValid ? accessToken.AccessToken : string.Empty;
}
/// <inheritdoc />
public async Task StartAsync(CancellationToken cancellationToken)
{
await _provider.Connection.CreateIndexAsync(typeof(SessionToken));
}
/// <inheritdoc />
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}

View File

@ -1,97 +0,0 @@
using System;
using System.Linq;
using BlueWest.Cryptography;
using BlueWest.Data.Application;
using BlueWest.WebApi.Context;
using BlueWest.WebApi.Context.Users;
namespace BlueWest.WebApi
{
internal class SessionManager : ISessionManager
{
private readonly SessionDbContext _dbContex;
private readonly IHasher _hasher;
public SessionManager(SessionDbContext sessionDbContext, IHasher hasher)
{
_dbContex = sessionDbContext;
_hasher = hasher;
}
/// <summary>
/// Check if token is authorized
/// </summary>
/// <param name="hash"></param>
/// <returns></returns>
public bool IsAuthorized(string hash)
{
var sessionToken = _dbContex.SessionTokens.FirstOrDefault(x => x.Token == hash);
if (sessionToken is not {IsValid: true}) return false;
var expirationDate = sessionToken.CreatedDate.Add(sessionToken.ValidFor);
if (expirationDate >= DateTime.Now)
{
return true;
}
return false;
}
/// <summary>
/// Gets a new or existing session token
/// </summary>
/// <param name="hash"></param>
/// <param name="applicationUser"></param>
/// <returns></returns>
public SessionToken GetSessionToken(string hash, ApplicationUser applicationUser)
{
var sessionToken = _dbContex.SessionTokens.FirstOrDefault(x => x.Token == hash);
if (sessionToken == null)
{
// Create token;
var newToken = new SessionToken
{
Token = hash,
CreatedDate = DateTime.Now,
ValidFor = TimeSpan.FromDays(1),
UserId = applicationUser.Id
};
_dbContex.SessionTokens.Add(newToken);
var result = _dbContex.SaveChanges() >= 0;
return !result ? null : newToken;
}
var expirationDate = sessionToken.CreatedDate.Add(sessionToken.ValidFor);
if (expirationDate >= DateTime.Now)
{
return sessionToken;
}
return null;
}
/// <summary>
/// Gets a session token for the user following a login request
/// </summary>
/// <param name="loginRequest"></param>
/// <param name="user"></param>
/// <returns></returns>
public SessionToken GetSessionToken(LoginRequest loginRequest, ApplicationUser user)
{
var content = $"{loginRequest.Uuid}|{user.Email}";
var hash = _hasher.CreateHash(content, BaseCryptoItem.HashAlgorithm.SHA2_512);
var sessionToken = GetSessionToken(hash, user);
return sessionToken;
}
}
}

View File

@ -153,7 +153,7 @@ namespace BlueWest.WebApi
switch (allowedDatabase)
{
case "mysql":
services.PrepareMySqlDatabasePool(_configuration, _environment);
services.PrepareMySqlDatabasePool(_configuration, _environment, configuration);
break;
case "sqlite":

View File

@ -5,21 +5,19 @@ using BlueWest.Cryptography;
using BlueWest.WebApi.Context;
using BlueWest.WebApi.Context.Users;
using BlueWest.WebApi.EF;
using Microsoft.AspNetCore.Authentication;
using BlueWest.WebApi.Session;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Redis.OM;
namespace BlueWest.WebApi
{
@ -39,13 +37,36 @@ namespace BlueWest.WebApi
private static DbContextOptionsBuilder GetMySqlSettings(
this DbContextOptionsBuilder optionsBuilder,
IConfiguration configuration,
IConfigurationRoot configurationRoot,
IWebHostEnvironment environment)
{
var sqlVersion = GetMySqlServerVersion(8, 0, 11);
// Docker / No-Docker
var startupMode = configurationRoot["mode"];
string mySqlConnectionString = String.Empty;
if (startupMode == "docker")
{
var config = configuration.Get<ConnectionStringDocker>();
if(config != null) mySqlConnectionString = config.MySql;
}
else
{
var config = configuration.Get<ConnectionStringNoDocker>();
if(config != null) mySqlConnectionString = config.MySql;
}
if (mySqlConnectionString == string.Empty)
{
throw new InvalidOperationException("Fatal error: MySQL Connection string is empty.");
}
optionsBuilder
.UseMySql(
configuration.GetConnectionString("DockerMySQL"),
mySqlConnectionString,
sqlVersion)
.UseMySql(sqlVersion,
builder =>
@ -74,16 +95,19 @@ namespace BlueWest.WebApi
/// <param name="environment"></param>
/// <returns></returns>
public static IServiceCollection PrepareMySqlDatabasePool(this IServiceCollection serviceCollection,
IConfiguration configuration, IWebHostEnvironment environment)
IConfiguration configuration, IWebHostEnvironment environment, IConfigurationRoot configurationRoot)
{
return serviceCollection
.AddDbContextPool<UserDbContext>(options => options.GetMySqlSettings(configuration, environment))
.AddDbContextPool<CountryDbContext>(options => options.GetMySqlSettings(configuration, environment))
.AddDbContextPool<FinanceDbContext>(options => options.GetMySqlSettings(configuration, environment))
.AddDbContextPool<CompanyDbContext>(options => options.GetMySqlSettings(configuration, environment))
.AddDbContextPool<UserDbContext>(options =>
options.GetMySqlSettings(configuration, configurationRoot, environment))
.AddDbContextPool<CountryDbContext>(options =>
options.GetMySqlSettings(configuration, configurationRoot, environment))
.AddDbContextPool<FinanceDbContext>(options =>
options.GetMySqlSettings(configuration, configurationRoot, environment))
.AddDbContextPool<CompanyDbContext>(options =>
options.GetMySqlSettings(configuration, configurationRoot, environment))
.AddDbContextPool<ApplicationUserDbContext>(options =>
options.GetMySqlSettings(configuration, environment))
.AddDbContextPool<SessionDbContext>(options => options.UseInMemoryDatabase("_s"));
options.GetMySqlSettings(configuration, configurationRoot, environment));
}
/// <summary>
@ -103,22 +127,23 @@ namespace BlueWest.WebApi
.AddDbContextPool<CountryDbContext>(options => options.UseSqlite(sqliteConString))
.AddDbContextPool<FinanceDbContext>(options => options.UseSqlite(sqliteConString))
.AddDbContextPool<CompanyDbContext>(options => options.UseSqlite(sqliteConString))
.AddDbContextPool<ApplicationUserDbContext>(options => options.UseSqlite(sqliteConString))
.AddDbContextPool<SessionDbContext>(options => options.UseInMemoryDatabase("_s"));
.AddDbContextPool<ApplicationUserDbContext>(options => options.UseSqlite(sqliteConString));
}
internal static IServiceCollection AddAuthServerServices(this IServiceCollection services, IConfiguration configuration , IWebHostEnvironment environment)
{
services.AddScoped<IJwtTokenHandler, JwtTokenHandler>()
services
.AddSingleton(new RedisConnectionProvider("redis://redisinstance:6379"))
.AddScoped<IJwtTokenHandler, JwtTokenHandler>()
.AddScoped<IJwtFactory, JwtFactory>()
.AddSingleton<IndexCreationDevice>()
.AddScoped<ISessionManager, SessionManager>()
.AddHostedService<SessionDataService>()
.AddSingleton<ISessionCache, SessionDataService>()
.AddScoped<UserRepository>()
.AddScoped<IUserManager, ApplicationUserManager>()
.AddScoped<IAuthManager, AuthManager>()
.AddScoped<IHasher, Hasher>();
// Database Context and Swagger
@ -201,7 +226,7 @@ namespace BlueWest.WebApi
// api user claim policy
services.AddAuthorization(options =>
{
options.AddPolicy("ApiUser",
options.AddPolicy(SessionConstants.ApiNamePolicy,
policy => policy.RequireClaim(Context.Users.Constants.JwtClaimIdentifiers.Rol,
Context.Users.Constants.JwtClaims.ApiAccess));
@ -230,6 +255,15 @@ namespace BlueWest.WebApi
return services;
}
}
}
internal class BlueWestConnectionString
{
public string Redis { get; set; }
public string MySql { get; set; }
}
internal class ConnectionStringDocker : BlueWestConnectionString { }
internal class ConnectionStringNoDocker : BlueWestConnectionString { }

View File

@ -1,92 +1,130 @@
using System;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using BlueWest.Cryptography;
using BlueWest.Data.Application;
using Duende.IdentityServer.Extensions;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
namespace BlueWest.WebApi.Context.Users;
internal class AuthManager : IAuthManager
namespace BlueWest.WebApi.Context.Users
{
internal class AuthManager : IAuthManager
{
private readonly ApplicationUserManager _userManager;
private readonly UserRepository _usersRepo;
private readonly ISessionManager _sessionManager;
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="usersRepo"></param>
/// <param name="jwtFactory"></param>
/// <param name="sessionCache"></param>
public AuthManager(
ApplicationUserManager userManager,
IHasher hasher,
UserRepository usersRepo,
ISessionManager sessionManager,
IJwtFactory jwtFactory)
IJwtFactory jwtFactory,
ISessionCache sessionCache)
{
_userManager = userManager;
_hasher = hasher;
_usersRepo = usersRepo;
_jwtFactory = jwtFactory;
_sessionManager = sessionManager;
_sessionCache = sessionCache;
}
public async Task<(bool, ClaimsIdentity, SessionTokenUnique)> DoLogin(LoginRequest loginRequest)
private async Task<SessionToken> GetSessionToken(LoginRequest loginRequest)
{
var uuid = loginRequest.GetUuid();
var hashUuid = GetHashFromUuid(uuid);
var sessionToken = await _sessionCache.GetSessionTokenByIdAsync(hashUuid);
if (sessionToken != null) return sessionToken;
return null;
}
private string GetHashFromUuid(string uuid)
{
return _hasher.CreateHash(uuid, BaseCryptoItem.HashAlgorithm.SHA2_512);
}
private SessionToken GetNewSessionToken(LoginRequest loginRequest, ApplicationUser user, string token)
{
long unixTime = ((DateTimeOffset)DateTimeOffset.Now).ToUnixTimeMilliseconds();;
var newToken = new SessionToken
{
Id = GetHashFromUuid(loginRequest.GetUuid()),
UserId = user.Id,
CreatedDate = unixTime,
IsValid = true,
AccessToken = token
};
return newToken;
}
public bool SessionTokenIsValid(SessionToken token)
{
var nowMilliseconds = DateTimeOffset.Now.ToUnixTimeMilliseconds();
var isExpired = token.CreatedDate + token.ValidFor > nowMilliseconds;
if (isExpired)
{
token.IsValid = false;
_sessionCache.SaveAsync();
}
return token.IsValid;
}
public async Task<(bool, string, ClaimsIdentity)> GetSessionTokenId(LoginRequest loginRequest)
{
var user = await _userManager.FindByEmailAsync(loginRequest.Email);
if (user != null)
{
if(await _userManager.CheckPasswordAsync(user, loginRequest.Password))
{
// Identity
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Email, user.Email));
if (user == null) return (false, string.Empty, null);
// Session
var sessionToken = _sessionManager.GetSessionToken(loginRequest, user);
var sessionResponse = new SessionTokenUnique(sessionToken);
return (true, identity, sessionResponse);
if (!await _userManager.CheckPasswordAsync(user, loginRequest.Password)) return (false, string.Empty, null);
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Email, user.Email));
// Session
var sessionToken = await GetSessionToken(loginRequest);
if (sessionToken != null)
{
if (SessionTokenIsValid(sessionToken))
{
return (true, sessionToken.Id, identity);
}
}
return (false, null, null);
var (success, bearerToken) = await GenerateBearerToken(identity, user);
var newSessionToken = GetNewSessionToken(loginRequest, user, bearerToken);
await _sessionCache.AddSessionToken(newSessionToken);
return (success, newSessionToken.Id, identity);
}
/// <inheritdoc />
public async Task<(bool, SessionTokenUnique, AccessToken)> GetToken(LoginRequest loginRequest)
private async Task<(bool, string)> GenerateBearerToken(ClaimsIdentity identity, ApplicationUser user)
{
if (!string.IsNullOrEmpty(loginRequest.Email) && !string.IsNullOrEmpty(loginRequest.Password))
{
var user = await _userManager.FindByEmailAsync(loginRequest.Email);
if (user != null)
{
if (await VerifyLoginByEmailAsync(loginRequest.Email,loginRequest.Password))
{
await _usersRepo.UpdateAsync(user, CancellationToken.None);
// Session
var sessionToken = _sessionManager.GetSessionToken(loginRequest, user);
var sessionResponse = new SessionTokenUnique(sessionToken);
var token = await _jwtFactory.GenerateEncodedToken(user.Id, user.UserName);
var completed = await _userManager.SetAuthenticationTokenAsync(user, "ApiUser", "ApiUser", token.Token);
return (completed == IdentityResult.Success, sessionResponse, token);
}
}
}
return (false, null, null);
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, string)> GetBearerTokenBySessionTokenId(string sessionId)
{
if (sessionId.IsNullOrEmpty()) return (false, string.Empty);
var bearer = await _sessionCache.GetBearerByAccessTokenId(sessionId);
return (bearer != string.Empty, bearer);
}
/// <inheritdoc />
public async Task<bool> VerifyLoginByEmailAsync(string email, string password)
{
@ -100,7 +138,7 @@ internal class AuthManager : IAuthManager
return await _userManager.CheckPasswordAsync(user, password);
}
private RegisterViewModel FromSignupToUser(RegisterViewModel signupDto)
private RegisterViewModel FromSignupToUser(RegisterViewModel signupDto)
{
var pwd = signupDto.Password;
var hash = _hasher.CreateHash(pwd, BaseCryptoItem.HashAlgorithm.SHA3_512);
@ -113,5 +151,5 @@ internal class AuthManager : IAuthManager
RegisterViewModel userToCreate = FromSignupToUser(userSignupDto);
return await _userManager.CreateAsync(userToCreate.ToUser());
}
}
}

View File

@ -80,18 +80,19 @@ namespace BlueWest.Cryptography
keyIndex = null;
trimmedCipherText = cipherText;
if (cipherText.Length <= 5 || cipherText[0] != '[') return;
if (cipherText.Length <= 5 ) return;
var cipherInfo = cipherText.Substring(1, cipherText.IndexOf(']') - 1).Split(",");
var cipherInfo = cipherText[0].ToString();
if (int.TryParse(cipherInfo[0], out var foundAlgorithm))
if (int.TryParse(cipherInfo, out int foundAlgorithm))
{
algorithm = foundAlgorithm;
}
if (cipherInfo.Length == 2 && int.TryParse(cipherInfo[1], out var foundKeyIndex))
if (int.TryParse(cipherInfo, out int foundKeyIndex))
keyIndex = foundKeyIndex;
trimmedCipherText = cipherText.Substring(cipherText.IndexOf(']') + 1);
trimmedCipherText = cipherText.Substring(cipherText.IndexOf(cipherInfo, StringComparison.Ordinal) + 1);
}
}
}

View File

@ -39,7 +39,7 @@ internal class JwtIssuerOptions
/// <summary>
/// Set the timespan the token will be valid for (default is 120 min)
/// </summary>
public TimeSpan ValidFor { get; set; } = TimeSpan.FromMinutes(120);
public TimeSpan ValidFor { get; set; } = SessionConstants.DefaultValidForSpan;
/// <summary>
/// "jti" (JWT ID) Claim (default ID is a GUID)

View File

@ -26,11 +26,11 @@ namespace BlueWest.Cryptography
if (storeSalt)
{
hash = $"[{(int)HashAlgorithm.SHA3_512}]{salt}{asString}";
hash = $"{(int)HashAlgorithm.SHA3_512}{salt}{asString}";
return hash;
}
hash = $"[{(int)HashAlgorithm.SHA3_512}]{asString}";
hash = $"{(int)HashAlgorithm.SHA3_512}{asString}";
return hash;
}

View File

@ -18,26 +18,18 @@ public interface IAuthManager
/// <returns></returns>
Task<IdentityResult> CreateUserAsync(RegisterViewModel registerViewModel);
/// <summary>
/// VerifyLoginAsync
/// </summary>
/// <param name="email"></param>
/// <param name="password"></param>
/// <returns></returns>
Task<bool> VerifyLoginByEmailAsync(string email, string password);
/// <summary>
/// GetToken
/// </summary>
/// <param name="loginRequest"></param>
/// <returns></returns>
Task<(bool, SessionTokenUnique, AccessToken)> GetToken(LoginRequest loginRequest);
/// <summary>
/// Does Login
/// </summary>
/// <param name="loginRequest"></param>
/// <returns></returns>
Task<(bool, ClaimsIdentity, SessionTokenUnique)> DoLogin(LoginRequest loginRequest);
public Task<(bool, string, ClaimsIdentity)> GetSessionTokenId(LoginRequest loginRequest);
/// <summary>
/// Gets a valid bearer token by the session id
/// </summary>
/// <param name="sessionId"></param>
/// <returns></returns>
Task<(bool, string)> GetBearerTokenBySessionTokenId(string sessionId);
}

View File

@ -25,6 +25,15 @@ namespace BlueWest.WebApi.Context.Users
[Required]
public string Uuid { get; set; }
/// <summary>
/// Gets Uuid for this login request
/// </summary>
/// <returns></returns>
public string GetUuid()
{
return $"{Uuid}|{Email}";
}
}
}

View File

@ -1,28 +1,26 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using BlueWest.WebApi.EF;
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>
namespace BlueWest.WebApi.Context.Users
{
private readonly ApplicationUserDbContext _context;
/// <summary>
/// Users Repository
/// </summary>
public class UserRepository : UserStore<ApplicationUser,
ApplicationRole,
ApplicationUserDbContext,
string,
ApplicationUserClaim,
ApplicationUserRole,
ApplicationUserLogin,
ApplicationUserToken,
ApplicationRoleClaim>
{
private readonly ApplicationUserDbContext _context;
/// <summary>
/// User repository
@ -66,4 +64,5 @@ public class UserRepository : UserStore<ApplicationUser,
return Task.FromResult(!string.IsNullOrEmpty(user.PasswordHash));
}
}
}

View File

@ -7,10 +7,15 @@
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DockerMySQL": "server=db;user=blueuser;password=dXjw127124dJ;database=bluedb;"
"ConnectionStringDocker": {
"MySQL": "server=db;user=blueuser;password=dXjw127124dJ;database=bluedb;",
"Redis": "redis://redisinstance:6379"
},
"REDIS_CONNECTION_STRING": "redis://localhost:6379",
"ConnectionStringNoDocker": {
"MySQL": "server=localhost;user=blueuser;password=dXjw127124dJ;database=bluedb;",
"Redis": "redis://localhost:6379"
},
"REDIS_CONNECTION_STRING": "redis://redis:6379",
"AuthSettings": {
"SecretKey": "iJWHDmHLpUA283sqsfhqGbMRdRj1PVkH"
},

View File

@ -1,4 +1,5 @@
{
"mode": "docker",
"database": "sqlite",
"environment": "dev"
}

View File

@ -1,8 +1,10 @@
using BlueWest.WebApi.Context.Users;
using MapTo;
using Redis.OM.Modeling;
namespace BlueWest.Data.Application
{
[Document(StorageType = StorageType.Json, Prefixes = new []{"SessionToken"})]
[MapFrom(new []
{
typeof(SessionTokenUnique)
@ -10,23 +12,18 @@ namespace BlueWest.Data.Application
public partial class SessionToken
{
[IgnoreMemberMapTo]
public string Id { get; set; }
[Indexed] public string Id { get; set; }
public string Token { get; set; }
public TimeSpan ValidFor { get; set;}
[Indexed] public int ValidFor { get; set;}
public bool IsValid { get; set; }
[Indexed] public bool IsValid { get; set; }
public DateTime CreatedDate { get; set; }
[Indexed] public long CreatedDate { get; set; }
public ApplicationUser User { get; set; }
public string UserId { get; set; }
[Indexed] public string UserId { get; set; }
public ApplicationDevice ApplicationDevice { get; set; }
[Indexed] public string AccessToken { get; set; }
public SessionData SessionData { get; set; }
public string AccessToken { get; set; }
}
}

View File

@ -7,9 +7,8 @@ namespace BlueWest.Data.Application
{
public string Id { get; set; }
public string Token { get; set; }
public TimeSpan ValidFor { get; set;}
public DateTime CreatedDate { get; set; }
public int ValidFor { get; set;}
public long CreatedDate { get; set; }
public string UserId { get; set; }
}

View File

@ -23,6 +23,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{D7BF4A
ProjectSection(SolutionItems) = preProject
docker-compose.yml = docker-compose.yml
BlueWest.Api\Dockerfile = BlueWest.Api\Dockerfile
docker-compose.db.only.yml = docker-compose.db.only.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "README", "README", "{E9E3CEB0-D00C-46E3-B497-B4ED7B291190}"

0
data/appendonly.aof Normal file
View File

View File

@ -0,0 +1,29 @@
version: '3'
services:
db:
container_name: BW1_DB_MYSQL
image: mysql/mysql-server:8.0
environment:
MYSQL_ROOT_HOST: db
MYSQL_USER_HOST: db
MYSQL_ROOT_PASSWORD: dXjw127124dJ
MYSQL_USER: blueuser
MYSQL_PASSWORD: dXjw127124dJ
MYSQL_DATABASE: bluedb
volumes:
- ./docker-entrypoint-initdb.d/:/docker-entrypoint-initdb.d/
phpmyadmin:
container_name: BW_PHPMYADMIN
image: phpmyadmin/phpmyadmin
ports:
- 80:80
environment:
MYSQL_USERNAME: 'blueuser'
MYSQL_ROOT_PASSWORD: 'dXjw127124dJ'
# ports:
# - "3308:3306"
redis:
image: "redis:alpine"
command: redis-server
ports:
- "6379:6379"

View File

@ -1,7 +1,7 @@
version: '3'
services:
db:
container_name: BW1_DB_MYSQL
container_name: BW1_MYSQL
image: mysql/mysql-server:8.0
environment:
MYSQL_ROOT_HOST: db
@ -12,16 +12,21 @@ services:
MYSQL_DATABASE: bluedb
volumes:
- ./docker-entrypoint-initdb.d/:/docker-entrypoint-initdb.d/
phpmyadmin:
container_name: BW_PHPMYADMIN
image: phpmyadmin/phpmyadmin
ports:
- 80:80
environment:
MYSQL_USERNAME: 'blueuser'
MYSQL_ROOT_PASSWORD: 'dXjw127124dJ'
# phpmyadmin:
# container_name: BW_PHPMYADMIN
# image: phpmyadmin/phpmyadmin
# ports:
# - 80:80
# environment:
# MYSQL_USERNAME: 'blueuser'
# MYSQL_ROOT_PASSWORD: 'dXjw127124dJ'
# ports:
# - "3308:3306"
redisinstance:
image: "redislabs/redismod"
ports:
- "6379:6379"
container_name: BW1_REDIS
bapi120:
build:
context: ./
@ -33,13 +38,5 @@ services:
restart: always
links:
- db:db
- redisinstance:redisinstance
container_name: BW1_API
redis:
image: "redis:alpine"
command: redis-server --requirepass Sup3rSecurePass0rd
ports:
- "6379:6379"
environment:
- REDIS_REPLICATION_MODE=master