Session wip

This commit is contained in:
Wvader 2022-09-17 20:13:35 +01:00
parent e0e9df2edd
commit 3b8d82049f
79 changed files with 1086 additions and 244 deletions

View File

@ -12,16 +12,18 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="6.0.2" />
<PackageReference Include="Grpc.AspNetCore" Version="2.49.0-pre1" />
<PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="6.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Authorization.Policy" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.2">
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.2">
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@ -42,6 +44,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BlueWest.Data.Application\BlueWest.Data.Application.csproj" />
<ProjectReference Include="..\BlueWest.Data.Capital\BlueWest.Data.Capital.csproj" />
<ProjectReference Include="..\BlueWest\BlueWest.csproj" />
<ProjectReference Include="..\include\BlueWest.EfMethods\src\BlueWest.EfMethods\BlueWest.EfMethods.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />

View File

@ -25,25 +25,24 @@ namespace BlueWest.WebApi.Context
[EfGetMany(typeof(ApplicationUserClaimUnique))]
public sealed override DbSet<ApplicationUserClaim> UserClaims { get; set; }
[EfGetMany(typeof(ApplicationUserRoleUnique))]
/// <inheritdoc />
[EfGetMany(typeof(ApplicationUserRoleUnique))]
public sealed override DbSet<ApplicationUserRole> UserRoles { get; set; }
[EfGetMany(typeof(ApplicationRoleUnique))]
/// <inheritdoc />
[EfGetMany(typeof(ApplicationRoleUnique))]
public sealed override DbSet<ApplicationRole> Roles { get; set; }
[EfGetMany(typeof(ApplicationRoleClaimUnique))]
/// <inheritdoc />
[EfGetMany(typeof(ApplicationRoleClaimUnique))]
public sealed override DbSet<ApplicationRoleClaim> RoleClaims { get; set; }
[EfGetMany(typeof(ApplicationUserUnique))]
[EfUpdateMethods( updateType: typeof(ApplicationUserUnique), returnType: typeof(ApplicationUserUnique), keyPropertyMemberName: nameof(ApplicationUserUnique.Id))]
public sealed override DbSet<ApplicationUser> Users { get; set; }
#region Initialization
/// <inheritdoc />
#region Initialization
public ApplicationUserDbContext(DbContextOptions<ApplicationUserDbContext> options) : base(options)
{
Database.EnsureCreated();
@ -55,62 +54,13 @@ namespace BlueWest.WebApi.Context
{
base.OnModelCreating(builder);
builder.Entity<ApplicationUserRole>().ToTable("UserRoles");
builder.Entity<ApplicationUser>(b =>
{
b.HasMany<ApplicationUserRole>()
.WithOne()
.HasForeignKey(ur => ur.UserId).IsRequired();
});
builder.Entity<ApplicationUser>().ToTable("ApplicationUser")
.HasKey(x => x.Id);
builder.Entity<ApplicationRole>(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<ApplicationUserRole>().WithOne().HasForeignKey(ur => ur.RoleId).IsRequired();
b.HasMany<ApplicationRoleClaim>().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired();
});
builder.Entity<ApplicationUserRole>().HasOne<ApplicationRole>(x => x.ApplicationRole);
builder.Entity<ApplicationRoleClaim>().HasOne<ApplicationRole>(x => x.ApplicationRole);
builder.Entity<ApplicationUserClaim>().HasOne<ApplicationUser>(x => x.ApplicationUser);
builder.Entity<ApplicationRoleClaim>(b =>
{
b.HasKey(rc => rc.Id);
b.ToTable("RoleClaims");
});
builder.Entity<ApplicationUserRole>(b =>
{
b.HasKey(r => new {r.UserId, r.RoleId});
b.ToTable("UserRoles");
});
builder.Entity<User>(b => b.HasOne<ApplicationUser>()
.WithMany(x => x.Users)
.HasForeignKey(x => x.ApplicationUserId));
builder.Entity<ApplicationRoleClaim>().ToTable("RoleClaims");
builder.Entity<ApplicationUserRole>().ToTable("UserRole");
builder.ConfigureCurrentDbModel();
}
#endregion
/// <inheritdoc />
}
}

View File

@ -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() { }
/// <summary>
/// Setup the database model
@ -24,11 +23,105 @@ namespace BlueWest.WebApi.EF.Model
modelBuilder
.ConfigureDatabaseKeys()
.CurrencyModel()
.ConfigureUserModel();
.ConfigureUserModel()
.ConfigureAppContextModel();
//.ConfigureIdentityModel();
}
#endregion
#region Application Users
/// <summary>
/// Configure App context model
/// </summary>
/// <param name="modelBuilder"></param>
private static void ConfigureAppContextModel(this ModelBuilder builder)
{
builder.Entity<ApplicationUserRole>().ToTable("UserRoles");
builder.Entity<ApplicationUser>(b =>
{
b.HasMany<ApplicationUserRole>()
.WithOne(b => b.User)
.HasForeignKey(ur => ur.UserId).IsRequired();
});
builder.Entity<ApplicationUser>().ToTable("ApplicationUser")
.HasKey(x => x.Id);
builder.Entity<ApplicationRole>(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<ApplicationUserRole>().WithOne().HasForeignKey(ur => ur.RoleId).IsRequired();
b.HasMany<ApplicationRoleClaim>().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired();
});
builder.Entity<ApplicationUserRole>().HasOne(x => x.ApplicationRole);
builder.Entity<ApplicationRoleClaim>().HasOne<ApplicationRole>(x => x.ApplicationRole);
builder.Entity<ApplicationUserClaim>().HasOne<ApplicationUser>(x => x.ApplicationUser);
builder.Entity<ApplicationRoleClaim>(b =>
{
b.HasKey(rc => rc.Id);
b.ToTable("RoleClaims");
});
builder.Entity<ApplicationUserRole>(b =>
{
b.HasKey(r => new {r.UserId, r.RoleId});
b.ToTable("UserRoles");
});
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 =>
b.HasKey(x => x.Id));
builder.Entity<SessionToken>(b =>
b.Property(x => x.Id).ValueGeneratedOnAdd());
// Session Data
builder.Entity<SessionData>()
.HasOne(b => b.User)
.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 =>
b.HasKey(x => x.Id));
builder.Entity<SessionData>(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<User>(b => b.HasOne<ApplicationUser>()
.WithMany(x => x.Users)
.HasForeignKey(x => x.ApplicationUserId));
return modelBuilder;
}
#endregion
public static void ConfigureIdentityModel(this ModelBuilder builder)
{
builder.Entity<ApplicationUser>(b =>
{
b.HasMany<ApplicationUserRole>().WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
});
builder.Entity<ApplicationRole>(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<ApplicationUserRole>().WithOne().HasForeignKey(ur => ur.RoleId).IsRequired();
b.HasMany<ApplicationRoleClaim>().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired();
});
builder.Entity<ApplicationRoleClaim>(b =>
{
b.HasKey(rc => rc.Id);
b.ToTable("RoleClaims");
});
builder.Entity<ApplicationUserRole>(b =>
{
b.HasKey(r => new { r.UserId, r.RoleId });
b.ToTable("UserRoles");
});
}
}
}

View File

@ -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
{
/// <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

@ -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());
}

View File

@ -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
{
/// <inheritdoc />
[ApiController]
[Route("application/users")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
@ -19,6 +21,7 @@ namespace BlueWest.WebApi.Controllers
{
private readonly ApplicationUserDbContext _dbContext;
/// <inheritdoc />
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
}
}
/// <summary>
/// Application Constants
/// </summary>
public static class Constants
{
/// <summary>
/// Policy Name
/// </summary>
public const string CorsPolicyName = "_myAllowSpecificOrigins";
}

View File

@ -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;
/// <summary>
/// Auth controller
/// </summary>
[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;
/// <summary>
///
/// </summary>
/// <param name="authManager"></param>
/// <param name="userManager"></param>
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
}
/// <summary>
/// Gets a bearer token
/// </summary>
/// <param name="loginViewModel"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost("login")]
public async Task<ActionResult<IdentityResult>> GetTokenAsync(LoginViewModel loginViewModel)
[HttpPost("token")]
public async Task<ActionResult<IdentityResult>> 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();
}
/// <summary>
/// Check if user is logged in
/// </summary>
/// <returns></returns>
[HttpGet("isLoggedIn")]
public ActionResult<bool> IsLoggedIn()
{
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
if (identity.IsAuthenticated)
{
return Ok(true);
}
return Ok(false);
}
/// <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.
@ -75,23 +116,26 @@ public class AuthController : Controller
/// <param name="loginDto"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost("logincookie")]
public async Task<ActionResult<IdentityResult>> DoLoginAsync(LoginViewModel loginDto)
[HttpPost("login")]
public async Task<ActionResult> 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);
}
/// <summary>
@ -101,10 +145,9 @@ public class AuthController : Controller
/// <returns></returns>
[AllowAnonymous]
[HttpPost("logout")]
public async Task<ActionResult<IdentityResult>> DoLogoutAsync(LoginViewModel loginDto)
public async Task DoLogoutAsync()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return Json(true);
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}

View File

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

View File

@ -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
{

View File

@ -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;

View File

@ -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
{

View File

@ -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/"]

View File

@ -0,0 +1,9 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace BlueWest.WebApi;
public class GreeterService
{
}

View File

@ -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;
/// <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,13 @@
using Grpc.Core.Interceptors;
namespace BlueWest.WebApi.Interceptors
{
/// <summary>
/// Server Logger Interceptor
/// </summary>
public class ServerLoggerInterceptor : Interceptor
{
}
}

View File

@ -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);
}
}

View File

@ -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;
}
/// <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

@ -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;
/// <summary>
@ -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<ServerLoggerInterceptor>();
});
services.AddSingleton<ServerLoggerInterceptor>();
/*
services.AddSingleton<IFileProvider>(
new PhysicalFileProvider(
@ -119,10 +144,12 @@ namespace BlueWest.WebApi
services
.AddSingleton<EventManager>();
services.AddAuthServerServices(MyAllowSpecificOrigins, _configuration, _environment);
services.AddAuthServerServices( _configuration, _environment);
services.AddScoped<ExchangeInterface>();
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();

View File

@ -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<CountryDbContext>(options => options.GetMySqlSettings(configuration, environment))
.AddDbContextPool<FinanceDbContext>(options => options.GetMySqlSettings(configuration, environment))
.AddDbContextPool<CompanyDbContext>(options => options.GetMySqlSettings(configuration, environment))
.AddDbContextPool<ApplicationUserDbContext>(options => options.GetMySqlSettings(configuration, environment));
.AddDbContextPool<ApplicationUserDbContext>(options =>
options.GetMySqlSettings(configuration, environment))
.AddDbContextPool<SessionDbContext>(options => options.UseInMemoryDatabase("_s"));
}
/// <summary>
@ -100,27 +103,24 @@ 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<ApplicationUserDbContext>(options => options.UseSqlite(sqliteConString))
.AddDbContextPool<SessionDbContext>(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<IJwtTokenHandler, JwtTokenHandler>();
services.AddScoped<IJwtFactory, JwtFactory>();
services
.AddScoped< UserRepository>()
services.AddScoped<IJwtTokenHandler, JwtTokenHandler>()
.AddScoped<IJwtFactory, JwtFactory>()
.AddSingleton<IndexCreationDevice>()
.AddScoped<ISessionManager, SessionManager>()
.AddScoped<UserRepository>()
.AddScoped<IUserManager, ApplicationUserManager>()
.AddScoped<IAuthManager, AuthManager>()
.AddScoped<IHasher, Hasher>();
services
.AddIdentityCore<ApplicationUser>(opt => { opt.User.RequireUniqueEmail = true; })
.AddUserManager<ApplicationUserManager>()
.AddUserStore<UserRepository>();
// 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<ApplicationUser>(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<ApplicationUserManager>()
.AddUserStore<UserRepository>();
identityBuilder = new IdentityBuilder(identityBuilder.UserType, typeof(ApplicationRole), identityBuilder.Services);
identityBuilder

View File

@ -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<ApplicationUser>, IUserManag
{
private readonly IHasher _hasher;
private readonly UserRepository _usersRepo;
public ApplicationUserManager(UserRepository store, IOptions<IdentityOptions> optionsAccessor,
IHasher passwordHasher, IEnumerable<IUserValidator<ApplicationUser>> userValidators,
IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators, ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<ApplicationUser>> logger) : base(store,
optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services,
logger)
public ApplicationUserManager(
UserRepository store,
IOptions<IdentityOptions> optionsAccessor,
IHasher passwordHasher,
IEnumerable<IUserValidator<ApplicationUser>> userValidators,
IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<UserManager<ApplicationUser>> logger) : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
_hasher = passwordHasher;
_usersRepo = store;
@ -49,6 +54,7 @@ internal class ApplicationUserManager : UserManager<ApplicationUser>, IUserManag
return success;
}
protected override async Task<PasswordVerificationResult> VerifyPasswordAsync(IUserPasswordStore<ApplicationUser> store, ApplicationUser user, string password)
{
string existingHash;
@ -89,7 +95,11 @@ internal class ApplicationUserManager : UserManager<ApplicationUser>, IUserManag
Logger.LogWarning(2, "Change password failed for user {userId}.", await GetUserIdAsync(user));
return IdentityResult.Failed(ErrorDescriber.PasswordMismatch());
}
public override Task<IdentityResult> SetAuthenticationTokenAsync(ApplicationUser user, string loginProvider, string tokenName, string tokenValue)
{
return base.SetAuthenticationTokenAsync(user, loginProvider, tokenName, tokenValue);
}
private IUserPasswordStore<ApplicationUser> GetPasswordStore()
{

View File

@ -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
/// <param name="hasher"></param>
/// <param name="usersRepo"></param>
/// <param name="jwtFactory"></param>
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);
}
/// <inheritdoc />
public async Task<AccessToken> 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);
}
/// <inheritdoc />
public async Task<bool> VerifyLoginAsync(string email, string password)
public async Task<bool> 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;
}

View File

@ -20,7 +20,7 @@ namespace BlueWest.Cryptography
/// <param name="text"></param>
/// <param name="algorithm"></param>
/// <returns></returns>
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);

View File

@ -0,0 +1,12 @@
namespace BlueWest.Cryptography
{
public interface ISessionHasher
{
/// <summary>
/// Generates a token for the current session
/// </summary>
void GenerateSessionToken();
}
}

View File

@ -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),

View File

@ -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
/// <param name="email"></param>
/// <param name="password"></param>
/// <returns></returns>
Task<bool> VerifyLoginAsync(string email, string password);
Task<bool> VerifyLoginByEmailAsync(string email, string password);
/// <summary>
/// GetToken
/// </summary>
/// <param name="loginViewModel"></param>
/// <param name="loginRequest"></param>
/// <returns></returns>
Task<AccessToken> GetToken(LoginViewModel loginViewModel);
Task<(bool, SessionTokenUnique, AccessToken)> GetToken(LoginRequest loginRequest);
/// <summary>
/// Does Login
/// </summary>
/// <param name="loginRequest"></param>
/// <returns></returns>
Task<(bool, ClaimsIdentity, SessionTokenUnique)> DoLogin(LoginRequest loginRequest);
}

View File

@ -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
/// <param name="email"></param>
/// <returns></returns>
Task<ApplicationUser> FindByEmailAsync(string email);
}
}

View File

@ -4,9 +4,9 @@ namespace BlueWest.WebApi.Context.Users
{
// from: https://github.com/dotnet/aspnetcore/tree/main/src/Identity/samples/IdentitySample.Mvc/Models/AccountViewModels
/// <summary>
/// Login View Model
/// Login Request adata
/// </summary>
public class LoginViewModel
public class LoginRequest
{
/// <summary>
/// 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; }
/// <summary>
/// RememberMe
/// </summary>
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
}

View File

@ -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; }
/// <summary>
/// ConfirmPassword
/// </summary>
[DataType(DataType.PhoneNumber)]
[Display(Name = "Phone Number")]
public string PhoneNumber { get; set; }
/// <summary>
/// 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;
}
}

View File

@ -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;
/// <summary>
/// Role storage management
/// </summary>
/*public class RoleStore : RoleStore<ApplicationRole>
{
}*/

View File

@ -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"
}
}

View File

@ -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

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
</Project>

View File

@ -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"]

View File

@ -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> task)
{
Console.WriteLine("Hello");
await task.Invoke();
}
async Task Task2()
{
Console.WriteLine("World");
}
var f = Task2;
await TaskReceiver(f);

View File

@ -1,3 +1,4 @@
namespace BlueWest.WebApi.Context.Users
{
public class AccessToken

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,17 @@
namespace BlueWest.Data.Application;
public enum ClientPlatformType
{
MicrosoftWindows,
WindowsPhone,
IPhone,
Samsung,
Unknown,
Wii,
Linux,
Blackberry,
Playstation,
Ipad,
IPod,
Curl
}

View File

@ -0,0 +1,12 @@
namespace BlueWest.Data.Application
{
public enum OperatingSystemType
{
MacOS,
iOS,
Windows,
Android,
Linux
}
}

View File

@ -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
/// </summary>
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[PersonalData]
public new string Id { get; set; }
public List<User> 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.
/// </summary>
public override int AccessFailedCount { get; set; }
public List<SessionToken> SessionToken { get; set; }
public List<SessionData> SessionDatas { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Identity;
namespace BlueWest.WebApi.Context.Users;
/// <inheritdoc />
public class ApplicationUserToken : IdentityUserToken<string>
{
public string Id { get; set; }
}

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\include\BlueWest.MapTo\src\BlueWest.MapTo\BlueWest.MapTo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\include\Math-Expression-Evaluator\SimpleExpressionEvaluator\SimpleExpressionEvaluator.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="obj\rider.project.restore.info" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="6.0.8" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="6.0.8" />
<PackageReference Include="Redis.OM" Version="0.2.2" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.10.0" />
</ItemGroup>
<Import Project="..\include\BlueWest.MapTo\src\BlueWest.MapTo\MapTo.props" />
</Project>

View File

@ -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);
}
}

View File

@ -0,0 +1,6 @@
namespace BlueWest.WebApi.Context.Users;
internal interface ITokenFactory
{
string GenerateToken(int size= 32);
}

View File

@ -0,0 +1,54 @@
using System;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Tokens;
namespace BlueWest.WebApi.Context.Users;
internal class JwtIssuerOptions
{
/// <summary>
/// 4.1.1. "iss" (Issuer) Claim - The "iss" (issuer) claim identifies the principal that issued the JWT.
/// </summary>
public string Issuer { get; set; }
/// <summary>
/// 4.1.2. "sub" (Subject) Claim - The "sub" (subject) claim identifies the principal that is the subject of the JWT.
/// </summary>
public string Subject { get; set; }
/// <summary>
/// 4.1.3. "aud" (Audience) Claim - The "aud" (audience) claim identifies the recipients that the JWT is intended for.
/// </summary>
public string Audience { get; set; }
/// <summary>
/// 4.1.4. "exp" (Expiration Time) Claim - The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing.
/// </summary>
public DateTime Expiration => IssuedAt.Add(ValidFor);
/// <summary>
/// 4.1.5. "nbf" (Not Before) Claim - The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing.
/// </summary>
public DateTime NotBefore => DateTime.UtcNow;
/// <summary>
/// 4.1.6. "iat" (Issued At) Claim - The "iat" (issued at) claim identifies the time at which the JWT was issued.
/// </summary>
public DateTime IssuedAt => DateTime.UtcNow;
/// <summary>
/// Set the timespan the token will be valid for (default is 120 min)
/// </summary>
public TimeSpan ValidFor { get; set; } = TimeSpan.FromMinutes(120);
/// <summary>
/// "jti" (JWT ID) Claim (default ID is a GUID)
/// </summary>
public Func<Task<string>> JtiGenerator =>
() => Task.FromResult(Guid.NewGuid().ToString());
/// <summary>
/// The signing key to use when generating tokens.
/// </summary>
public SigningCredentials SigningCredentials { get; set; }
}

View File

@ -0,0 +1,53 @@
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
namespace BlueWest.WebApi.Context.Users;
public class JwtTokenHandler : IJwtTokenHandler
{
private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler;
/// <summary>
/// JwtTokenHandler
/// </summary>
public JwtTokenHandler()
{
_jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
}
/// <summary>
/// Write token
/// </summary>
/// <param name="jwt"></param>
/// <returns></returns>
public string WriteToken(JwtSecurityToken jwt)
{
return _jwtSecurityTokenHandler.WriteToken(jwt);
}
/// <summary>
/// Validate Token
/// </summary>
/// <param name="token"></param>
/// <param name="tokenValidationParameters"></param>
/// <returns></returns>
/// <exception cref="SecurityTokenException"></exception>
public ClaimsPrincipal ValidateToken(string token, TokenValidationParameters tokenValidationParameters)
{
try
{
var principal = _jwtSecurityTokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken);
if (!(securityToken is JwtSecurityToken jwtSecurityToken) || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
throw new SecurityTokenException("Invalid token");
return principal;
}
catch (Exception e)
{
return null;
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -1,6 +0,0 @@
using Microsoft.AspNetCore.Identity;
namespace BlueWest.WebApi.Context.Users;
/// <inheritdoc />
public class ApplicationUserToken : IdentityUserToken<string> { }

View File

@ -17,11 +17,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="6.0.7" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="6.0.7" />
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="6.0.8" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="6.0.8" />
</ItemGroup>
<ItemGroup>
<Folder Include="Application" />
<Folder Include="Capital" />
</ItemGroup>
<Import Project="..\include\BlueWest.MapTo\src\BlueWest.MapTo\MapTo.props" />

@ -1 +1 @@
Subproject commit 2afc7cc1eb60369db3d2db021d514647baba93dc
Subproject commit b67ba4a1a6bb8a58ee44b34dbadbd6a0e99c5c87

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,8 @@
namespace BlueWest.Posts
{
public class PostType
{
}
}

View File

@ -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

View File

@ -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<EventListenerBase> list = _subscribersList[type];
for (int i=0; i<list.Count; i++)
for (int i=0; i<list.Count; i++)
{
var eventListener = list[i];
var casted = eventListener as EventListener<TEvent>;

View File

@ -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);

View File

@ -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
redis:
image: "redis:alpine"
command: redis-server --requirepass Sup3rSecurePass0rd
ports:
- "6379:6379"
environment:
- REDIS_REPLICATION_MODE=master