From 3b8d82049f088198bc3cbc44aed9086491ac9c35 Mon Sep 17 00:00:00 2001 From: Wvader <34067397+wvader@users.noreply.github.com> Date: Sat, 17 Sep 2022 20:13:35 +0100 Subject: [PATCH] Session wip --- BlueWest.Api/BlueWest.Api.csproj | 11 +- .../Context/ApplicationUserDbContext.cs | 62 +------- .../Extensions/ModelBuilderExtensions.cs | 140 ++++++++++++------ BlueWest.Api/Context/SessionDbContext.cs | 30 ++++ .../SwaggerEnumSchemaFilter.cs | 0 .../Controllers/ApplicationController.cs | 21 +++ .../Controllers/ApplicationUserController.cs | 20 ++- BlueWest.Api/Controllers/AuthController.cs | 97 ++++++++---- BlueWest.Api/Controllers/CountryController.cs | 3 +- .../Controllers/CurrencyController.cs | 3 + BlueWest.Api/Controllers/FinanceController.cs | 5 +- BlueWest.Api/Controllers/UserController.cs | 6 +- BlueWest.Api/Dockerfile | 4 +- BlueWest.Api/Services/GreeterService.cs | 9 ++ BlueWest.Api/Services/IndexCreationDevice.cs | 33 +++++ .../Interceptors/ServerLoggerInterceptor.cs | 13 ++ BlueWest.Api/Session/ISessionManager.cs | 16 ++ BlueWest.Api/Session/SessionManager.cs | 97 ++++++++++++ BlueWest.Api/Startup.cs | 53 +++++-- BlueWest.Api/StartupExtensions.cs | 38 +++-- BlueWest.Api/Users/ApplicationUserManager.cs | 24 ++- BlueWest.Api/Users/Auth/AuthManager.cs | 68 ++++++--- .../{ => Users/Auth}/Crypto/BaseCryptoItem.cs | 0 .../{ => Users/Auth}/Crypto/Hasher.cs | 2 +- .../{ => Users/Auth}/Crypto/IHasher.cs | 0 .../Jwt => Users/Auth/Crypto}/IJwtFactory.cs | 0 .../Auth/Crypto}/IJwtTokenHandler.cs | 0 .../Users/Auth/Crypto/ISessionHasher.cs | 12 ++ .../Auth/Crypto}/ITokenFactory.cs | 0 .../Jwt => Users/Auth/Crypto}/JwtFactory.cs | 2 + .../Auth/Crypto}/JwtIssuerOptions.cs | 0 .../Auth/Crypto}/JwtTokenHandler.cs | 0 .../{ => Users/Auth}/Crypto/SHA_512.cs | 0 BlueWest.Api/Users/Auth/IAuthManager.cs | 16 +- BlueWest.Api/Users/IUserManager.cs | 4 + .../{LoginViewModel.cs => LoginRequest.cs} | 12 +- .../Users/Models/RegisterViewModel.cs | 11 ++ BlueWest.Api/Users/RoleStore.cs | 17 --- BlueWest.Api/appsettings.json | 5 + BlueWest.Console/.dockerignore | 25 ++++ BlueWest.Console/BlueWest.Console.csproj | 11 ++ BlueWest.Console/Dockerfile | 18 +++ BlueWest.Console/Program.cs | 25 ++++ .../AccessToken.cs | 1 + .../ApplicationDevice/ApplicationDevice.cs | 14 ++ .../ApplicationDeviceCreate.cs | 14 ++ .../ClientOperatingSystem.cs | 39 +++++ .../ApplicationDevice/ClientPlatformType.cs | 17 +++ .../ApplicationDevice/OperatingSystemType.cs | 12 ++ .../ApplicationRole/ApplicationRole.cs | 0 .../ApplicationRole/ApplicationRoleUnique.cs | 0 .../ApplicationRoleClaim.cs | 0 .../ApplicationRoleClaimUnique.cs | 0 .../ApplicationUser/ApplicationUser.cs | 13 +- .../ApplicationUser/ApplicationUserUnique.cs | 0 .../ApplicationUserClaim.cs | 0 .../ApplicationUserClaimUnique.cs | 0 .../ApplicationUserLogin.cs | 0 .../ApplicationUserRole.cs | 0 .../ApplicationUserRoleUnique.cs | 0 .../ApplicationUserToken.cs | 9 ++ .../BlueWest.Data.Application.csproj | 26 ++++ .../Crypto/Jwt/IJwtTokenHandler.cs | 12 ++ .../Crypto/Jwt/ITokenFactory.cs | 6 + .../Crypto/Jwt/JwtIssuerOptions.cs | 54 +++++++ .../Crypto/Jwt/JwtTokenHandler.cs | 53 +++++++ .../SessionToken/SessionCacheItem.cs | 16 ++ .../SessionToken/SessionData.cs | 14 ++ .../SessionToken/SessionToken.cs | 33 +++++ .../SessionToken/SessionTokenUnique.cs | 17 +++ .../ApplicationUserToken.cs | 6 - .../BlueWest.Data.Capital.csproj | 5 +- BlueWest.Frontend | 2 +- BlueWest.Posts/BlueWest.Posts.csproj | 9 ++ BlueWest.Posts/PostType.cs | 8 + BlueWest.sln | 18 +++ BlueWest/Core/Events/EventManager.cs | 6 +- .../Core/Tests/BlueSocketServerSingleton.cs | 2 +- docker-compose.yml | 11 +- 79 files changed, 1086 insertions(+), 244 deletions(-) create mode 100644 BlueWest.Api/Context/SessionDbContext.cs rename BlueWest.Api/{Controllers => Context}/SwaggerEnumSchemaFilter.cs (100%) create mode 100644 BlueWest.Api/Controllers/ApplicationController.cs create mode 100644 BlueWest.Api/Services/GreeterService.cs create mode 100644 BlueWest.Api/Services/IndexCreationDevice.cs create mode 100644 BlueWest.Api/Services/Interceptors/ServerLoggerInterceptor.cs create mode 100644 BlueWest.Api/Session/ISessionManager.cs create mode 100644 BlueWest.Api/Session/SessionManager.cs rename BlueWest.Api/{ => Users/Auth}/Crypto/BaseCryptoItem.cs (100%) rename BlueWest.Api/{ => Users/Auth}/Crypto/Hasher.cs (97%) rename BlueWest.Api/{ => Users/Auth}/Crypto/IHasher.cs (100%) rename BlueWest.Api/{Crypto/Jwt => Users/Auth/Crypto}/IJwtFactory.cs (100%) rename BlueWest.Api/{Crypto/Jwt => Users/Auth/Crypto}/IJwtTokenHandler.cs (100%) create mode 100644 BlueWest.Api/Users/Auth/Crypto/ISessionHasher.cs rename BlueWest.Api/{Crypto/Jwt => Users/Auth/Crypto}/ITokenFactory.cs (100%) rename BlueWest.Api/{Crypto/Jwt => Users/Auth/Crypto}/JwtFactory.cs (97%) rename BlueWest.Api/{Crypto/Jwt => Users/Auth/Crypto}/JwtIssuerOptions.cs (100%) rename BlueWest.Api/{Crypto/Jwt => Users/Auth/Crypto}/JwtTokenHandler.cs (100%) rename BlueWest.Api/{ => Users/Auth}/Crypto/SHA_512.cs (100%) rename BlueWest.Api/Users/Models/{LoginViewModel.cs => LoginRequest.cs} (73%) delete mode 100644 BlueWest.Api/Users/RoleStore.cs create mode 100644 BlueWest.Console/.dockerignore create mode 100644 BlueWest.Console/BlueWest.Console.csproj create mode 100644 BlueWest.Console/Dockerfile create mode 100644 BlueWest.Console/Program.cs rename {BlueWest.Api/Users/Auth => BlueWest.Data.Application}/AccessToken.cs (99%) create mode 100644 BlueWest.Data.Application/ApplicationDevice/ApplicationDevice.cs create mode 100644 BlueWest.Data.Application/ApplicationDevice/ApplicationDeviceCreate.cs create mode 100644 BlueWest.Data.Application/ApplicationDevice/ClientOperatingSystem.cs create mode 100644 BlueWest.Data.Application/ApplicationDevice/ClientPlatformType.cs create mode 100644 BlueWest.Data.Application/ApplicationDevice/OperatingSystemType.cs rename {BlueWest.Data.Capital/Application => BlueWest.Data.Application}/ApplicationRole/ApplicationRole.cs (100%) rename {BlueWest.Data.Capital/Application => BlueWest.Data.Application}/ApplicationRole/ApplicationRoleUnique.cs (100%) rename {BlueWest.Data.Capital/Application => BlueWest.Data.Application}/ApplicationRoleClaim/ApplicationRoleClaim.cs (100%) rename {BlueWest.Data.Capital/Application => BlueWest.Data.Application}/ApplicationRoleClaim/ApplicationRoleClaimUnique.cs (100%) rename {BlueWest.Data.Capital/Application => BlueWest.Data.Application}/ApplicationUser/ApplicationUser.cs (93%) rename {BlueWest.Data.Capital/Application => BlueWest.Data.Application}/ApplicationUser/ApplicationUserUnique.cs (100%) rename {BlueWest.Data.Capital/Application => BlueWest.Data.Application}/ApplicationUserClaim/ApplicationUserClaim.cs (100%) rename {BlueWest.Data.Capital/Application => BlueWest.Data.Application}/ApplicationUserClaim/ApplicationUserClaimUnique.cs (100%) rename {BlueWest.Data.Capital/Application => BlueWest.Data.Application}/ApplicationUserLogin/ApplicationUserLogin.cs (100%) rename {BlueWest.Data.Capital/Application => BlueWest.Data.Application}/ApplicationUserRole/ApplicationUserRole.cs (100%) rename {BlueWest.Data.Capital/Application => BlueWest.Data.Application}/ApplicationUserRole/ApplicationUserRoleUnique.cs (100%) create mode 100644 BlueWest.Data.Application/ApplicationUserToken/ApplicationUserToken.cs create mode 100644 BlueWest.Data.Application/BlueWest.Data.Application.csproj create mode 100644 BlueWest.Data.Application/Crypto/Jwt/IJwtTokenHandler.cs create mode 100644 BlueWest.Data.Application/Crypto/Jwt/ITokenFactory.cs create mode 100644 BlueWest.Data.Application/Crypto/Jwt/JwtIssuerOptions.cs create mode 100644 BlueWest.Data.Application/Crypto/Jwt/JwtTokenHandler.cs create mode 100644 BlueWest.Data.Application/SessionToken/SessionCacheItem.cs create mode 100644 BlueWest.Data.Application/SessionToken/SessionData.cs create mode 100644 BlueWest.Data.Application/SessionToken/SessionToken.cs create mode 100644 BlueWest.Data.Application/SessionToken/SessionTokenUnique.cs delete mode 100644 BlueWest.Data.Capital/Application/ApplicationUserToken/ApplicationUserToken.cs create mode 100644 BlueWest.Posts/BlueWest.Posts.csproj create mode 100644 BlueWest.Posts/PostType.cs diff --git a/BlueWest.Api/BlueWest.Api.csproj b/BlueWest.Api/BlueWest.Api.csproj index fd088be..ae93c5c 100644 --- a/BlueWest.Api/BlueWest.Api.csproj +++ b/BlueWest.Api/BlueWest.Api.csproj @@ -12,16 +12,18 @@ - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive + - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -42,6 +44,7 @@ + diff --git a/BlueWest.Api/Context/ApplicationUserDbContext.cs b/BlueWest.Api/Context/ApplicationUserDbContext.cs index 74f98b9..d3c3968 100644 --- a/BlueWest.Api/Context/ApplicationUserDbContext.cs +++ b/BlueWest.Api/Context/ApplicationUserDbContext.cs @@ -25,25 +25,24 @@ namespace BlueWest.WebApi.Context [EfGetMany(typeof(ApplicationUserClaimUnique))] public sealed override DbSet UserClaims { get; set; } - [EfGetMany(typeof(ApplicationUserRoleUnique))] /// + [EfGetMany(typeof(ApplicationUserRoleUnique))] public sealed override DbSet UserRoles { get; set; } - [EfGetMany(typeof(ApplicationRoleUnique))] /// + [EfGetMany(typeof(ApplicationRoleUnique))] public sealed override DbSet Roles { get; set; } - [EfGetMany(typeof(ApplicationRoleClaimUnique))] /// + [EfGetMany(typeof(ApplicationRoleClaimUnique))] public sealed override DbSet RoleClaims { get; set; } [EfGetMany(typeof(ApplicationUserUnique))] [EfUpdateMethods( updateType: typeof(ApplicationUserUnique), returnType: typeof(ApplicationUserUnique), keyPropertyMemberName: nameof(ApplicationUserUnique.Id))] public sealed override DbSet Users { get; set; } - #region Initialization - /// + #region Initialization public ApplicationUserDbContext(DbContextOptions options) : base(options) { Database.EnsureCreated(); @@ -55,62 +54,13 @@ namespace BlueWest.WebApi.Context { base.OnModelCreating(builder); - builder.Entity().ToTable("UserRoles"); - builder.Entity(b => - { - b.HasMany() - .WithOne() - .HasForeignKey(ur => ur.UserId).IsRequired(); - }); - - builder.Entity().ToTable("ApplicationUser") - .HasKey(x => x.Id); - - - builder.Entity(b => - { - b.HasKey(r => r.Id); - b.HasIndex(r => r.NormalizedName).HasDatabaseName("RoleNameIndex").IsUnique(); - b.ToTable("Roles"); - b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken(); - - b.Property(u => u.Name).HasMaxLength(256); - b.Property(u => u.NormalizedName).HasMaxLength(256); - - b.HasMany().WithOne().HasForeignKey(ur => ur.RoleId).IsRequired(); - b.HasMany().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired(); - }); - - builder.Entity().HasOne(x => x.ApplicationRole); - builder.Entity().HasOne(x => x.ApplicationRole); - builder.Entity().HasOne(x => x.ApplicationUser); - - - builder.Entity(b => - { - b.HasKey(rc => rc.Id); - b.ToTable("RoleClaims"); - }); - - builder.Entity(b => - { - b.HasKey(r => new {r.UserId, r.RoleId}); - b.ToTable("UserRoles"); - }); - - builder.Entity(b => b.HasOne() - .WithMany(x => x.Users) - .HasForeignKey(x => x.ApplicationUserId)); - - builder.Entity().ToTable("RoleClaims"); - builder.Entity().ToTable("UserRole"); - builder.ConfigureCurrentDbModel(); + + } #endregion - /// } } \ No newline at end of file diff --git a/BlueWest.Api/Context/Extensions/ModelBuilderExtensions.cs b/BlueWest.Api/Context/Extensions/ModelBuilderExtensions.cs index 46012ca..d76dfac 100644 --- a/BlueWest.Api/Context/Extensions/ModelBuilderExtensions.cs +++ b/BlueWest.Api/Context/Extensions/ModelBuilderExtensions.cs @@ -1,4 +1,5 @@ using BlueWest.Data; +using BlueWest.Data.Application; using BlueWest.WebApi.Context.Users; using Microsoft.EntityFrameworkCore; @@ -11,9 +12,7 @@ namespace BlueWest.WebApi.EF.Model { #region Initialization - static ModelBuilderExtensions() - { - } + static ModelBuilderExtensions() { } /// /// Setup the database model @@ -24,11 +23,105 @@ namespace BlueWest.WebApi.EF.Model modelBuilder .ConfigureDatabaseKeys() .CurrencyModel() - .ConfigureUserModel(); + .ConfigureUserModel() + .ConfigureAppContextModel(); //.ConfigureIdentityModel(); } + + #endregion + + #region Application Users + + /// + /// Configure App context model + /// + /// + private static void ConfigureAppContextModel(this ModelBuilder builder) + { + builder.Entity().ToTable("UserRoles"); + builder.Entity(b => + { + b.HasMany() + .WithOne(b => b.User) + .HasForeignKey(ur => ur.UserId).IsRequired(); + }); + + + builder.Entity().ToTable("ApplicationUser") + .HasKey(x => x.Id); + + + builder.Entity(b => + { + b.HasKey(r => r.Id); + b.HasIndex(r => r.NormalizedName).HasDatabaseName("RoleNameIndex").IsUnique(); + b.ToTable("Roles"); + b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken(); + + b.Property(u => u.Name).HasMaxLength(256); + b.Property(u => u.NormalizedName).HasMaxLength(256); + + b.HasMany().WithOne().HasForeignKey(ur => ur.RoleId).IsRequired(); + b.HasMany().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired(); + }); + + builder.Entity().HasOne(x => x.ApplicationRole); + builder.Entity().HasOne(x => x.ApplicationRole); + builder.Entity().HasOne(x => x.ApplicationUser); + + + builder.Entity(b => + { + b.HasKey(rc => rc.Id); + b.ToTable("RoleClaims"); + }); + + builder.Entity(b => + { + b.HasKey(r => new {r.UserId, r.RoleId}); + b.ToTable("UserRoles"); + }); + + builder.Entity().ToTable("RoleClaims"); + builder.Entity().ToTable("UserRole"); + + // Session Token + builder.Entity() + .HasOne(b => b.User) + .WithMany(x => x.SessionToken) + .HasForeignKey(x => x.UserId); + + // Session Token Primary Key + builder.Entity(b => + b.HasKey(x => x.Id)); + builder.Entity(b => + b.Property(x => x.Id).ValueGeneratedOnAdd()); + + // Session Data + builder.Entity() + .HasOne(b => b.User) + .WithMany(x => x.SessionDatas) + .HasForeignKey(x => x.UserId); + + // Session Data + builder.Entity() + .HasOne(b => b.SessionToken) + .WithOne(x => x.SessionData); + + + + // Session Data Primary Key + builder.Entity(b => + b.HasKey(x => x.Id)); + builder.Entity(b => + b.Property(x => x.Id).ValueGeneratedOnAdd()); + + + //.ConfigureIdentityModel(); + } + #endregion #region DatabasePK @@ -121,48 +214,11 @@ namespace BlueWest.WebApi.EF.Model .WithMany(ft => ft.FinanceTransactions) .HasForeignKey(x => x.UserId)); - modelBuilder.Entity(b => b.HasOne() - .WithMany(x => x.Users) - .HasForeignKey(x => x.ApplicationUserId)); - return modelBuilder; } #endregion - - public static void ConfigureIdentityModel(this ModelBuilder builder) - { - builder.Entity(b => - { - b.HasMany().WithOne().HasForeignKey(ur => ur.UserId).IsRequired(); - }); - - builder.Entity(b => - { - b.HasKey(r => r.Id); - b.HasIndex(r => r.NormalizedName).HasDatabaseName("RoleNameIndex").IsUnique(); - b.ToTable("Roles"); - b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken(); - - b.Property(u => u.Name).HasMaxLength(256); - b.Property(u => u.NormalizedName).HasMaxLength(256); - - b.HasMany().WithOne().HasForeignKey(ur => ur.RoleId).IsRequired(); - b.HasMany().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired(); - }); - - builder.Entity(b => - { - b.HasKey(rc => rc.Id); - b.ToTable("RoleClaims"); - }); - - builder.Entity(b => - { - b.HasKey(r => new { r.UserId, r.RoleId }); - b.ToTable("UserRoles"); - }); - } + } } diff --git a/BlueWest.Api/Context/SessionDbContext.cs b/BlueWest.Api/Context/SessionDbContext.cs new file mode 100644 index 0000000..8a2eaba --- /dev/null +++ b/BlueWest.Api/Context/SessionDbContext.cs @@ -0,0 +1,30 @@ +using BlueWest.Data.Application; +using BlueWest.WebApi.EF.Model; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.InMemory; + +namespace BlueWest.WebApi.Context +{ + + public class SessionDbContext : DbContext + { + /// + /// CountryDbContext Constructor. + /// + /// + + public SessionDbContext(DbContextOptions options) : base(options) + { + Database.EnsureCreated(); + } + + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.ConfigureCurrentDbModel(); + } + + public DbSet SessionTokens { get; set; } + } +} diff --git a/BlueWest.Api/Controllers/SwaggerEnumSchemaFilter.cs b/BlueWest.Api/Context/SwaggerEnumSchemaFilter.cs similarity index 100% rename from BlueWest.Api/Controllers/SwaggerEnumSchemaFilter.cs rename to BlueWest.Api/Context/SwaggerEnumSchemaFilter.cs diff --git a/BlueWest.Api/Controllers/ApplicationController.cs b/BlueWest.Api/Controllers/ApplicationController.cs new file mode 100644 index 0000000..abf8b4a --- /dev/null +++ b/BlueWest.Api/Controllers/ApplicationController.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; + +namespace BlueWest.WebApi.Controllers; + +[ApiController] +[Route("application")] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] +[EnableCors(Constants.CorsPolicyName)] +public class ApplicationController : ControllerBase +{ + [HttpGet] + [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] + public ContentResult GetTimeTicks() => Content( + DateTime.Now.Ticks.ToString()); +} \ No newline at end of file diff --git a/BlueWest.Api/Controllers/ApplicationUserController.cs b/BlueWest.Api/Controllers/ApplicationUserController.cs index 0375d38..516d423 100644 --- a/BlueWest.Api/Controllers/ApplicationUserController.cs +++ b/BlueWest.Api/Controllers/ApplicationUserController.cs @@ -1,5 +1,6 @@ using BlueWest.Data; using BlueWest.WebApi.Context; +using BlueWest.WebApi.Context.Users; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; @@ -9,6 +10,7 @@ using Microsoft.AspNetCore.Mvc; namespace BlueWest.WebApi.Controllers { + /// [ApiController] [Route("application/users")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] @@ -19,6 +21,7 @@ namespace BlueWest.WebApi.Controllers { private readonly ApplicationUserDbContext _dbContext; + /// public ApplicationUserController(ApplicationUserDbContext context) { _dbContext = context; @@ -54,16 +57,15 @@ namespace BlueWest.WebApi.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [HttpPut("{UserCode}")] - public ActionResult UpdateApplicationUser(int UserCode, UserUnique UserToUpdate) + public ActionResult UpdateApplicationUser(string applicationUserId, ApplicationUserUnique UserToUpdate) { - //var (success, User) = _dbContext.UpdateUser(UserToUpdate, UserCode); - - /* + var (success, updatedUser) = _dbContext.UpdateApplicationUser(UserToUpdate, applicationUserId); + if (success) { - return Ok(User); + return Ok(updatedUser); } - */ + return new NotFoundResult(); } @@ -124,8 +126,14 @@ namespace BlueWest.WebApi.Controllers } } +/// +/// Application Constants +/// public static class Constants { + /// + /// Policy Name + /// public const string CorsPolicyName = "_myAllowSpecificOrigins"; } diff --git a/BlueWest.Api/Controllers/AuthController.cs b/BlueWest.Api/Controllers/AuthController.cs index 14ba787..7884191 100644 --- a/BlueWest.Api/Controllers/AuthController.cs +++ b/BlueWest.Api/Controllers/AuthController.cs @@ -1,5 +1,8 @@ +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; @@ -14,25 +17,28 @@ namespace BlueWest.WebApi.Controllers; /// /// Auth controller /// -[Route("api/[controller]")] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] [ApiController] -[EnableCors(Constants.CorsPolicyName)] -public class AuthController : Controller + [Route("api/[controller]")] + + [Authorize(Policy = "ApiUser")] + +/*[EnableCors(Constants.CorsPolicyName)]*/ + public class AuthController : Controller { private readonly IAuthManager _authManager; private readonly IUserManager _userManager; + private readonly ISessionManager _sessionManager; /// /// /// /// /// - public AuthController( IAuthManager authManager, IUserManager userManager) + public AuthController( IAuthManager authManager, IUserManager userManager, ISessionManager sessionManager) { _authManager = authManager; _userManager = userManager; + _sessionManager = sessionManager; } @@ -49,25 +55,60 @@ public class AuthController : Controller } + /// /// Gets a bearer token /// /// /// [AllowAnonymous] - [HttpPost("login")] - public async Task> GetTokenAsync(LoginViewModel loginViewModel) + [HttpPost("token")] + public async Task> GetTokenAsync(LoginRequest loginViewModel) { - var loginResultSucceded = await _authManager.GetToken(loginViewModel); + var (success, sessionToken, token) = await _authManager.GetToken(loginViewModel); - if (loginResultSucceded != null) + if (success) { - return Ok(loginResultSucceded); + return Ok(new {sessionToken, token}); } return Problem(); } + + /// + /// Check if user is logged in + /// + /// + [HttpGet("isLoggedIn")] + + public ActionResult IsLoggedIn() + { + var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); + + if (identity.IsAuthenticated) + { + return Ok(true); + } + + return Ok(false); + } + + /// + /// Checks if the session is authorized + /// + /// + /// + [HttpGet("isAuthorized")] + + public ActionResult IsAuthorized(string hash) + { + var isAuthorized = _sessionManager.IsAuthorized(hash); + + return Ok(isAuthorized ? new {authenticated = true} : new {authenticated = false}); + } + + /// /// Do Cookie based login. @@ -75,23 +116,26 @@ public class AuthController : Controller /// /// [AllowAnonymous] - [HttpPost("logincookie")] - public async Task> DoLoginAsync(LoginViewModel loginDto) + [HttpPost("login")] + public async Task DoLoginAsync(LoginRequest loginDto) { - var user = await _userManager.FindByEmailAsync(loginDto.Email); - - if (user != null) + var (success, identity, sessionToken) = await _authManager.DoLogin(loginDto); + + if (success) { - if(await _userManager.CheckPasswordAsync(user, loginDto.Password)) - { - var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); - identity.AddClaim(new Claim(ClaimTypes.Email, user.Email)); - await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity)); - return Ok(new {authenticated = true}); - } + await HttpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(identity), + new AuthenticationProperties + { + IsPersistent = true, + ExpiresUtc = DateTime.UtcNow.AddDays(1) + }); + + return Ok(new {authenticated = true, sessionToken}); } - return Ok(new {authenticated = false}); + return new ForbidResult(CookieAuthenticationDefaults.AuthenticationScheme); } /// @@ -101,10 +145,9 @@ public class AuthController : Controller /// [AllowAnonymous] [HttpPost("logout")] - public async Task> DoLogoutAsync(LoginViewModel loginDto) + public async Task DoLogoutAsync() { - await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); - return Json(true); + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); } } \ No newline at end of file diff --git a/BlueWest.Api/Controllers/CountryController.cs b/BlueWest.Api/Controllers/CountryController.cs index 273cdfd..d82e63c 100644 --- a/BlueWest.Api/Controllers/CountryController.cs +++ b/BlueWest.Api/Controllers/CountryController.cs @@ -18,8 +18,7 @@ namespace BlueWest.WebApi.Controllers /// [ApiController] [Route("[controller]")] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] + [Authorize(Policy = "ApiUser")] [EnableCors(Constants.CorsPolicyName)] // [Authorize(Roles = "Administrator")] public class CountryController : ControllerBase diff --git a/BlueWest.Api/Controllers/CurrencyController.cs b/BlueWest.Api/Controllers/CurrencyController.cs index 9c5b5e1..73e0892 100644 --- a/BlueWest.Api/Controllers/CurrencyController.cs +++ b/BlueWest.Api/Controllers/CurrencyController.cs @@ -6,6 +6,7 @@ using BlueWest.WebApi.EF; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -18,6 +19,8 @@ namespace BlueWest.WebApi.Controllers [Route("[controller]")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] + [EnableCors(Constants.CorsPolicyName)] + // [Authorize(Roles = "Administrator")] public partial class CurrencyController : ControllerBase { diff --git a/BlueWest.Api/Controllers/FinanceController.cs b/BlueWest.Api/Controllers/FinanceController.cs index 13dc7cb..bde67c3 100644 --- a/BlueWest.Api/Controllers/FinanceController.cs +++ b/BlueWest.Api/Controllers/FinanceController.cs @@ -4,6 +4,7 @@ using BlueWest.WebApi.EF; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -16,7 +17,9 @@ namespace BlueWest.WebApi.Controllers; [Route("[controller]")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] -[Authorize(Roles = "Administrator")] +//[Authorize(Roles = "Administrator")] +[EnableCors(Constants.CorsPolicyName)] + public class FinanceController : ControllerBase { private readonly FinanceDbContext _dbContext; diff --git a/BlueWest.Api/Controllers/UserController.cs b/BlueWest.Api/Controllers/UserController.cs index 3c63e1a..d41c830 100644 --- a/BlueWest.Api/Controllers/UserController.cs +++ b/BlueWest.Api/Controllers/UserController.cs @@ -7,6 +7,7 @@ using BlueWest.WebApi.EF; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -19,8 +20,9 @@ namespace BlueWest.WebApi.Controllers [Route("[controller]")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] - [Authorize(Roles = "Administrator")] - + //[Authorize(Roles = "Administrator")] + [EnableCors(Constants.CorsPolicyName)] + public class UserController : ControllerBase { diff --git a/BlueWest.Api/Dockerfile b/BlueWest.Api/Dockerfile index 46f67a9..3789d9a 100644 --- a/BlueWest.Api/Dockerfile +++ b/BlueWest.Api/Dockerfile @@ -15,8 +15,8 @@ COPY ["BlueWest/BlueWest.csproj", "BlueWest/"] RUN dotnet restore "BlueWest/BlueWest.csproj" -COPY ["BlueWest.Data.Geography/BlueWest.Data.Geography.csproj", "BlueWest.Data.Geography/"] -RUN dotnet restore "BlueWest.Data.Geography/BlueWest.Data.Geography.csproj" +COPY ["BlueWest.Data.Application/BlueWest.Data.Application.csproj", "BlueWest.Data.Application/"] +RUN dotnet restore "BlueWest.Data.Application/BlueWest.Data.Application.csproj" COPY ["BlueWest.Api/BlueWest.Api.csproj", "BlueWest.Api/"] diff --git a/BlueWest.Api/Services/GreeterService.cs b/BlueWest.Api/Services/GreeterService.cs new file mode 100644 index 0000000..7d236eb --- /dev/null +++ b/BlueWest.Api/Services/GreeterService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace BlueWest.WebApi; + +public class GreeterService +{ + +} \ No newline at end of file diff --git a/BlueWest.Api/Services/IndexCreationDevice.cs b/BlueWest.Api/Services/IndexCreationDevice.cs new file mode 100644 index 0000000..24329b4 --- /dev/null +++ b/BlueWest.Api/Services/IndexCreationDevice.cs @@ -0,0 +1,33 @@ +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; + + /// + /// Index Creation Device + /// + /// + public IndexCreationDevice(RedisConnectionProvider provider) + { + _provider = provider; + } + + /// + public async Task StartAsync(CancellationToken cancellationToken) + { + await _provider.Connection.CreateIndexAsync(typeof(SessionToken)); } + + /// + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; } + } +} + diff --git a/BlueWest.Api/Services/Interceptors/ServerLoggerInterceptor.cs b/BlueWest.Api/Services/Interceptors/ServerLoggerInterceptor.cs new file mode 100644 index 0000000..27ac98b --- /dev/null +++ b/BlueWest.Api/Services/Interceptors/ServerLoggerInterceptor.cs @@ -0,0 +1,13 @@ +using Grpc.Core.Interceptors; + +namespace BlueWest.WebApi.Interceptors +{ + /// + /// Server Logger Interceptor + /// + public class ServerLoggerInterceptor : Interceptor + { + + } +} + diff --git a/BlueWest.Api/Session/ISessionManager.cs b/BlueWest.Api/Session/ISessionManager.cs new file mode 100644 index 0000000..2017751 --- /dev/null +++ b/BlueWest.Api/Session/ISessionManager.cs @@ -0,0 +1,16 @@ +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); + + } +} + diff --git a/BlueWest.Api/Session/SessionManager.cs b/BlueWest.Api/Session/SessionManager.cs new file mode 100644 index 0000000..45e6a25 --- /dev/null +++ b/BlueWest.Api/Session/SessionManager.cs @@ -0,0 +1,97 @@ +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; + } + + + /// + /// Check if token is authorized + /// + /// + /// + 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; + + } + + /// + /// Gets a new or existing session token + /// + /// + /// + /// + 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; + + } + + /// + /// Gets a session token for the user following a login request + /// + /// + /// + /// + 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; + } + } +} + diff --git a/BlueWest.Api/Startup.cs b/BlueWest.Api/Startup.cs index d8b118a..99711cb 100644 --- a/BlueWest.Api/Startup.cs +++ b/BlueWest.Api/Startup.cs @@ -7,12 +7,11 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System.Text.Json.Serialization; using BlueWest.Tools; -using BlueWest.WebApi.Context.Users; +using BlueWest.WebApi.Interceptors; using BlueWest.WebApi.Interfaces; using BlueWest.WebApi.Tools; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.FileProviders; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; namespace BlueWest.WebApi @@ -24,6 +23,7 @@ namespace BlueWest.WebApi { private readonly IConfiguration _configuration; private readonly IWebHostEnvironment _environment; + private readonly string MyAllowSpecificOrigins = Constants.CorsPolicyName; /// @@ -47,7 +47,7 @@ namespace BlueWest.WebApi options.AddPolicy(name: MyAllowSpecificOrigins, builder => { - builder.WithOrigins("http://127.0.0.1:5173", "http://localhost:5173", "http://127.0.0.1:5173") + builder.WithOrigins("http://localhost:5173/", "http://localhost:5173", "http://127.0.0.1:5173", "localhost:5173") .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); @@ -55,9 +55,27 @@ namespace BlueWest.WebApi }); services - .AddControllers() + .AddResponseCaching() + .AddControllers(options => + { + options.CacheProfiles.Add("Default30", + new CacheProfile() + { + Duration = 30 + }); + }) .AddJsonOptions(options => options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve); + services.AddLogging(builder => + { + builder.AddSimpleConsole(); + }); + + services.AddSession(options => + { + options.Cookie.Domain = "http://localhost:5173"; + options.Cookie.HttpOnly = true; + }); services .AddSwaggerGen(options => { @@ -101,7 +119,14 @@ namespace BlueWest.WebApi }); }); - + + + services.AddGrpc(options => + { + options.Interceptors.Add(); + }); + + services.AddSingleton(); /* services.AddSingleton( new PhysicalFileProvider( @@ -119,10 +144,12 @@ namespace BlueWest.WebApi services .AddSingleton(); + - services.AddAuthServerServices(MyAllowSpecificOrigins, _configuration, _environment); + services.AddAuthServerServices( _configuration, _environment); services.AddScoped(); + switch (allowedDatabase) { case "mysql": @@ -138,10 +165,6 @@ namespace BlueWest.WebApi } - - - - // services.AddGrpc(); } @@ -156,19 +179,17 @@ namespace BlueWest.WebApi //app.UseStaticFiles(); - app.UseDeveloperExceptionPage(); app.UseSwagger(); + app.UseSwaggerUI(c => { c.RoutePrefix = "swagger"; c.SwaggerEndpoint("/swagger/v1/swagger.json", "BlueWest.Api v1"); }); + app.UseStaticFiles(); //app.UseHttpsRedirection(); - - - app.UseRouting(); app.UseCors(MyAllowSpecificOrigins); app.UseAuthentication(); diff --git a/BlueWest.Api/StartupExtensions.cs b/BlueWest.Api/StartupExtensions.cs index c4ad530..11ff6ee 100644 --- a/BlueWest.Api/StartupExtensions.cs +++ b/BlueWest.Api/StartupExtensions.cs @@ -10,6 +10,7 @@ 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; @@ -80,7 +81,9 @@ namespace BlueWest.WebApi .AddDbContextPool(options => options.GetMySqlSettings(configuration, environment)) .AddDbContextPool(options => options.GetMySqlSettings(configuration, environment)) .AddDbContextPool(options => options.GetMySqlSettings(configuration, environment)) - .AddDbContextPool(options => options.GetMySqlSettings(configuration, environment)); + .AddDbContextPool(options => + options.GetMySqlSettings(configuration, environment)) + .AddDbContextPool(options => options.UseInMemoryDatabase("_s")); } /// @@ -100,27 +103,24 @@ namespace BlueWest.WebApi .AddDbContextPool(options => options.UseSqlite(sqliteConString)) .AddDbContextPool(options => options.UseSqlite(sqliteConString)) .AddDbContextPool(options => options.UseSqlite(sqliteConString)) - .AddDbContextPool(options => options.UseSqlite(sqliteConString)); - + .AddDbContextPool(options => options.UseSqlite(sqliteConString)) + .AddDbContextPool(options => options.UseInMemoryDatabase("_s")); + } - internal static IServiceCollection AddAuthServerServices(this IServiceCollection services, string origins, IConfiguration configuration , IWebHostEnvironment environment) + internal static IServiceCollection AddAuthServerServices(this IServiceCollection services, IConfiguration configuration , IWebHostEnvironment environment) { - services.AddScoped(); - services.AddScoped(); - - - services - .AddScoped< UserRepository>() + services.AddScoped() + .AddScoped() + .AddSingleton() + .AddScoped() + .AddScoped() .AddScoped() .AddScoped() + .AddScoped(); - services - .AddIdentityCore(opt => { opt.User.RequireUniqueEmail = true; }) - .AddUserManager() - .AddUserStore(); // Database Context and Swagger @@ -162,6 +162,7 @@ namespace BlueWest.WebApi services.AddAuthentication(options => { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; @@ -170,6 +171,9 @@ namespace BlueWest.WebApi }) .AddCookie(options => { + options.Cookie.SameSite = SameSiteMode.Lax; + options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; + options.Cookie.MaxAge = TimeSpan.FromDays(1); options.LoginPath = "/api/auth/logincookie"; options.LogoutPath = "/api/auth/logout"; }) @@ -206,13 +210,17 @@ namespace BlueWest.WebApi // add identity var identityBuilder = services.AddIdentityCore(o => { + o.User.RequireUniqueEmail = true; + // configure identity options o.Password.RequireDigit = false; o.Password.RequireLowercase = false; o.Password.RequireUppercase = false; o.Password.RequireNonAlphanumeric = false; o.Password.RequiredLength = 6; - }); + }) + .AddUserManager() + .AddUserStore(); identityBuilder = new IdentityBuilder(identityBuilder.UserType, typeof(ApplicationRole), identityBuilder.Services); identityBuilder diff --git a/BlueWest.Api/Users/ApplicationUserManager.cs b/BlueWest.Api/Users/ApplicationUserManager.cs index 96fa33f..c7592b5 100644 --- a/BlueWest.Api/Users/ApplicationUserManager.cs +++ b/BlueWest.Api/Users/ApplicationUserManager.cs @@ -3,6 +3,7 @@ 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; @@ -16,12 +17,16 @@ internal class ApplicationUserManager : UserManager, IUserManag { private readonly IHasher _hasher; private readonly UserRepository _usersRepo; - public ApplicationUserManager(UserRepository store, IOptions optionsAccessor, - IHasher passwordHasher, IEnumerable> userValidators, - IEnumerable> passwordValidators, ILookupNormalizer keyNormalizer, - IdentityErrorDescriber errors, IServiceProvider services, ILogger> logger) : base(store, - optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, - logger) + public ApplicationUserManager( + UserRepository store, + IOptions optionsAccessor, + IHasher passwordHasher, + IEnumerable> userValidators, + IEnumerable> passwordValidators, + ILookupNormalizer keyNormalizer, + IdentityErrorDescriber errors, + IServiceProvider services, + ILogger> logger) : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) { _hasher = passwordHasher; _usersRepo = store; @@ -49,6 +54,7 @@ internal class ApplicationUserManager : UserManager, IUserManag return success; } + protected override async Task VerifyPasswordAsync(IUserPasswordStore store, ApplicationUser user, string password) { string existingHash; @@ -89,7 +95,11 @@ internal class ApplicationUserManager : UserManager, IUserManag Logger.LogWarning(2, "Change password failed for user {userId}.", await GetUserIdAsync(user)); return IdentityResult.Failed(ErrorDescriber.PasswordMismatch()); } - + + public override Task SetAuthenticationTokenAsync(ApplicationUser user, string loginProvider, string tokenName, string tokenValue) + { + return base.SetAuthenticationTokenAsync(user, loginProvider, tokenName, tokenValue); + } private IUserPasswordStore GetPasswordStore() { diff --git a/BlueWest.Api/Users/Auth/AuthManager.cs b/BlueWest.Api/Users/Auth/AuthManager.cs index 3225359..eb77915 100644 --- a/BlueWest.Api/Users/Auth/AuthManager.cs +++ b/BlueWest.Api/Users/Auth/AuthManager.cs @@ -1,14 +1,20 @@ +using System; +using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using BlueWest.Cryptography; +using BlueWest.Data.Application; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; namespace BlueWest.WebApi.Context.Users; internal class AuthManager : IAuthManager { - private readonly IUserManager _userManager; + private readonly ApplicationUserManager _userManager; private readonly UserRepository _usersRepo; + private readonly ISessionManager _sessionManager; private readonly IHasher _hasher; private readonly IJwtFactory _jwtFactory; @@ -19,51 +25,79 @@ internal class AuthManager : IAuthManager /// /// /// - public AuthManager(IUserManager userManager, IHasher hasher, UserRepository usersRepo, IJwtFactory jwtFactory) + public AuthManager( + ApplicationUserManager userManager, + IHasher hasher, + UserRepository usersRepo, + ISessionManager sessionManager, + IJwtFactory jwtFactory) { _userManager = userManager; _hasher = hasher; _usersRepo = usersRepo; _jwtFactory = jwtFactory; + _sessionManager = sessionManager; + } + + public async Task<(bool, ClaimsIdentity, SessionTokenUnique)> DoLogin(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)); + + // Session + var sessionToken = _sessionManager.GetSessionToken(loginRequest, user); + var sessionResponse = new SessionTokenUnique(sessionToken); + return (true, identity, sessionResponse); + } + } + + return (false, null, null); } /// - public async Task GetToken(LoginViewModel loginViewModel) + public async Task<(bool, SessionTokenUnique, AccessToken)> GetToken(LoginRequest loginRequest) { - if (!string.IsNullOrEmpty(loginViewModel.Email) && !string.IsNullOrEmpty(loginViewModel.Password)) + if (!string.IsNullOrEmpty(loginRequest.Email) && !string.IsNullOrEmpty(loginRequest.Password)) { - var user = await _userManager.FindByEmailAsync(loginViewModel.Email); + var user = await _userManager.FindByEmailAsync(loginRequest.Email); if (user != null) { - if (await VerifyLoginAsync(loginViewModel.Email,loginViewModel.Password)) + if (await VerifyLoginByEmailAsync(loginRequest.Email,loginRequest.Password)) { - // Todo generate refresh token - // Todo Add refresh token await _usersRepo.UpdateAsync(user, CancellationToken.None); - var token = await _jwtFactory.GenerateEncodedToken(user.Id.ToString(), user.UserName); - // await _userManager.SetAuthenticationTokenAsync(user, "Income", "ApiUser", token.Token); + // 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 token; + return (completed == IdentityResult.Success, sessionResponse, token); } } } - return null; + return (false, null, null); } /// - public async Task VerifyLoginAsync(string email, string password) + public async Task VerifyLoginByEmailAsync(string email, string password) { var user = await _userManager.FindByEmailAsync(email); if (user == null) { - return false; // return error user doesn't exist + return false; } return await _userManager.CheckPasswordAsync(user, password); - - // return await GenerateAuthenticationResultForUserAsync(user); } private RegisterViewModel FromSignupToUser(RegisterViewModel signupDto) @@ -71,8 +105,6 @@ internal class AuthManager : IAuthManager var pwd = signupDto.Password; var hash = _hasher.CreateHash(pwd, BaseCryptoItem.HashAlgorithm.SHA3_512); signupDto.Password = hash; - signupDto.ConfirmPassword = hash; - return signupDto; } diff --git a/BlueWest.Api/Crypto/BaseCryptoItem.cs b/BlueWest.Api/Users/Auth/Crypto/BaseCryptoItem.cs similarity index 100% rename from BlueWest.Api/Crypto/BaseCryptoItem.cs rename to BlueWest.Api/Users/Auth/Crypto/BaseCryptoItem.cs diff --git a/BlueWest.Api/Crypto/Hasher.cs b/BlueWest.Api/Users/Auth/Crypto/Hasher.cs similarity index 97% rename from BlueWest.Api/Crypto/Hasher.cs rename to BlueWest.Api/Users/Auth/Crypto/Hasher.cs index 500cc84..f930403 100644 --- a/BlueWest.Api/Crypto/Hasher.cs +++ b/BlueWest.Api/Users/Auth/Crypto/Hasher.cs @@ -20,7 +20,7 @@ namespace BlueWest.Cryptography /// /// /// - public string CreateHash(string text, BaseCryptoItem.HashAlgorithm algorithm) + public string CreateHash(string text, HashAlgorithm algorithm) { var salt = CreateRandomString(SaltLength); return CreateHash(text, salt, algorithm, true); diff --git a/BlueWest.Api/Crypto/IHasher.cs b/BlueWest.Api/Users/Auth/Crypto/IHasher.cs similarity index 100% rename from BlueWest.Api/Crypto/IHasher.cs rename to BlueWest.Api/Users/Auth/Crypto/IHasher.cs diff --git a/BlueWest.Api/Crypto/Jwt/IJwtFactory.cs b/BlueWest.Api/Users/Auth/Crypto/IJwtFactory.cs similarity index 100% rename from BlueWest.Api/Crypto/Jwt/IJwtFactory.cs rename to BlueWest.Api/Users/Auth/Crypto/IJwtFactory.cs diff --git a/BlueWest.Api/Crypto/Jwt/IJwtTokenHandler.cs b/BlueWest.Api/Users/Auth/Crypto/IJwtTokenHandler.cs similarity index 100% rename from BlueWest.Api/Crypto/Jwt/IJwtTokenHandler.cs rename to BlueWest.Api/Users/Auth/Crypto/IJwtTokenHandler.cs diff --git a/BlueWest.Api/Users/Auth/Crypto/ISessionHasher.cs b/BlueWest.Api/Users/Auth/Crypto/ISessionHasher.cs new file mode 100644 index 0000000..52525a0 --- /dev/null +++ b/BlueWest.Api/Users/Auth/Crypto/ISessionHasher.cs @@ -0,0 +1,12 @@ +namespace BlueWest.Cryptography +{ + public interface ISessionHasher + { + /// + /// Generates a token for the current session + /// + void GenerateSessionToken(); + + } +} + diff --git a/BlueWest.Api/Crypto/Jwt/ITokenFactory.cs b/BlueWest.Api/Users/Auth/Crypto/ITokenFactory.cs similarity index 100% rename from BlueWest.Api/Crypto/Jwt/ITokenFactory.cs rename to BlueWest.Api/Users/Auth/Crypto/ITokenFactory.cs diff --git a/BlueWest.Api/Crypto/Jwt/JwtFactory.cs b/BlueWest.Api/Users/Auth/Crypto/JwtFactory.cs similarity index 97% rename from BlueWest.Api/Crypto/Jwt/JwtFactory.cs rename to BlueWest.Api/Users/Auth/Crypto/JwtFactory.cs index 948f7ad..a0dd1aa 100644 --- a/BlueWest.Api/Crypto/Jwt/JwtFactory.cs +++ b/BlueWest.Api/Users/Auth/Crypto/JwtFactory.cs @@ -28,6 +28,8 @@ internal class JwtFactory : IJwtFactory { 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), diff --git a/BlueWest.Api/Crypto/Jwt/JwtIssuerOptions.cs b/BlueWest.Api/Users/Auth/Crypto/JwtIssuerOptions.cs similarity index 100% rename from BlueWest.Api/Crypto/Jwt/JwtIssuerOptions.cs rename to BlueWest.Api/Users/Auth/Crypto/JwtIssuerOptions.cs diff --git a/BlueWest.Api/Crypto/Jwt/JwtTokenHandler.cs b/BlueWest.Api/Users/Auth/Crypto/JwtTokenHandler.cs similarity index 100% rename from BlueWest.Api/Crypto/Jwt/JwtTokenHandler.cs rename to BlueWest.Api/Users/Auth/Crypto/JwtTokenHandler.cs diff --git a/BlueWest.Api/Crypto/SHA_512.cs b/BlueWest.Api/Users/Auth/Crypto/SHA_512.cs similarity index 100% rename from BlueWest.Api/Crypto/SHA_512.cs rename to BlueWest.Api/Users/Auth/Crypto/SHA_512.cs diff --git a/BlueWest.Api/Users/Auth/IAuthManager.cs b/BlueWest.Api/Users/Auth/IAuthManager.cs index bf18985..d775f43 100644 --- a/BlueWest.Api/Users/Auth/IAuthManager.cs +++ b/BlueWest.Api/Users/Auth/IAuthManager.cs @@ -1,4 +1,7 @@ +using System; +using System.Security.Claims; using System.Threading.Tasks; +using BlueWest.Data.Application; using Microsoft.AspNetCore.Identity; namespace BlueWest.WebApi.Context.Users; @@ -21,13 +24,20 @@ public interface IAuthManager /// /// /// - Task VerifyLoginAsync(string email, string password); + Task VerifyLoginByEmailAsync(string email, string password); /// /// GetToken /// - /// + /// /// - Task GetToken(LoginViewModel loginViewModel); + Task<(bool, SessionTokenUnique, AccessToken)> GetToken(LoginRequest loginRequest); + + /// + /// Does Login + /// + /// + /// + Task<(bool, ClaimsIdentity, SessionTokenUnique)> DoLogin(LoginRequest loginRequest); } \ No newline at end of file diff --git a/BlueWest.Api/Users/IUserManager.cs b/BlueWest.Api/Users/IUserManager.cs index 9b2ac56..9db5b03 100644 --- a/BlueWest.Api/Users/IUserManager.cs +++ b/BlueWest.Api/Users/IUserManager.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using BlueWest.Data.Application; using Microsoft.AspNetCore.Identity; namespace BlueWest.WebApi.Context.Users @@ -26,6 +27,9 @@ namespace BlueWest.WebApi.Context.Users /// /// Task FindByEmailAsync(string email); + + + } } diff --git a/BlueWest.Api/Users/Models/LoginViewModel.cs b/BlueWest.Api/Users/Models/LoginRequest.cs similarity index 73% rename from BlueWest.Api/Users/Models/LoginViewModel.cs rename to BlueWest.Api/Users/Models/LoginRequest.cs index 2e5cfa4..7dea8b4 100644 --- a/BlueWest.Api/Users/Models/LoginViewModel.cs +++ b/BlueWest.Api/Users/Models/LoginRequest.cs @@ -4,9 +4,9 @@ namespace BlueWest.WebApi.Context.Users { // from: https://github.com/dotnet/aspnetcore/tree/main/src/Identity/samples/IdentitySample.Mvc/Models/AccountViewModels /// - /// Login View Model + /// Login Request adata /// - public class LoginViewModel + public class LoginRequest { /// /// Email @@ -21,12 +21,10 @@ namespace BlueWest.WebApi.Context.Users [Required] [DataType(DataType.Password)] public string Password { get; set; } + + [Required] + public string Uuid { get; set; } - /// - /// RememberMe - /// - [Display(Name = "Remember me?")] - public bool RememberMe { get; set; } } } diff --git a/BlueWest.Api/Users/Models/RegisterViewModel.cs b/BlueWest.Api/Users/Models/RegisterViewModel.cs index d599d3c..dc548a9 100644 --- a/BlueWest.Api/Users/Models/RegisterViewModel.cs +++ b/BlueWest.Api/Users/Models/RegisterViewModel.cs @@ -35,6 +35,16 @@ public class RegisterViewModel [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } + + + /// + /// ConfirmPassword + /// + [DataType(DataType.PhoneNumber)] + [Display(Name = "Phone Number")] + public string PhoneNumber { get; set; } + + /// /// Convert RegisterViewModel to ApplicationUser @@ -46,6 +56,7 @@ public class RegisterViewModel newUser.Email = Email; newUser.PasswordHash = Password; newUser.UserName = Username; + newUser.PhoneNumber = PhoneNumber; return newUser; } } \ No newline at end of file diff --git a/BlueWest.Api/Users/RoleStore.cs b/BlueWest.Api/Users/RoleStore.cs deleted file mode 100644 index effeac8..0000000 --- a/BlueWest.Api/Users/RoleStore.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; - -namespace BlueWest.WebApi.Context.Users; - -/// -/// Role storage management -/// -/*public class RoleStore : RoleStore -{ - -}*/ \ No newline at end of file diff --git a/BlueWest.Api/appsettings.json b/BlueWest.Api/appsettings.json index 547b74d..a12487d 100644 --- a/BlueWest.Api/appsettings.json +++ b/BlueWest.Api/appsettings.json @@ -10,7 +10,12 @@ "ConnectionStrings": { "DockerMySQL": "server=db;user=blueuser;password=dXjw127124dJ;database=bluedb;" }, + "REDIS_CONNECTION_STRING": "redis://localhost:6379", "AuthSettings": { "SecretKey": "iJWHDmHLpUA283sqsfhqGbMRdRj1PVkH" + }, + "JwtIssuerOptions": { + "Issuer": "SomeIssuer", + "Audience": "http://localhost:5000" } } diff --git a/BlueWest.Console/.dockerignore b/BlueWest.Console/.dockerignore new file mode 100644 index 0000000..38bece4 --- /dev/null +++ b/BlueWest.Console/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/BlueWest.Console/BlueWest.Console.csproj b/BlueWest.Console/BlueWest.Console.csproj new file mode 100644 index 0000000..6e6affd --- /dev/null +++ b/BlueWest.Console/BlueWest.Console.csproj @@ -0,0 +1,11 @@ + + + + Exe + net6.0 + enable + enable + Linux + + + diff --git a/BlueWest.Console/Dockerfile b/BlueWest.Console/Dockerfile new file mode 100644 index 0000000..b231d22 --- /dev/null +++ b/BlueWest.Console/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["BlueWest.Console/BlueWest.Console.csproj", "BlueWest.Console/"] +RUN dotnet restore "BlueWest.Console/BlueWest.Console.csproj" +COPY . . +WORKDIR "/src/BlueWest.Console" +RUN dotnet build "BlueWest.Console.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "BlueWest.Console.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "BlueWest.Console.dll"] diff --git a/BlueWest.Console/Program.cs b/BlueWest.Console/Program.cs new file mode 100644 index 0000000..1920ca7 --- /dev/null +++ b/BlueWest.Console/Program.cs @@ -0,0 +1,25 @@ +using System.Globalization; + +/*var s = CultureInfo.GetCultures(CultureTypes.AllCultures); + +foreach (var culture in s) +{ + var actualCulture = culture; + Console.WriteLine(actualCulture); + +}*/ + +async Task TaskReceiver(Func task) +{ + Console.WriteLine("Hello"); + await task.Invoke(); +} + +async Task Task2() +{ + Console.WriteLine("World"); +} + +var f = Task2; + +await TaskReceiver(f); \ No newline at end of file diff --git a/BlueWest.Api/Users/Auth/AccessToken.cs b/BlueWest.Data.Application/AccessToken.cs similarity index 99% rename from BlueWest.Api/Users/Auth/AccessToken.cs rename to BlueWest.Data.Application/AccessToken.cs index 3a19ad1..cdc5771 100644 --- a/BlueWest.Api/Users/Auth/AccessToken.cs +++ b/BlueWest.Data.Application/AccessToken.cs @@ -1,3 +1,4 @@ + namespace BlueWest.WebApi.Context.Users { public class AccessToken diff --git a/BlueWest.Data.Application/ApplicationDevice/ApplicationDevice.cs b/BlueWest.Data.Application/ApplicationDevice/ApplicationDevice.cs new file mode 100644 index 0000000..f0dd28f --- /dev/null +++ b/BlueWest.Data.Application/ApplicationDevice/ApplicationDevice.cs @@ -0,0 +1,14 @@ +using System.Globalization; +using System.Net; + +namespace BlueWest.Data.Application +{ + public class ApplicationDevice + { + public string Id { get; set; } + public ClientOperatingSystem ClientOperatingSystem { get; set; } + public ClientPlatformType ClientPlatformType { get; set; } + public string Resolution { get; set; } + } +} + diff --git a/BlueWest.Data.Application/ApplicationDevice/ApplicationDeviceCreate.cs b/BlueWest.Data.Application/ApplicationDevice/ApplicationDeviceCreate.cs new file mode 100644 index 0000000..b46e6af --- /dev/null +++ b/BlueWest.Data.Application/ApplicationDevice/ApplicationDeviceCreate.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace BlueWest.Data.Application +{ + + public class ApplicationDeviceCreate + { + [Required] public string Uuid; + public string Languages { get; set; } + public string UserAgent { get; set; } + public OperatingSystemType OperatingSystemType { get; set; } + } +} + diff --git a/BlueWest.Data.Application/ApplicationDevice/ClientOperatingSystem.cs b/BlueWest.Data.Application/ApplicationDevice/ClientOperatingSystem.cs new file mode 100644 index 0000000..4213583 --- /dev/null +++ b/BlueWest.Data.Application/ApplicationDevice/ClientOperatingSystem.cs @@ -0,0 +1,39 @@ +namespace BlueWest.Data.Application +{ + public enum ClientOperatingSystem + { + Unknown, + WindowsVista, + Windows7, + Windows8, + Windows81, + Windows10, + Windows2003, + WindowsXp, + WindowsPhone8, + Playstation, + Wii, + Linux, + Linux64, + Curl, + Bada, + Iphone, + Ipad, + ChromeOs, + Osxx, + OsxElCapitan, + OsxYosemite, + OsxMavericks, + OsxMountainLion, + OsxPuma, + OsxPanther, + OsxJaguar, + OsxCheetah, + OsxLeopard, + OsxSnowLeopard, + MacOsSierra, + OsxLion + } + +} + diff --git a/BlueWest.Data.Application/ApplicationDevice/ClientPlatformType.cs b/BlueWest.Data.Application/ApplicationDevice/ClientPlatformType.cs new file mode 100644 index 0000000..4b78e42 --- /dev/null +++ b/BlueWest.Data.Application/ApplicationDevice/ClientPlatformType.cs @@ -0,0 +1,17 @@ +namespace BlueWest.Data.Application; + +public enum ClientPlatformType +{ + MicrosoftWindows, + WindowsPhone, + IPhone, + Samsung, + Unknown, + Wii, + Linux, + Blackberry, + Playstation, + Ipad, + IPod, + Curl +} diff --git a/BlueWest.Data.Application/ApplicationDevice/OperatingSystemType.cs b/BlueWest.Data.Application/ApplicationDevice/OperatingSystemType.cs new file mode 100644 index 0000000..c192858 --- /dev/null +++ b/BlueWest.Data.Application/ApplicationDevice/OperatingSystemType.cs @@ -0,0 +1,12 @@ +namespace BlueWest.Data.Application +{ + public enum OperatingSystemType + { + MacOS, + iOS, + Windows, + Android, + Linux + } +} + diff --git a/BlueWest.Data.Capital/Application/ApplicationRole/ApplicationRole.cs b/BlueWest.Data.Application/ApplicationRole/ApplicationRole.cs similarity index 100% rename from BlueWest.Data.Capital/Application/ApplicationRole/ApplicationRole.cs rename to BlueWest.Data.Application/ApplicationRole/ApplicationRole.cs diff --git a/BlueWest.Data.Capital/Application/ApplicationRole/ApplicationRoleUnique.cs b/BlueWest.Data.Application/ApplicationRole/ApplicationRoleUnique.cs similarity index 100% rename from BlueWest.Data.Capital/Application/ApplicationRole/ApplicationRoleUnique.cs rename to BlueWest.Data.Application/ApplicationRole/ApplicationRoleUnique.cs diff --git a/BlueWest.Data.Capital/Application/ApplicationRoleClaim/ApplicationRoleClaim.cs b/BlueWest.Data.Application/ApplicationRoleClaim/ApplicationRoleClaim.cs similarity index 100% rename from BlueWest.Data.Capital/Application/ApplicationRoleClaim/ApplicationRoleClaim.cs rename to BlueWest.Data.Application/ApplicationRoleClaim/ApplicationRoleClaim.cs diff --git a/BlueWest.Data.Capital/Application/ApplicationRoleClaim/ApplicationRoleClaimUnique.cs b/BlueWest.Data.Application/ApplicationRoleClaim/ApplicationRoleClaimUnique.cs similarity index 100% rename from BlueWest.Data.Capital/Application/ApplicationRoleClaim/ApplicationRoleClaimUnique.cs rename to BlueWest.Data.Application/ApplicationRoleClaim/ApplicationRoleClaimUnique.cs diff --git a/BlueWest.Data.Capital/Application/ApplicationUser/ApplicationUser.cs b/BlueWest.Data.Application/ApplicationUser/ApplicationUser.cs similarity index 93% rename from BlueWest.Data.Capital/Application/ApplicationUser/ApplicationUser.cs rename to BlueWest.Data.Application/ApplicationUser/ApplicationUser.cs index dabd0a9..a53a8c8 100644 --- a/BlueWest.Data.Capital/Application/ApplicationUser/ApplicationUser.cs +++ b/BlueWest.Data.Application/ApplicationUser/ApplicationUser.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using BlueWest.Data; +using BlueWest.Data.Application; using MapTo; using Microsoft.AspNetCore.Identity; @@ -20,10 +21,8 @@ namespace BlueWest.WebApi.Context.Users /// [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [PersonalData] - public new string Id { get; set; } - - public List Users { get; set; } - + public override string Id { get; set; } + [ProtectedPersonalData] public override string UserName { get; set; } @@ -97,5 +96,11 @@ namespace BlueWest.WebApi.Context.Users /// Gets or sets the number of failed login attempts for the current user. /// public override int AccessFailedCount { get; set; } + + public List SessionToken { get; set; } + + public List SessionDatas { get; set; } + + } } \ No newline at end of file diff --git a/BlueWest.Data.Capital/Application/ApplicationUser/ApplicationUserUnique.cs b/BlueWest.Data.Application/ApplicationUser/ApplicationUserUnique.cs similarity index 100% rename from BlueWest.Data.Capital/Application/ApplicationUser/ApplicationUserUnique.cs rename to BlueWest.Data.Application/ApplicationUser/ApplicationUserUnique.cs diff --git a/BlueWest.Data.Capital/Application/ApplicationUserClaim/ApplicationUserClaim.cs b/BlueWest.Data.Application/ApplicationUserClaim/ApplicationUserClaim.cs similarity index 100% rename from BlueWest.Data.Capital/Application/ApplicationUserClaim/ApplicationUserClaim.cs rename to BlueWest.Data.Application/ApplicationUserClaim/ApplicationUserClaim.cs diff --git a/BlueWest.Data.Capital/Application/ApplicationUserClaim/ApplicationUserClaimUnique.cs b/BlueWest.Data.Application/ApplicationUserClaim/ApplicationUserClaimUnique.cs similarity index 100% rename from BlueWest.Data.Capital/Application/ApplicationUserClaim/ApplicationUserClaimUnique.cs rename to BlueWest.Data.Application/ApplicationUserClaim/ApplicationUserClaimUnique.cs diff --git a/BlueWest.Data.Capital/Application/ApplicationUserLogin/ApplicationUserLogin.cs b/BlueWest.Data.Application/ApplicationUserLogin/ApplicationUserLogin.cs similarity index 100% rename from BlueWest.Data.Capital/Application/ApplicationUserLogin/ApplicationUserLogin.cs rename to BlueWest.Data.Application/ApplicationUserLogin/ApplicationUserLogin.cs diff --git a/BlueWest.Data.Capital/Application/ApplicationUserRole/ApplicationUserRole.cs b/BlueWest.Data.Application/ApplicationUserRole/ApplicationUserRole.cs similarity index 100% rename from BlueWest.Data.Capital/Application/ApplicationUserRole/ApplicationUserRole.cs rename to BlueWest.Data.Application/ApplicationUserRole/ApplicationUserRole.cs diff --git a/BlueWest.Data.Capital/Application/ApplicationUserRole/ApplicationUserRoleUnique.cs b/BlueWest.Data.Application/ApplicationUserRole/ApplicationUserRoleUnique.cs similarity index 100% rename from BlueWest.Data.Capital/Application/ApplicationUserRole/ApplicationUserRoleUnique.cs rename to BlueWest.Data.Application/ApplicationUserRole/ApplicationUserRoleUnique.cs diff --git a/BlueWest.Data.Application/ApplicationUserToken/ApplicationUserToken.cs b/BlueWest.Data.Application/ApplicationUserToken/ApplicationUserToken.cs new file mode 100644 index 0000000..6aefa55 --- /dev/null +++ b/BlueWest.Data.Application/ApplicationUserToken/ApplicationUserToken.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Identity; + +namespace BlueWest.WebApi.Context.Users; + +/// +public class ApplicationUserToken : IdentityUserToken +{ + public string Id { get; set; } +} \ No newline at end of file diff --git a/BlueWest.Data.Application/BlueWest.Data.Application.csproj b/BlueWest.Data.Application/BlueWest.Data.Application.csproj new file mode 100644 index 0000000..04745fb --- /dev/null +++ b/BlueWest.Data.Application/BlueWest.Data.Application.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + + + + + diff --git a/BlueWest.Data.Application/Crypto/Jwt/IJwtTokenHandler.cs b/BlueWest.Data.Application/Crypto/Jwt/IJwtTokenHandler.cs new file mode 100644 index 0000000..a88b355 --- /dev/null +++ b/BlueWest.Data.Application/Crypto/Jwt/IJwtTokenHandler.cs @@ -0,0 +1,12 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using Microsoft.IdentityModel.Tokens; + +namespace BlueWest.WebApi.Context.Users +{ + public interface IJwtTokenHandler + { + string WriteToken(JwtSecurityToken jwt); + ClaimsPrincipal ValidateToken(string token, TokenValidationParameters tokenValidationParameters); + } +} \ No newline at end of file diff --git a/BlueWest.Data.Application/Crypto/Jwt/ITokenFactory.cs b/BlueWest.Data.Application/Crypto/Jwt/ITokenFactory.cs new file mode 100644 index 0000000..392e198 --- /dev/null +++ b/BlueWest.Data.Application/Crypto/Jwt/ITokenFactory.cs @@ -0,0 +1,6 @@ +namespace BlueWest.WebApi.Context.Users; + +internal interface ITokenFactory +{ + string GenerateToken(int size= 32); +} \ No newline at end of file diff --git a/BlueWest.Data.Application/Crypto/Jwt/JwtIssuerOptions.cs b/BlueWest.Data.Application/Crypto/Jwt/JwtIssuerOptions.cs new file mode 100644 index 0000000..530e5a5 --- /dev/null +++ b/BlueWest.Data.Application/Crypto/Jwt/JwtIssuerOptions.cs @@ -0,0 +1,54 @@ +using System; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Tokens; + +namespace BlueWest.WebApi.Context.Users; + +internal class JwtIssuerOptions +{ + /// + /// 4.1.1. "iss" (Issuer) Claim - The "iss" (issuer) claim identifies the principal that issued the JWT. + /// + public string Issuer { get; set; } + + /// + /// 4.1.2. "sub" (Subject) Claim - The "sub" (subject) claim identifies the principal that is the subject of the JWT. + /// + public string Subject { get; set; } + + /// + /// 4.1.3. "aud" (Audience) Claim - The "aud" (audience) claim identifies the recipients that the JWT is intended for. + /// + public string Audience { get; set; } + + /// + /// 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. + /// + public DateTime Expiration => IssuedAt.Add(ValidFor); + + /// + /// 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. + /// + public DateTime NotBefore => DateTime.UtcNow; + + /// + /// 4.1.6. "iat" (Issued At) Claim - The "iat" (issued at) claim identifies the time at which the JWT was issued. + /// + public DateTime IssuedAt => DateTime.UtcNow; + + /// + /// Set the timespan the token will be valid for (default is 120 min) + /// + public TimeSpan ValidFor { get; set; } = TimeSpan.FromMinutes(120); + + /// + /// "jti" (JWT ID) Claim (default ID is a GUID) + /// + public Func> JtiGenerator => + () => Task.FromResult(Guid.NewGuid().ToString()); + + /// + /// The signing key to use when generating tokens. + /// + public SigningCredentials SigningCredentials { get; set; } +} \ No newline at end of file diff --git a/BlueWest.Data.Application/Crypto/Jwt/JwtTokenHandler.cs b/BlueWest.Data.Application/Crypto/Jwt/JwtTokenHandler.cs new file mode 100644 index 0000000..54820c9 --- /dev/null +++ b/BlueWest.Data.Application/Crypto/Jwt/JwtTokenHandler.cs @@ -0,0 +1,53 @@ +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using Microsoft.IdentityModel.Tokens; + +namespace BlueWest.WebApi.Context.Users; + +public class JwtTokenHandler : IJwtTokenHandler +{ + private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler; + + /// + /// JwtTokenHandler + /// + public JwtTokenHandler() + { + _jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); + } + + /// + /// Write token + /// + /// + /// + public string WriteToken(JwtSecurityToken jwt) + { + return _jwtSecurityTokenHandler.WriteToken(jwt); + } + + /// + /// Validate Token + /// + /// + /// + /// + /// + 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; + } + } +} \ No newline at end of file diff --git a/BlueWest.Data.Application/SessionToken/SessionCacheItem.cs b/BlueWest.Data.Application/SessionToken/SessionCacheItem.cs new file mode 100644 index 0000000..7a13659 --- /dev/null +++ b/BlueWest.Data.Application/SessionToken/SessionCacheItem.cs @@ -0,0 +1,16 @@ +using Redis.OM.Modeling; + +namespace BlueWest.Data.Application +{ + public class SessionCacheItem + { + public string UserId { get; set; } + + [RedisIdField] [Indexed] + public string SessionTokenId { get; set; } + + [Indexed] + public string AccessToken { get; set; } + } +} + diff --git a/BlueWest.Data.Application/SessionToken/SessionData.cs b/BlueWest.Data.Application/SessionToken/SessionData.cs new file mode 100644 index 0000000..49a6a77 --- /dev/null +++ b/BlueWest.Data.Application/SessionToken/SessionData.cs @@ -0,0 +1,14 @@ +using BlueWest.WebApi.Context.Users; + +namespace BlueWest.Data.Application +{ + public class SessionData + { + public int Id { get; set; } + public string UserId { get; set; } + public string SessionTokenId { get; set; } + public SessionToken SessionToken { get; set; } + public ApplicationUser User { get; set; } + } +} + diff --git a/BlueWest.Data.Application/SessionToken/SessionToken.cs b/BlueWest.Data.Application/SessionToken/SessionToken.cs new file mode 100644 index 0000000..b73d4ec --- /dev/null +++ b/BlueWest.Data.Application/SessionToken/SessionToken.cs @@ -0,0 +1,33 @@ +using BlueWest.WebApi.Context.Users; +using MapTo; + +namespace BlueWest.Data.Application +{ + [MapFrom(new [] + { + typeof(SessionTokenUnique) + })] + public partial class SessionToken + { + [IgnoreMemberMapTo] + public string Id { get; set; } + + public string Token { get; set; } + public TimeSpan ValidFor { get; set;} + + public bool IsValid { get; set; } + + public DateTime CreatedDate { get; set; } + + public ApplicationUser User { get; set; } + public string UserId { get; set; } + + public ApplicationDevice ApplicationDevice { get; set; } + + public SessionData SessionData { get; set; } + + public string AccessToken { get; set; } + + } +} + diff --git a/BlueWest.Data.Application/SessionToken/SessionTokenUnique.cs b/BlueWest.Data.Application/SessionToken/SessionTokenUnique.cs new file mode 100644 index 0000000..07acccf --- /dev/null +++ b/BlueWest.Data.Application/SessionToken/SessionTokenUnique.cs @@ -0,0 +1,17 @@ +using MapTo; + +namespace BlueWest.Data.Application +{ + [MapFrom(typeof(SessionToken))] + public partial class SessionTokenUnique + { + public string Id { get; set; } + + public string Token { get; set; } + public TimeSpan ValidFor { get; set;} + public DateTime CreatedDate { get; set; } + public string UserId { get; set; } + + } +} + diff --git a/BlueWest.Data.Capital/Application/ApplicationUserToken/ApplicationUserToken.cs b/BlueWest.Data.Capital/Application/ApplicationUserToken/ApplicationUserToken.cs deleted file mode 100644 index 8d88574..0000000 --- a/BlueWest.Data.Capital/Application/ApplicationUserToken/ApplicationUserToken.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace BlueWest.WebApi.Context.Users; - -/// -public class ApplicationUserToken : IdentityUserToken { } \ No newline at end of file diff --git a/BlueWest.Data.Capital/BlueWest.Data.Capital.csproj b/BlueWest.Data.Capital/BlueWest.Data.Capital.csproj index e63e3d0..d216f12 100644 --- a/BlueWest.Data.Capital/BlueWest.Data.Capital.csproj +++ b/BlueWest.Data.Capital/BlueWest.Data.Capital.csproj @@ -17,11 +17,12 @@ - - + + + diff --git a/BlueWest.Frontend b/BlueWest.Frontend index 2afc7cc..b67ba4a 160000 --- a/BlueWest.Frontend +++ b/BlueWest.Frontend @@ -1 +1 @@ -Subproject commit 2afc7cc1eb60369db3d2db021d514647baba93dc +Subproject commit b67ba4a1a6bb8a58ee44b34dbadbd6a0e99c5c87 diff --git a/BlueWest.Posts/BlueWest.Posts.csproj b/BlueWest.Posts/BlueWest.Posts.csproj new file mode 100644 index 0000000..eb2460e --- /dev/null +++ b/BlueWest.Posts/BlueWest.Posts.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/BlueWest.Posts/PostType.cs b/BlueWest.Posts/PostType.cs new file mode 100644 index 0000000..22b614a --- /dev/null +++ b/BlueWest.Posts/PostType.cs @@ -0,0 +1,8 @@ +namespace BlueWest.Posts +{ + public class PostType + { + + } +} + diff --git a/BlueWest.sln b/BlueWest.sln index 98d9c29..a3fe70c 100644 --- a/BlueWest.sln +++ b/BlueWest.sln @@ -36,6 +36,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlueWest.Api.Gateway", "Blu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlueWest.EfMethods", "include\BlueWest.EfMethods\src\BlueWest.EfMethods\BlueWest.EfMethods.csproj", "{BBF5E860-A880-450B-B6C9-EF92F6421B3D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlueWest.Data.Application", "BlueWest.Data.Application\BlueWest.Data.Application.csproj", "{F0F4A1F3-E279-4374-B146-1E1D82011574}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlueWest.Console", "BlueWest.Console\BlueWest.Console.csproj", "{D50ED30A-B560-4D00-8D56-653F994045CE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlueWest.Posts", "BlueWest.Posts\BlueWest.Posts.csproj", "{78540D1E-240F-41FF-B8ED-A265DD5ADCB5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -78,6 +84,18 @@ Global {BBF5E860-A880-450B-B6C9-EF92F6421B3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {BBF5E860-A880-450B-B6C9-EF92F6421B3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {BBF5E860-A880-450B-B6C9-EF92F6421B3D}.Release|Any CPU.Build.0 = Release|Any CPU + {F0F4A1F3-E279-4374-B146-1E1D82011574}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F0F4A1F3-E279-4374-B146-1E1D82011574}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0F4A1F3-E279-4374-B146-1E1D82011574}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0F4A1F3-E279-4374-B146-1E1D82011574}.Release|Any CPU.Build.0 = Release|Any CPU + {D50ED30A-B560-4D00-8D56-653F994045CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D50ED30A-B560-4D00-8D56-653F994045CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D50ED30A-B560-4D00-8D56-653F994045CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D50ED30A-B560-4D00-8D56-653F994045CE}.Release|Any CPU.Build.0 = Release|Any CPU + {78540D1E-240F-41FF-B8ED-A265DD5ADCB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78540D1E-240F-41FF-B8ED-A265DD5ADCB5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78540D1E-240F-41FF-B8ED-A265DD5ADCB5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78540D1E-240F-41FF-B8ED-A265DD5ADCB5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/BlueWest/Core/Events/EventManager.cs b/BlueWest/Core/Events/EventManager.cs index b7059c1..724946f 100644 --- a/BlueWest/Core/Events/EventManager.cs +++ b/BlueWest/Core/Events/EventManager.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; namespace BlueWest.Tools { @@ -83,8 +85,8 @@ namespace BlueWest.Tools #endif List list = _subscribersList[type]; - - for (int i=0; i; diff --git a/BlueWest/Core/Tests/BlueSocketServerSingleton.cs b/BlueWest/Core/Tests/BlueSocketServerSingleton.cs index 32a20c0..3de810c 100644 --- a/BlueWest/Core/Tests/BlueSocketServerSingleton.cs +++ b/BlueWest/Core/Tests/BlueSocketServerSingleton.cs @@ -42,7 +42,7 @@ namespace PerformanceSolution void StartServer() { - string ip = "127.0.0.1"; + string ip = "127.0.0-preview.7.22376.2.1"; int port = 80; var server = new TcpListener(IPAddress.Parse(ip), port); diff --git a/docker-compose.yml b/docker-compose.yml index ebe09e8..4a61b32 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,7 +27,7 @@ services: context: ./ dockerfile: ./BlueWest.Api/Dockerfile ports: - - 8080:80 + - "8080:80" environment: VIRTUAL_HOST: localhost restart: always @@ -35,4 +35,11 @@ services: - db:db container_name: BW1_API - \ No newline at end of file + redis: + image: "redis:alpine" + command: redis-server --requirepass Sup3rSecurePass0rd + ports: + - "6379:6379" + + environment: + - REDIS_REPLICATION_MODE=master