Add Users code
This commit is contained in:
parent
f46d20c1ea
commit
2a404c3560
|
@ -12,8 +12,10 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="6.0.2" />
|
||||
<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">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
@ -41,6 +43,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BlueWest.Data.Capital\BlueWest.Data.Capital.csproj" />
|
||||
<ProjectReference Include="..\BlueWest.Data.Geography\BlueWest.Data.Geography.csproj" />
|
||||
<ProjectReference Include="..\BlueWest\BlueWest.csproj" />
|
||||
<ProjectReference Include="..\include\BlueWest.EfMethods\src\BlueWest.EfMethods\BlueWest.EfMethods.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
|
||||
|
|
|
@ -4,6 +4,9 @@ using Microsoft.EntityFrameworkCore;
|
|||
|
||||
namespace BlueWest.WebApi.Context.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Code first model builder
|
||||
/// </summary>
|
||||
public static class ModelBuilderCountryDbContextExtensions
|
||||
{ /// <summary>
|
||||
/// Setup the database model
|
||||
|
|
|
@ -16,7 +16,7 @@ public static (bool, {returnTypeFullName}[]) Get{propertyName}(this {contextFull
|
|||
|
||||
var query = dbContext
|
||||
.{propertyName}
|
||||
.Select(x => new {returnTypeFullName}(x));
|
||||
.Select(x => new {returnTypeFullName}(x))
|
||||
.Skip(skip)
|
||||
.Take(take);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using BlueWest.Data;
|
||||
using BlueWest.WebApi.Context.Users;
|
||||
using BlueWest.WebApi.EF.Model;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
@ -16,6 +17,7 @@ namespace BlueWest.WebApi.EF
|
|||
/// </summary>
|
||||
public DbSet<User> Users { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// App configuration.
|
||||
/// </summary>
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using AutoMapper;
|
||||
using BlueWest.WebApi.Context.Users;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BlueWest.WebApi.Controllers;
|
||||
|
||||
[Route("api/[controller]")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
|
||||
[ApiController]
|
||||
public class AuthController : Controller
|
||||
{
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IAuthManager _authManager;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
public AuthController( IMapper mapper, IAuthManager authManager, IUserManager userManager)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_authManager = authManager;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("register")]
|
||||
public async Task<ActionResult<IdentityResult>> SignupUserAsync(RegisterViewModel registerViewModel)
|
||||
{
|
||||
return await _authManager.CreateUserAsync(registerViewModel);
|
||||
}
|
||||
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("login")]
|
||||
public async Task<ActionResult<IdentityResult>> GetTokenAsync(LoginViewModel loginViewModel)
|
||||
{
|
||||
var loginResultSucceded = await _authManager.GetToken(loginViewModel);
|
||||
|
||||
if (loginResultSucceded != null)
|
||||
{
|
||||
return Ok(_mapper.Map<AccessToken>(loginResultSucceded));
|
||||
|
||||
}
|
||||
return Problem();
|
||||
}
|
||||
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("login2")]
|
||||
public async Task<ActionResult<IdentityResult>> DoLoginAsync(LoginViewModel loginDto)
|
||||
{
|
||||
var user = await _userManager.FindByEmailAsync(loginDto.Email);
|
||||
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
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 Json(true);
|
||||
}
|
||||
}
|
||||
|
||||
return Json(false);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("logout")]
|
||||
public async Task<ActionResult<IdentityResult>> DoLogoutAsync(LoginViewModel loginDto)
|
||||
{
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
|
||||
return Json(true);
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("test")]
|
||||
public ActionResult TestRequest()
|
||||
{
|
||||
return Ok(new {Message = "Test"});
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -77,9 +77,9 @@ namespace BlueWest.WebApi.Controllers
|
|||
#region GetCurrencyWithCode
|
||||
|
||||
/// <summary>
|
||||
/// Gets a currency by the currency number (id)
|
||||
/// Gets a currency by code.
|
||||
/// </summary>
|
||||
/// <param name="currencyId">The id of the currency to get</param>
|
||||
/// <param name="currencyCode">The currency Code </param>
|
||||
/// <returns></returns>
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
|
@ -179,7 +179,6 @@ namespace BlueWest.WebApi.Controllers
|
|||
/// <summary>
|
||||
/// Add Currency to the table of currencies
|
||||
/// </summary>
|
||||
/// <param name="currencyId"></param>
|
||||
/// <param name="countryToCreate"></param>
|
||||
/// <returns></returns>
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
|
|
|
@ -7,8 +7,12 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Text.Json.Serialization;
|
||||
using BlueWest.Tools;
|
||||
using BlueWest.WebApi.Context.Users;
|
||||
using BlueWest.WebApi.Interfaces;
|
||||
using BlueWest.WebApi.Tools;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace BlueWest.WebApi
|
||||
|
@ -70,6 +74,38 @@ namespace BlueWest.WebApi
|
|||
});
|
||||
|
||||
|
||||
services.Configure<IdentityOptions>(options =>
|
||||
{
|
||||
// Password settings.
|
||||
options.Password.RequireDigit = true;
|
||||
options.Password.RequireLowercase = true;
|
||||
options.Password.RequireNonAlphanumeric = true;
|
||||
options.Password.RequireUppercase = true;
|
||||
options.Password.RequiredLength = 6;
|
||||
options.Password.RequiredUniqueChars = 1;
|
||||
|
||||
// Lockout settings.
|
||||
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
|
||||
options.Lockout.MaxFailedAccessAttempts = 5;
|
||||
options.Lockout.AllowedForNewUsers = true;
|
||||
|
||||
// User settings.
|
||||
options.User.AllowedUserNameCharacters =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
|
||||
options.User.RequireUniqueEmail = false;
|
||||
});
|
||||
|
||||
services.AddScoped<SignInManager>();
|
||||
|
||||
services.AddScoped<RoleManager>();
|
||||
|
||||
|
||||
services.AddSingleton<IFileProvider>(
|
||||
new PhysicalFileProvider(
|
||||
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/ImageFiles")
|
||||
)
|
||||
);
|
||||
|
||||
IConfigurationRoot configuration = new ConfigurationBuilder()
|
||||
.AddJsonFile("config.json")
|
||||
.Build();
|
||||
|
@ -118,11 +154,12 @@ namespace BlueWest.WebApi
|
|||
c.RoutePrefix = "swagger";
|
||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "BlueWest.Api v1");
|
||||
});
|
||||
|
||||
app.UseStaticFiles();
|
||||
//app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
app.UseCors(MyAllowSpecificOrigins);
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
|
|
|
@ -1,12 +1,24 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BlueWest.Cryptography;
|
||||
using BlueWest.Data;
|
||||
using BlueWest.WebApi.Context.Users;
|
||||
using BlueWest.WebApi.EF;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace BlueWest.WebApi
|
||||
{
|
||||
|
@ -89,5 +101,139 @@ namespace BlueWest.WebApi
|
|||
.AddDbContextPool<CompanyDbContext>(options => options.UseSqlite(sqliteConString));
|
||||
}
|
||||
|
||||
public static void AddAuthServerServices(this IServiceCollection services, string origins, IConfiguration _configuration)
|
||||
{
|
||||
services.AddScoped<IJwtTokenHandler, JwtTokenHandler>();
|
||||
services.AddScoped<IJwtFactory, JwtFactory>();
|
||||
|
||||
// User management
|
||||
services
|
||||
.AddIdentityCore<ApplicationUser>(opt => { opt.User.RequireUniqueEmail = true; })
|
||||
.AddEntityFrameworkStores<UserDbContext>()
|
||||
.AddUserManager<UserManager>()
|
||||
.AddUserStore<UserRepository>();
|
||||
// Database Context and Swagger
|
||||
|
||||
services.TryAddSingleton<ISystemClock, SystemClock>();
|
||||
// Registering 'services' and Authentication, Cookies, JWT
|
||||
services
|
||||
.AddScoped<IUsersRepo, UserRepository>()
|
||||
.AddScoped<IUserManager, UserManager>() // So it gets successfully registered in UserManager
|
||||
.AddScoped<IAuthManager, AuthManager>()
|
||||
.AddScoped<IHasher, Hasher>();
|
||||
|
||||
|
||||
// Register the ConfigurationBuilder instance of AuthSettings
|
||||
var authSettings = _configuration.GetSection(nameof(AuthSettings));
|
||||
services.Configure<AuthSettings>(authSettings);
|
||||
var signingKey = new SymmetricSecurityKey
|
||||
(Encoding.ASCII.GetBytes(authSettings[nameof(AuthSettings.SecretKey)]));
|
||||
|
||||
// jwt wire up
|
||||
// Get options from app settings
|
||||
var jwtAppSettingOptions = _configuration
|
||||
.GetSection(nameof(JwtIssuerOptions));
|
||||
|
||||
// Configure JwtIssuerOptions
|
||||
services.Configure<JwtIssuerOptions>(options =>
|
||||
{
|
||||
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
|
||||
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
|
||||
options.SigningCredentials = new SigningCredentials
|
||||
(signingKey, SecurityAlgorithms.HmacSha256);
|
||||
});
|
||||
|
||||
var tokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
|
||||
|
||||
ValidateAudience = true,
|
||||
ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],
|
||||
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = signingKey,
|
||||
|
||||
RequireExpirationTime = false,
|
||||
ValidateLifetime = true,
|
||||
ClockSkew = TimeSpan.Zero
|
||||
};
|
||||
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.LoginPath = "/api/auth/login2";
|
||||
options.LogoutPath = "/api/auth/logout";
|
||||
})
|
||||
.AddJwtBearer(configureOptions =>
|
||||
{
|
||||
configureOptions.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
|
||||
configureOptions.TokenValidationParameters = tokenValidationParameters;
|
||||
configureOptions.SaveToken = true;
|
||||
|
||||
configureOptions.Events = new JwtBearerEvents
|
||||
{
|
||||
OnAuthenticationFailed = context =>
|
||||
{
|
||||
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
|
||||
{
|
||||
context.Response.Headers.Add("Token-Expired", "true");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// api user claim policy
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("ApiUser",
|
||||
policy => policy.RequireClaim(Constants.JwtClaimIdentifiers.Rol,
|
||||
Constants.JwtClaims.ApiAccess));
|
||||
});
|
||||
|
||||
// add identity
|
||||
var identityBuilder = services.AddIdentityCore<User>(o =>
|
||||
{
|
||||
// configure identity options
|
||||
o.Password.RequireDigit = false;
|
||||
o.Password.RequireLowercase = false;
|
||||
o.Password.RequireUppercase = false;
|
||||
o.Password.RequireNonAlphanumeric = false;
|
||||
o.Password.RequiredLength = 6;
|
||||
});
|
||||
|
||||
identityBuilder = new IdentityBuilder(identityBuilder.UserType, typeof(IdentityRole), identityBuilder.Services);
|
||||
identityBuilder.AddEntityFrameworkStores<UserDbContext>().AddDefaultTokenProviders();
|
||||
}
|
||||
public static void ConfigureApiWithUsers(this IApplicationBuilder app, IWebHostEnvironment env, string origins)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseSwagger()
|
||||
.UseSwaggerUI(config => { config.SwaggerEndpoint("/swagger/v1/swagger.json", "Commands And Snippets API"); })
|
||||
.UseRouting()
|
||||
.UseAuthentication()
|
||||
.UseAuthorization()
|
||||
.UseCors(origins)
|
||||
.UseEndpoints(endpoints => endpoints.MapControllers());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <summary>
|
||||
/// Application User in the Identity System.
|
||||
/// </summary>
|
||||
public class ApplicationUser : IdentityUser<string>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the primary key for this user.
|
||||
/// </summary>
|
||||
[PersonalData]
|
||||
public new Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user name for this user.
|
||||
/// </summary>
|
||||
[ProtectedPersonalData]
|
||||
public override string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the normalized user name for this user.
|
||||
/// </summary>
|
||||
public override string NormalizedUserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the email address for this user.
|
||||
/// </summary>
|
||||
[ProtectedPersonalData]
|
||||
public override string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the normalized email address for this user.
|
||||
/// </summary>
|
||||
public override string NormalizedEmail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag indicating if a user has confirmed their email address.
|
||||
/// </summary>
|
||||
/// <value>True if the email address has been confirmed, otherwise false.</value>
|
||||
[PersonalData]
|
||||
public override bool EmailConfirmed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a salted and hashed representation of the password for this user.
|
||||
/// </summary>
|
||||
public override string PasswordHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A random value that must change whenever a users credentials change (password changed, login removed)
|
||||
/// </summary>
|
||||
public override string SecurityStamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A random value that must change whenever a user is persisted to the store
|
||||
/// </summary>
|
||||
public override string ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a telephone number for the user.
|
||||
/// </summary>
|
||||
[ProtectedPersonalData]
|
||||
public override string PhoneNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag indicating if a user has confirmed their telephone address.
|
||||
/// </summary>
|
||||
/// <value>True if the telephone number has been confirmed, otherwise false.</value>
|
||||
[PersonalData]
|
||||
public override bool PhoneNumberConfirmed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag indicating if two factor authentication is enabled for this user.
|
||||
/// </summary>
|
||||
/// <value>True if 2fa is enabled, otherwise false.</value>
|
||||
[PersonalData]
|
||||
public override bool TwoFactorEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date and time, in UTC, when any user lockout ends.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A value in the past means the user is not locked out.
|
||||
/// </remarks>
|
||||
public override DateTimeOffset? LockoutEnd { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag indicating if the user could be locked out.
|
||||
/// </summary>
|
||||
/// <value>True if the user could be locked out, otherwise false.</value>
|
||||
public override bool LockoutEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of failed login attempts for the current user.
|
||||
/// </summary>
|
||||
public override int AccessFailedCount { get; set; }
|
||||
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BlueWest.Data;
|
||||
using BlueWest.WebApi.EF;
|
||||
using BlueWest.WebApi.EF.Model;
|
||||
using Duende.IdentityServer.EntityFramework.Entities;
|
||||
using Duende.IdentityServer.EntityFramework.Interfaces;
|
||||
using Duende.IdentityServer.Stores.Serialization;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <summary>
|
||||
/// Application User Db Context
|
||||
/// </summary>
|
||||
public class ApplicationUserDbContext : IdentityDbContext<
|
||||
ApplicationUser,
|
||||
ApplicationRole,
|
||||
string,
|
||||
ApplicationUserClaim,
|
||||
ApplicationUserRole,
|
||||
ApplicationUserLogin,
|
||||
ApplicationRoleClaim,
|
||||
ApplicationUserToken>, IPersistedGrantDbContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="DbSet{TEntity}"/> of User roles.
|
||||
/// </summary>
|
||||
public override DbSet<ApplicationUserRole> UserRoles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="DbSet{TEntity}"/> of roles.
|
||||
/// </summary>
|
||||
public override DbSet<ApplicationRole> Roles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="DbSet{TEntity}"/> of role claims.
|
||||
/// </summary>
|
||||
public override DbSet<ApplicationRoleClaim> RoleClaims { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Configures the schema needed for the identity framework.
|
||||
/// </summary>
|
||||
/// <param name="builder">
|
||||
/// The builder being used to construct the model for this context.
|
||||
/// </param>
|
||||
|
||||
/// <summary>
|
||||
/// Database for the context of database users
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
public ApplicationUserDbContext(DbContextOptions<UserDbContext> options) : base(options)
|
||||
{
|
||||
Database.EnsureCreated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the schema needed for the identity framework.
|
||||
/// </summary>
|
||||
/// <param name="builder">
|
||||
/// The builder being used to construct the model for this context.
|
||||
/// </param>
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
builder.ConfigureCurrentDbModel();
|
||||
base.OnModelCreating(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");
|
||||
});
|
||||
}
|
||||
|
||||
public Task<int> SaveChangesAsync()
|
||||
{
|
||||
return SaveChangesAsync();
|
||||
}
|
||||
|
||||
public DbSet<PersistedGrant> PersistedGrants { get; set; }
|
||||
public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }
|
||||
public DbSet<Key> Keys { get; set; }
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
namespace BlueWest.WebApi.Context.Users
|
||||
{
|
||||
public class AccessToken
|
||||
{
|
||||
public string Token { get; }
|
||||
public int ExpiresIn { get; }
|
||||
|
||||
public AccessToken(string token, int expiresIn)
|
||||
{
|
||||
Token = token;
|
||||
ExpiresIn = expiresIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AutoMapper;
|
||||
using BlueWest.Cryptography;
|
||||
using BlueWest.Data;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public class AuthManager : IAuthManager
|
||||
{
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IUsersRepo _usersRepo;
|
||||
private readonly IHasher _hasher;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IJwtFactory _jwtFactory;
|
||||
|
||||
public AuthManager(IUserManager userManager, IHasher hasher, IMapper mapper
|
||||
, IUsersRepo usersRepo, IJwtFactory jwtFactory)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_hasher = hasher;
|
||||
_mapper = mapper;
|
||||
_usersRepo = usersRepo;
|
||||
_jwtFactory = jwtFactory;
|
||||
}
|
||||
|
||||
public async Task<AccessToken> GetToken(LoginViewModel loginViewModel)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(loginViewModel.Email) && !string.IsNullOrEmpty(loginViewModel.Password))
|
||||
{
|
||||
var user = await _userManager.FindByEmailAsync(loginViewModel.Email);
|
||||
if (user != null)
|
||||
{
|
||||
if (await VerifyLoginAsync(loginViewModel.Email,loginViewModel.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);
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<bool> VerifyLoginAsync(string email, string password)
|
||||
{
|
||||
var user = await _userManager.FindByEmailAsync(email);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return false; // return error user doesn't exist
|
||||
}
|
||||
|
||||
return await _userManager.CheckPasswordAsync(user, password);
|
||||
|
||||
// return await GenerateAuthenticationResultForUserAsync(user);
|
||||
}
|
||||
|
||||
private RegisterViewModel FromSignupToUser(RegisterViewModel signupDto)
|
||||
{
|
||||
var pwd = signupDto.Password;
|
||||
var hash = _hasher.CreateHash(pwd, BaseCryptoItem.HashAlgorithm.SHA3_512);
|
||||
signupDto.Password = hash;
|
||||
signupDto.ConfirmPassword = hash;
|
||||
|
||||
return signupDto;
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> CreateUserAsync(RegisterViewModel userSignupDto)
|
||||
{
|
||||
|
||||
return await _userManager.CreateAsync(userSignupDto.ToUser());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public class AuthSettings
|
||||
{
|
||||
public string SecretKey { get; set; }
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System.Threading.Tasks;
|
||||
using BlueWest.Data;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public interface IAuthManager
|
||||
{
|
||||
Task<IdentityResult> CreateUserAsync(RegisterViewModel registerViewModel);
|
||||
|
||||
Task<bool> VerifyLoginAsync(string email, string password);
|
||||
|
||||
Task<AccessToken> GetToken(LoginViewModel loginViewModel);
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <summary>
|
||||
/// SignInManager
|
||||
/// </summary>
|
||||
internal class SignInManager : SignInManager<ApplicationUser>
|
||||
{
|
||||
public SignInManager(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IHttpContextAccessor contextAccessor,
|
||||
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory,
|
||||
IOptions<IdentityOptions> optionsAccessor,
|
||||
ILogger<SignInManager<ApplicationUser>> logger,
|
||||
IAuthenticationSchemeProvider schemes,
|
||||
IUserConfirmation<ApplicationUser> confirmation) :
|
||||
base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override async Task<ClaimsPrincipal> CreateUserPrincipalAsync(ApplicationUser user) => await ClaimsFactory.CreateAsync(user);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public const string AdminRoleName = "Admin";
|
||||
public const string UserRoleName = "User";
|
||||
public const string ExpectatorRoleName = "Expectator";
|
||||
public static class JwtClaimIdentifiers
|
||||
{
|
||||
public const string Rol = "rol", Id = "id";
|
||||
}
|
||||
|
||||
public static class JwtClaims
|
||||
{
|
||||
public const string ApiAccess = "api_access";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace BlueWest.Cryptography
|
||||
{
|
||||
public abstract class BaseCryptoItem
|
||||
{
|
||||
public enum HashAlgorithm
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
SHA2_512 = 1,
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
PBKDF2_SHA512 = 2,
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
SHA3_512 = 3
|
||||
}
|
||||
|
||||
protected byte[] HexStringToByteArray(string stringInHexFormat)
|
||||
{
|
||||
var converted = Enumerable.Range(0, stringInHexFormat.Length)
|
||||
.Where(x => x % 2 == 0)
|
||||
.Select(x => Convert.ToByte(stringInHexFormat.Substring(x, 2), 16))
|
||||
.ToArray();
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
protected string ByteArrayToString(byte[] bytes)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
sb.Append(bytes[i].ToString("X2"));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random string
|
||||
/// </summary>
|
||||
/// <param name="length">The length of the random string</param>
|
||||
/// <returns></returns>
|
||||
protected string CreateRandomString(int length)
|
||||
{
|
||||
var rng = RandomNumberGenerator.Create();
|
||||
var buffer = new byte[length / 2];
|
||||
rng.GetBytes(buffer);
|
||||
var randomString = BitConverter.ToString(buffer).Replace("-", "");
|
||||
return randomString;
|
||||
}
|
||||
|
||||
// TODO Refactor me
|
||||
/// <summary>
|
||||
/// Get Cryptographic algorithm
|
||||
/// </summary>
|
||||
/// <param name="cipherText"></param>
|
||||
/// <param name="algorithm"></param>
|
||||
/// <param name="keyIndex"></param>
|
||||
/// <param name="trimmedCipherText"></param>
|
||||
protected void GetAlgorithm(string cipherText, out int? algorithm, out int? keyIndex,
|
||||
out string trimmedCipherText)
|
||||
{
|
||||
algorithm = null;
|
||||
keyIndex = null;
|
||||
trimmedCipherText = cipherText;
|
||||
|
||||
if (cipherText.Length <= 5 || cipherText[0] != '[') return;
|
||||
|
||||
var cipherInfo = cipherText.Substring(1, cipherText.IndexOf(']') - 1).Split(",");
|
||||
|
||||
if (int.TryParse(cipherInfo[0], out var foundAlgorithm))
|
||||
{
|
||||
algorithm = foundAlgorithm;
|
||||
}
|
||||
|
||||
if (cipherInfo.Length == 2 && int.TryParse(cipherInfo[1], out var foundKeyIndex))
|
||||
keyIndex = foundKeyIndex;
|
||||
trimmedCipherText = cipherText.Substring(cipherText.IndexOf(']') + 1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
|
||||
using System;
|
||||
using BlueWest.Data;
|
||||
using BlueWest.WebApi.Context.Users;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
|
||||
namespace BlueWest.Cryptography
|
||||
{
|
||||
|
||||
public class Hasher : BaseCryptoItem, IHasher
|
||||
{
|
||||
private const int SaltLength = 64;
|
||||
|
||||
public string CreateHash(string text, BaseCryptoItem.HashAlgorithm algorithm)
|
||||
{
|
||||
var salt = CreateRandomString(SaltLength);
|
||||
return CreateHash(text, salt, algorithm, true);
|
||||
}
|
||||
|
||||
public string CreateHash(string text, string saltName, BaseCryptoItem.HashAlgorithm algorithm)
|
||||
{
|
||||
var salt = "TODOFIXME";
|
||||
return CreateHash(text, salt, algorithm, false);
|
||||
}
|
||||
|
||||
private string CreateHash(string text, string salt, HashAlgorithm algorithm, bool storeSalt)
|
||||
{
|
||||
string hash;
|
||||
|
||||
switch (algorithm)
|
||||
{
|
||||
case HashAlgorithm.SHA2_512:
|
||||
var sha2 = new SHA2_512();
|
||||
hash = sha2.Hash(text, salt, storeSalt);
|
||||
break;
|
||||
case HashAlgorithm.SHA3_512:
|
||||
var sha3 = new SHA2_512();
|
||||
hash = sha3.Hash(text, salt, storeSalt);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public bool MatchesHash(string text, string hash)
|
||||
{
|
||||
string salt = "";
|
||||
|
||||
GetAlgorithm(hash, out var algoAsInt, out _, out _, out salt);
|
||||
|
||||
if (!algoAsInt.HasValue) return false;
|
||||
|
||||
var hashAlgorithm = (HashAlgorithm) algoAsInt.Value;
|
||||
var hashed = CreateHash(text, salt, hashAlgorithm, true);
|
||||
return hashed == hash;
|
||||
}
|
||||
|
||||
public string HashPassword(ApplicationUser ApplicationUser, string password)
|
||||
{
|
||||
return CreateHash(password, HashAlgorithm.SHA3_512);
|
||||
}
|
||||
|
||||
public PasswordVerificationResult VerifyHashedPassword(ApplicationUser ApplicationUser, string hashedPassword,
|
||||
string providedPassword)
|
||||
{
|
||||
var match = MatchesHash(providedPassword, hashedPassword);
|
||||
return match ? PasswordVerificationResult.Success : PasswordVerificationResult.Failed;
|
||||
}
|
||||
|
||||
private void GetAlgorithm(string cipherText, out int? algorithm, out int? keyIndex,
|
||||
out string trimmedCipherText, out string salt)
|
||||
{
|
||||
GetAlgorithm(cipherText, out algorithm, out keyIndex, out trimmedCipherText);
|
||||
if (algorithm.HasValue && trimmedCipherText.Length > SaltLength)
|
||||
{
|
||||
salt = trimmedCipherText.Substring(0, SaltLength);
|
||||
trimmedCipherText = trimmedCipherText.Substring(SaltLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
salt = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using BlueWest.Data;
|
||||
using BlueWest.WebApi.Context.Users;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.Cryptography;
|
||||
|
||||
public interface IHasher : IPasswordHasher<ApplicationUser>
|
||||
{
|
||||
string CreateHash(string text, BaseCryptoItem.HashAlgorithm algorithm);
|
||||
string CreateHash(string text, string salt, BaseCryptoItem.HashAlgorithm algorithm);
|
||||
bool MatchesHash(string text, string hash);
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
namespace BlueWest.Cryptography
|
||||
{
|
||||
public class SHA2_512 : BaseCryptoItem
|
||||
{
|
||||
public string Hash(string text, string salt, bool storeSalt)
|
||||
{
|
||||
var fullText = string.Concat(text, salt);
|
||||
var data = Encoding.UTF8.GetBytes(fullText);
|
||||
string hash;
|
||||
using SHA512 sha = new SHA512Managed();
|
||||
var hashBytes = sha.ComputeHash(data);
|
||||
var asString = ByteArrayToString(hashBytes);
|
||||
|
||||
if (storeSalt)
|
||||
{
|
||||
hash = $"[{(int)HashAlgorithm.SHA3_512}]{salt}{asString}";
|
||||
return hash;
|
||||
}
|
||||
|
||||
hash = $"[{(int)HashAlgorithm.SHA3_512}]{asString}";
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
public string Hash_PBKDF2(string plainText, string salt, bool saveSaltInResult)
|
||||
{
|
||||
var saltAsBytes = Encoding.ASCII.GetBytes(salt);
|
||||
|
||||
string hashed = ByteArrayToString(KeyDerivation.Pbkdf2(
|
||||
password: plainText,
|
||||
salt: saltAsBytes,
|
||||
prf: KeyDerivationPrf.HMACSHA512, //.NET 3.1 uses HMACSHA256 here
|
||||
iterationCount: 100000, //.NET 3.1 uses 10,000 iterations here
|
||||
numBytesRequested: 64)); //.NET 3.1 uses 32 bytes here
|
||||
|
||||
if (saveSaltInResult)
|
||||
return string.Format("[{0}]{1}{2}", (int)HashAlgorithm.PBKDF2_SHA512, salt, hashed);
|
||||
else
|
||||
return string.Format("[{0}]{1}", (int)HashAlgorithm.PBKDF2_SHA512, hashed);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using System.Threading.Tasks;
|
||||
using BlueWest.Data;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users
|
||||
{
|
||||
public interface IUserManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Create user.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
Task<IdentityResult> CreateAsync(ApplicationUser user);
|
||||
|
||||
/// <summary>
|
||||
/// Checks for user password
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> CheckPasswordAsync(ApplicationUser user, string password);
|
||||
|
||||
/// <summary>
|
||||
/// Find by email
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <returns></returns>
|
||||
Task<ApplicationUser> FindByEmailAsync(string email);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BlueWest.Data;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
/// <summary>
|
||||
/// This is our Users repository.
|
||||
/// Since this is a simple app we'll have the following roles
|
||||
/// Admin and APIClient
|
||||
/// </summary>
|
||||
public interface IUsersRepo : IUserStore<ApplicationUser>
|
||||
{
|
||||
public Task<IEnumerable<ApplicationUser>> GetUsers();
|
||||
public Task CreateUser(ApplicationUser user);
|
||||
public Task SaveChanges();
|
||||
|
||||
public Task<ApplicationUser> GetUserById(string id);
|
||||
|
||||
Task<ApplicationUser> FindByEmailAsync(string email, CancellationToken cancellationToken);
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public interface IJwtFactory
|
||||
{
|
||||
Task<AccessToken> GenerateEncodedToken(string id, string userName);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users
|
||||
{
|
||||
public interface IJwtTokenHandler
|
||||
{
|
||||
string WriteToken(JwtSecurityToken jwt);
|
||||
ClaimsPrincipal ValidateToken(string token, TokenValidationParameters tokenValidationParameters);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public interface ITokenFactory
|
||||
{
|
||||
string GenerateToken(int size= 32);
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Options;
|
||||
using static BlueWest.WebApi.Context.Users.Constants;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public class JwtFactory : IJwtFactory
|
||||
{
|
||||
private readonly IJwtTokenHandler _jwtTokenHandler;
|
||||
private readonly JwtIssuerOptions _jwtOptions;
|
||||
|
||||
public JwtFactory(IJwtTokenHandler jwtTokenHandler, IOptions<JwtIssuerOptions> jwtOptions)
|
||||
{
|
||||
_jwtTokenHandler = jwtTokenHandler;
|
||||
_jwtOptions = jwtOptions.Value;
|
||||
ThrowIfInvalidOptions(_jwtOptions);
|
||||
}
|
||||
|
||||
public async Task<AccessToken> GenerateEncodedToken(string id, string userName)
|
||||
{
|
||||
var identity = GenerateClaimsIdentity(id, userName);
|
||||
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, userName),
|
||||
new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
|
||||
new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(),
|
||||
ClaimValueTypes.Integer64),
|
||||
identity.FindFirst(JwtClaimIdentifiers.Rol),
|
||||
identity.FindFirst(JwtClaimIdentifiers.Id)
|
||||
};
|
||||
|
||||
// Create the JWT security token and encode it.
|
||||
var jwt = new JwtSecurityToken(
|
||||
_jwtOptions.Issuer,
|
||||
_jwtOptions.Audience,
|
||||
claims,
|
||||
_jwtOptions.NotBefore,
|
||||
_jwtOptions.Expiration,
|
||||
_jwtOptions.SigningCredentials);
|
||||
|
||||
return new AccessToken(_jwtTokenHandler.WriteToken(jwt), (int)_jwtOptions.ValidFor.TotalSeconds);
|
||||
}
|
||||
|
||||
private static ClaimsIdentity GenerateClaimsIdentity(string id, string userName)
|
||||
{
|
||||
return new ClaimsIdentity(new GenericIdentity(userName, "Token"), new[]
|
||||
{
|
||||
new Claim(JwtClaimIdentifiers.Id, id),
|
||||
new Claim(JwtClaimIdentifiers.Rol, JwtClaims.ApiAccess)
|
||||
});
|
||||
}
|
||||
|
||||
/// <returns>Date converted to seconds since Unix epoch (Jan 1, 1970, midnight UTC).</returns>
|
||||
private static long ToUnixEpochDate(DateTime date)
|
||||
=> (long)Math.Round((date.ToUniversalTime() -
|
||||
new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero))
|
||||
.TotalSeconds);
|
||||
|
||||
private static void ThrowIfInvalidOptions(JwtIssuerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (options.ValidFor <= TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentException("Must be a non-zero TimeSpan.", nameof(JwtIssuerOptions.ValidFor));
|
||||
}
|
||||
|
||||
if (options.SigningCredentials == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(JwtIssuerOptions.SigningCredentials));
|
||||
}
|
||||
|
||||
if (options.JtiGenerator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(JwtIssuerOptions.JtiGenerator));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public 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; }
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
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;
|
||||
|
||||
public JwtTokenHandler()
|
||||
{
|
||||
if (_jwtSecurityTokenHandler == null)
|
||||
_jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
|
||||
}
|
||||
|
||||
public string WriteToken(JwtSecurityToken jwt)
|
||||
{
|
||||
return _jwtSecurityTokenHandler.WriteToken(jwt);
|
||||
}
|
||||
|
||||
public ClaimsPrincipal ValidateToken(string token, TokenValidationParameters tokenValidationParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
var principal = _jwtSecurityTokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken);
|
||||
|
||||
if (!(securityToken is JwtSecurityToken jwtSecurityToken) || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
|
||||
throw new SecurityTokenException("Invalid token");
|
||||
|
||||
return principal;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users
|
||||
{
|
||||
// from: https://github.com/dotnet/aspnetcore/tree/main/src/Identity/samples/IdentitySample.Mvc/Models/AccountViewModels
|
||||
/// <summary>
|
||||
/// Login View Model
|
||||
/// </summary>
|
||||
public class LoginViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Email
|
||||
/// </summary>
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Password
|
||||
/// </summary>
|
||||
[Required]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// RememberMe
|
||||
/// </summary>
|
||||
[Display(Name = "Remember me?")]
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class RegisterViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Email
|
||||
/// </summary>
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
[Display(Name = "Email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Password
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Password")]
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ConfirmPassword
|
||||
/// </summary>
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm password")]
|
||||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
|
||||
public ApplicationUser ToUser()
|
||||
{
|
||||
var newUser = new ApplicationUser();
|
||||
newUser.Email = Email;
|
||||
newUser.PasswordHash = Password;
|
||||
return newUser;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public class ResetPasswordViewModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm password")]
|
||||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
|
||||
public string Code { get; set; }
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class ApplicationRole : IdentityRole<string>
|
||||
{ }
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class ApplicationRoleClaim : IdentityRoleClaim<string>
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class ApplicationUserClaim : IdentityUserClaim<string>
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class ApplicationUserLogin : IdentityUserLogin<string> { }
|
|
@ -0,0 +1,7 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class ApplicationUserRole : IdentityUserRole<string> { }
|
|
@ -0,0 +1,6 @@
|
|||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class ApplicationUserToken : IdentityUserToken<string> { }
|
|
@ -0,0 +1,18 @@
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public class RoleManager : RoleManager<ApplicationRole>
|
||||
{
|
||||
public RoleManager(
|
||||
IRoleStore<ApplicationRole> store,
|
||||
IEnumerable<IRoleValidator<ApplicationRole>> roleValidators,
|
||||
ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors,
|
||||
ILogger<RoleManager<ApplicationRole>> logger) : base(store, roleValidators, keyNormalizer, errors, logger)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <summary>
|
||||
/// Role storage management
|
||||
/// </summary>
|
||||
public class RoleStore : IRoleStore<ApplicationRole>
|
||||
{
|
||||
private ApplicationUserDbContext _dbContext;
|
||||
|
||||
/// <summary>
|
||||
/// Role Store constructor
|
||||
/// </summary>
|
||||
/// <param name="dbContext"></param>
|
||||
public RoleStore(ApplicationUserDbContext dbContext)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public void Dispose()
|
||||
{
|
||||
_dbContext = null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get role name
|
||||
/// </summary>
|
||||
/// <param name="role"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public async Task<string> GetRoleNameAsync(ApplicationUserRole role, CancellationToken cancellationToken)
|
||||
{
|
||||
var foundRole = await _dbContext.Roles
|
||||
.FirstOrDefaultAsync(x => x.Id == role.RoleId, cancellationToken: cancellationToken);
|
||||
|
||||
if (foundRole != null)
|
||||
{
|
||||
return foundRole.Name;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
public async Task<IdentityResult> CreateAsync(ApplicationRole role, CancellationToken cancellationToken)
|
||||
{
|
||||
_dbContext.Roles.Add(role);
|
||||
return await _dbContext.SaveChangesAsync(cancellationToken) >= 0 ? IdentityResult.Success : IdentityResult.Failed();
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> UpdateAsync(ApplicationRole role, CancellationToken cancellationToken)
|
||||
{
|
||||
_dbContext.Roles.Update(role);
|
||||
return await _dbContext.SaveChangesAsync(cancellationToken) >= 0 ? IdentityResult.Success : IdentityResult.Failed();
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> DeleteAsync(ApplicationRole role, CancellationToken cancellationToken)
|
||||
{
|
||||
_dbContext.Roles.Remove(role);
|
||||
return await _dbContext.SaveChangesAsync(cancellationToken) >= 0 ? IdentityResult.Success : IdentityResult.Failed();
|
||||
}
|
||||
|
||||
public async Task<string> GetRoleIdAsync(ApplicationRole role, CancellationToken cancellationToken)
|
||||
{
|
||||
var x = await _dbContext.Roles.FirstOrDefaultAsync(x => x.Id == role.Id, cancellationToken: cancellationToken);
|
||||
if (x != null)
|
||||
{
|
||||
return x.Id;
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public Task<string> GetRoleNameAsync(ApplicationRole role, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task SetRoleNameAsync(ApplicationRole role, string roleName, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<string> GetNormalizedRoleNameAsync(ApplicationRole role, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task SetNormalizedRoleNameAsync(ApplicationRole role, string normalizedName, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<ApplicationRole> FindByIdAsync(string roleId, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<ApplicationRole> FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BlueWest.Cryptography;
|
||||
using BlueWest.Data;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
public class UserManager : UserManager<ApplicationUser>, IUserManager
|
||||
{
|
||||
private readonly IHasher _hasher;
|
||||
private readonly IUsersRepo _usersRepo;
|
||||
public UserManager(IUsersRepo store, IOptions<IdentityOptions> optionsAccessor,
|
||||
IHasher passwordHasher, IEnumerable<IUserValidator<ApplicationUser>> userValidators,
|
||||
IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators, ILookupNormalizer keyNormalizer,
|
||||
IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<ApplicationUser>> logger) : base(store,
|
||||
optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services,
|
||||
logger)
|
||||
{
|
||||
_hasher = passwordHasher;
|
||||
_usersRepo = store;
|
||||
}
|
||||
|
||||
public override async Task<IdentityResult> CreateAsync(ApplicationUser user)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var result = await ValidateUserAsync(user);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if (Options.Lockout.AllowedForNewUsers && SupportsUserLockout)
|
||||
{
|
||||
// await GetUserLockoutStore().SetLockoutEnabledAsync(user, true, CancellationToken);
|
||||
}
|
||||
await UpdateNormalizedUserNameAsync(user);
|
||||
await UpdateNormalizedEmailAsync(user);
|
||||
|
||||
return await _usersRepo.CreateAsync(user, CancellationToken);
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckPasswordAsync(ApplicationUser user, string password)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var passwordStore = GetPasswordStore();
|
||||
|
||||
var result = await VerifyPasswordAsync(passwordStore, user, password);
|
||||
if (result == PasswordVerificationResult.SuccessRehashNeeded)
|
||||
{
|
||||
//Remove the IPasswordStore parameter so we can call the protected, not private, method
|
||||
await UpdatePasswordHash(user, password, validatePassword: false);
|
||||
await UpdateUserAsync(user);
|
||||
}
|
||||
|
||||
var success = result != PasswordVerificationResult.Failed;
|
||||
if (!success)
|
||||
{
|
||||
var userId = user != null ? GetUserIdAsync(user).Result : "(null)";
|
||||
Logger.LogWarning(0, "Invalid password for user {userId}.", userId);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
protected override async Task<PasswordVerificationResult> VerifyPasswordAsync(IUserPasswordStore<ApplicationUser> store, ApplicationUser user, string password)
|
||||
{
|
||||
string existingHash;
|
||||
|
||||
if (user != null)
|
||||
existingHash = await store.GetPasswordHashAsync(user, CancellationToken);
|
||||
else
|
||||
existingHash = "not a real hash";
|
||||
|
||||
if (existingHash == null)
|
||||
{
|
||||
return PasswordVerificationResult.Failed;
|
||||
}
|
||||
return PasswordHasher.VerifyHashedPassword(user, existingHash, password);
|
||||
}
|
||||
|
||||
public override async Task<ApplicationUser> FindByNameAsync(string userName)
|
||||
{
|
||||
if (userName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(userName));
|
||||
}
|
||||
|
||||
ApplicationUser user;
|
||||
|
||||
if (Store is IUsersRepo repo)
|
||||
{
|
||||
user = await repo.FindByNameAsync(userName, CancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
userName = NormalizeName(userName);
|
||||
user = await Store.FindByNameAsync(userName, CancellationToken);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
public override async Task<IdentityResult> ChangePasswordAsync(ApplicationUser user, string currentPassword, string newPassword)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var passwordStore = GetPasswordStore();
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
if (await VerifyPasswordAsync(passwordStore, user, currentPassword) != PasswordVerificationResult.Failed)
|
||||
{
|
||||
var result = await UpdatePasswordHash(user, newPassword, validatePassword: false);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return await UpdateUserAsync(user);
|
||||
}
|
||||
Logger.LogWarning(2, "Change password failed for user {userId}.", await GetUserIdAsync(user));
|
||||
return IdentityResult.Failed(ErrorDescriber.PasswordMismatch());
|
||||
}
|
||||
|
||||
|
||||
private IUserPasswordStore<ApplicationUser> GetPasswordStore()
|
||||
{
|
||||
if (Store is IUserPasswordStore<ApplicationUser> passwordStore)
|
||||
{
|
||||
return passwordStore;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override async Task<ApplicationUser> FindByEmailAsync(string email)
|
||||
{
|
||||
ApplicationUser user = null;
|
||||
|
||||
if (Store is IUsersRepo repo)
|
||||
{
|
||||
user = await repo.FindByEmailAsync(email, CancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
user = await Store.FindByNameAsync(email, CancellationToken);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BlueWest.Data;
|
||||
using BlueWest.WebApi.EF;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BlueWest.WebApi.Context.Users;
|
||||
|
||||
/// <summary>
|
||||
/// Users Repository
|
||||
/// </summary>
|
||||
public class UserRepository : UserStore<ApplicationUser, ApplicationRole, ApplicationUserDbContext>, IUsersRepo
|
||||
{
|
||||
private readonly ApplicationUserDbContext _context;
|
||||
|
||||
public UserRepository(ApplicationUserDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Application Users
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<ApplicationUser>> GetUsers()
|
||||
{
|
||||
var users = await _context.Users.ToListAsync();
|
||||
return users;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create Application User
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
public async Task CreateUser(ApplicationUser user)
|
||||
{
|
||||
await CreateAsync(user, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save Changes
|
||||
/// </summary>
|
||||
public async Task SaveChanges()
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task<bool> SaveChanges(ApplicationUser user)
|
||||
{
|
||||
_context.Users.Update(user);
|
||||
return await _context.SaveChangesAsync() > 0;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Dispose repository
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_context.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<string> GetUserIdAsync(ApplicationUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<string>(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.FromResult(user.Id.ToString());
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<string> GetUserNameAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<string>(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.FromResult(user.UserName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task SetUserNameAsync(ApplicationUser user, string userName, CancellationToken cancellationToken)
|
||||
{
|
||||
var foundUser = await _context.Users.FirstOrDefaultAsync(x => x.Id == user.Id, cancellationToken: cancellationToken);
|
||||
if (foundUser == null) return;
|
||||
foundUser.UserName = userName;
|
||||
await SaveChanges(user);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<string> GetNormalizedUserNameAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<string>(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.FromResult(user.NormalizedUserName);
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<IdentityResult> CreateAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
var u = await _context.AddAsync(user, cancellationToken);
|
||||
|
||||
if(u.State == EntityState.Added)
|
||||
{
|
||||
await SaveChanges();
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
|
||||
return IdentityResult.Failed();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<IdentityResult> UpdateAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
_context.Users.Update(user);
|
||||
|
||||
var success = await _context.SaveChangesAsync(cancellationToken) > 0;
|
||||
|
||||
if (success) return IdentityResult.Success;
|
||||
|
||||
return IdentityResult.Failed();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<IdentityResult> DeleteAsync(ApplicationUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
var foundUser = await _context.Users.FirstOrDefaultAsync(x=> x.Id == user.Id, cancellationToken: cancellationToken);
|
||||
|
||||
var error = new IdentityError {Description = "ApplicationUser Not found"};
|
||||
|
||||
if (foundUser == null) return IdentityResult.Failed(error);
|
||||
|
||||
_context.Users.Remove(foundUser);
|
||||
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ApplicationUser> GetUserById(string id)
|
||||
{
|
||||
var db = _context.Users;
|
||||
var user = await db.FirstOrDefaultAsync(u => u.Id.ToString() == id);
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<string> GetPasswordHashAsync(ApplicationUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<string>(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.FromResult(user.PasswordHash);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<bool> HasPasswordAsync(ApplicationUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<bool>(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.FromResult(!string.IsNullOrEmpty(user.PasswordHash));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<string> GetEmailAsync(ApplicationUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<string>(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.FromResult(user.Email);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<bool> GetEmailConfirmedAsync(ApplicationUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<bool>(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.FromResult(user.EmailConfirmed);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ApplicationUser> FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ApplicationUser user = null;
|
||||
var db = _context.Users;
|
||||
user = await db.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, cancellationToken: cancellationToken);
|
||||
return user;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<string> GetNormalizedEmailAsync(ApplicationUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
base.GetNormalizedEmailAsync(user, cancellationToken);
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<string>(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.FromResult(user.NormalizedEmail);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,2 +1,2 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeEditing/SuppressNullableWarningFix/Enabled/@EntryValue">False</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:Boolean x:Key="/Default/CodeEditing/SuppressUninitializedWarningFix/Enabled/@EntryValue">False</s:Boolean></wpf:ResourceDictionary>
|
|
@ -103,8 +103,8 @@ namespace BlueWest.Core
|
|||
|
||||
private static void MkdirCommand(string? input)
|
||||
{
|
||||
var argument = input.Split(" ");
|
||||
if (argument.Length > 1 && argument[0] != "")
|
||||
var argument = input?.Split(" ");
|
||||
if (argument != null && argument.Length > 1 && argument[0] != "")
|
||||
{
|
||||
PathUtils.CreateDirectory(argument[1]);
|
||||
return;
|
||||
|
@ -127,8 +127,9 @@ namespace BlueWest.Core
|
|||
|
||||
private static void LsCommand(string? input)
|
||||
{
|
||||
var split = input.Split(" ");
|
||||
var split = input?.Split(" ");
|
||||
|
||||
if (split != null)
|
||||
foreach (var name in split)
|
||||
{
|
||||
if (name == "ls") continue;
|
||||
|
|
|
@ -88,7 +88,9 @@ namespace BlueWest.Tools
|
|||
{
|
||||
var eventListener = list[i];
|
||||
var casted = eventListener as EventListener<TEvent>;
|
||||
#pragma warning disable CS8602
|
||||
casted.OnEvent( newEvent );
|
||||
#pragma warning restore CS8602
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,9 +102,7 @@ namespace BlueWest.Tools
|
|||
/// <param name="receiver">Receiver.</param>
|
||||
private bool SubscriptionExists( Type type, EventListenerBase receiver )
|
||||
{
|
||||
List<EventListenerBase> receivers;
|
||||
|
||||
if( !_subscribersList.TryGetValue( type, out receivers ) ) return false;
|
||||
if( !_subscribersList.TryGetValue( type, out var receivers ) ) return false;
|
||||
|
||||
bool exists = false;
|
||||
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BlueWest.Tools
|
||||
{
|
||||
public struct EventManagerAsync
|
||||
{
|
||||
private static readonly Dictionary<Type, List<EventListenerBaseAsync>> _subscribersList;
|
||||
private static readonly ConcurrentDictionary<Type, List<EventListenerBaseAsync>> _subscribersList;
|
||||
|
||||
static EventManagerAsync()
|
||||
{
|
||||
_subscribersList = new Dictionary<Type, List<EventListenerBaseAsync>>(12412);
|
||||
_subscribersList = new ConcurrentDictionary<Type, List<EventListenerBaseAsync>>();
|
||||
}
|
||||
|
||||
/// <suary>
|
||||
|
@ -58,7 +59,7 @@ namespace BlueWest.Tools
|
|||
subscriberList.Remove(subscriberList[i]);
|
||||
|
||||
if (subscriberList.Count == 0)
|
||||
_subscribersList.Remove(eventType);
|
||||
_subscribersList.Remove(eventType, out subscriberList);
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -116,11 +116,13 @@ namespace BlueWest.Core.ComponentSystem
|
|||
}
|
||||
public void AddComponent<T>() where T : Component
|
||||
{
|
||||
var component = Activator.CreateInstance(typeof(T), _eventManager) as Component;
|
||||
if (Activator.CreateInstance(typeof(T), _eventManager) is Component component)
|
||||
{
|
||||
_components.Add(component);
|
||||
_componentsCount += 1;
|
||||
component.Start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected virtual void Awake()
|
||||
|
|
|
@ -6,7 +6,7 @@ public class DisabledArtefact
|
|||
{
|
||||
|
||||
protected internal ArtefactFrequency Frequency;
|
||||
protected EventManager _eventManager;
|
||||
protected EventManager _eventManager = null!;
|
||||
|
||||
protected virtual void Update(double delta)
|
||||
{
|
||||
|
|
|
@ -59,9 +59,15 @@ namespace BlueWest.Core
|
|||
{
|
||||
SpawnThread(() =>
|
||||
{
|
||||
object instantiatiable = Activator.CreateInstance(type);
|
||||
Artefact behavior = instantiatiable as Artefact;
|
||||
#pragma warning disable CS8600
|
||||
object instantiate = Activator.CreateInstance(type);
|
||||
#pragma warning restore CS8600
|
||||
#pragma warning disable CS8600
|
||||
Artefact behavior = instantiate as Artefact;
|
||||
#pragma warning restore CS8600
|
||||
#pragma warning disable CS8602
|
||||
behavior.SetupEntity(_eventManager);
|
||||
#pragma warning restore CS8602
|
||||
behavior.RunLoop();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace BlueWest.DataAgent
|
|||
|
||||
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(source))
|
||||
{
|
||||
object value = property.GetValue(source).ToString();
|
||||
object? value = property.GetValue(source)?.ToString();
|
||||
|
||||
if (value is Dictionary<string, string>.KeyCollection keyCollection)
|
||||
{
|
||||
|
|
|
@ -9,8 +9,6 @@ namespace BlueWest.Core
|
|||
{
|
||||
public static class MappingExtensions
|
||||
{
|
||||
private static Type _currentType = null;
|
||||
|
||||
private static bool IsMatchingProperty(ref Type type, string propertyName)
|
||||
{
|
||||
var typeProperties = type.GetProperties();
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace PerformanceSolution.Tools
|
|||
{
|
||||
public static class JsonTools
|
||||
{
|
||||
public static Dictionary<string, object> ConvertFromObjectToDictionary(object arg)
|
||||
public static Dictionary<string, object?> ConvertFromObjectToDictionary(object arg)
|
||||
{
|
||||
var properties = arg.GetType().GetProperties();
|
||||
|
||||
|
|
|
@ -1,149 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using Directory = System.IO.Directory;
|
||||
using File = System.IO.File;
|
||||
|
||||
|
||||
namespace PerformanceSolution.Tools
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows the save and load of objects in a specific folder and file.
|
||||
/// </summary>
|
||||
public static class SaveLoadManager
|
||||
{
|
||||
private static readonly string _baseFolderName = Path.DirectorySeparatorChar + "BMData";
|
||||
private const string _defaultFolderName = "BlueWest";
|
||||
|
||||
/// <summary>
|
||||
/// Determines the save path to use when loading and saving a file based on a folder name.
|
||||
/// </summary>
|
||||
/// <returns>The save path.</returns>
|
||||
/// <param name="folderName">Folder name.</param>
|
||||
static string DetermineSavePath(string folderName = _defaultFolderName)
|
||||
{
|
||||
string savePath;
|
||||
// depending on the device we're on, we assemble the path
|
||||
|
||||
//savePath = OS.GetUserDataDir() + "/";
|
||||
|
||||
savePath = Path.Combine(Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.ApplicationData));
|
||||
|
||||
// #if UNITY_EDITOR
|
||||
// savePath = Application.dataPath + _baseFolderName;
|
||||
// #endif
|
||||
|
||||
var pathSeparator = Path.DirectorySeparatorChar;
|
||||
|
||||
savePath = savePath + pathSeparator + folderName + pathSeparator;
|
||||
return savePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the name of the file to save
|
||||
/// </summary>
|
||||
/// <returns>The save file name.</returns>
|
||||
/// <param name="fileName">File name.</param>
|
||||
static string DetermineSaveFileName(string fileName)
|
||||
{
|
||||
return fileName + ".binary";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the specified saveObject, fileName and foldername into a file on disk.
|
||||
/// </summary>
|
||||
/// <param name="saveObject">Save object.</param>
|
||||
/// <param name="fileName">File name.</param>
|
||||
/// <param name="foldername">Foldername.</param>
|
||||
public static void Save<T>(T saveObject, string fileName, string foldername = _defaultFolderName)
|
||||
where T : class
|
||||
{
|
||||
string savePath = DetermineSavePath(foldername);
|
||||
string saveFileName = DetermineSaveFileName(fileName);
|
||||
// if the directory doesn't already exist, we create it
|
||||
if (!Directory.Exists(savePath))
|
||||
{
|
||||
Directory.CreateDirectory(savePath);
|
||||
}
|
||||
|
||||
// we serialize and write our object into a file on disk
|
||||
//var byteData = MessagePackSerializer.Serialize(saveObject);
|
||||
|
||||
|
||||
File.WriteAllText(savePath + saveFileName + ".json", saveObject.ToString());
|
||||
|
||||
//File.WriteAllBytes(savePath + saveFileName, byteData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the specified file based on a file name into a specified folder
|
||||
/// </summary>
|
||||
/// <param name="fileName">File name.</param>
|
||||
/// <param name="foldername">Foldername.</param>
|
||||
public static T Load<T>(string fileName, string foldername = _defaultFolderName) where T : class
|
||||
{
|
||||
string savePath = DetermineSavePath(foldername);
|
||||
string saveFileName = savePath + DetermineSaveFileName(fileName);
|
||||
|
||||
object returnObject;
|
||||
|
||||
// if the MMSaves directory or the save file doesn't exist, there's nothing to load, we do nothing and exit
|
||||
if (!Directory.Exists(savePath) || !File.Exists(saveFileName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] readByte = File.ReadAllBytes(saveFileName);
|
||||
|
||||
//var finalObject = MessagePackSerializer.Deserialize<T>(readByte);
|
||||
|
||||
//return finalObject;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a save from disk
|
||||
/// </summary>
|
||||
/// <param name="fileName">File name.</param>
|
||||
/// <param name="folderName">Folder name.</param>
|
||||
public static void DeleteSave(string fileName, string folderName = _defaultFolderName)
|
||||
{
|
||||
string savePath = DetermineSavePath(folderName);
|
||||
string saveFileName = DetermineSaveFileName(fileName);
|
||||
if (File.Exists(savePath + saveFileName))
|
||||
{
|
||||
File.Delete(savePath + saveFileName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeleteSaveFolder(string folderName = _defaultFolderName)
|
||||
{
|
||||
string savePath = DetermineSavePath(folderName);
|
||||
if (Directory.Exists(savePath))
|
||||
{
|
||||
DeleteDirectory(savePath);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeleteDirectory(string target_dir)
|
||||
{
|
||||
string[] files = Directory.GetFiles(target_dir);
|
||||
string[] dirs = Directory.GetDirectories(target_dir);
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
File.SetAttributes(file, FileAttributes.Normal);
|
||||
File.Delete(file);
|
||||
}
|
||||
|
||||
foreach (string dir in dirs)
|
||||
{
|
||||
DeleteDirectory(dir);
|
||||
}
|
||||
|
||||
Directory.Delete(target_dir, false);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue