mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Refractor token auth stuff to use identiycore framework
This commit is contained in:
parent
f8d7581a12
commit
8f7df85d49
@ -8,6 +8,7 @@
|
|||||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.0" />
|
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.1" NoWarn="NU1605" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.1" NoWarn="NU1605" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.1" NoWarn="NU1605" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.1" NoWarn="NU1605" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.1">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.Data;
|
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Interfaces;
|
using API.Interfaces;
|
||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -14,17 +13,25 @@ namespace API.Controllers
|
|||||||
{
|
{
|
||||||
public class AccountController : BaseApiController
|
public class AccountController : BaseApiController
|
||||||
{
|
{
|
||||||
private readonly DataContext _context;
|
private readonly UserManager<AppUser> _userManager;
|
||||||
|
private readonly SignInManager<AppUser> _signInManager;
|
||||||
private readonly ITokenService _tokenService;
|
private readonly ITokenService _tokenService;
|
||||||
private readonly IUserRepository _userRepository;
|
private readonly IUserRepository _userRepository;
|
||||||
private readonly ILogger<AccountController> _logger;
|
private readonly ILogger<AccountController> _logger;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
public AccountController(DataContext context, ITokenService tokenService, IUserRepository userRepository, ILogger<AccountController> logger)
|
public AccountController(UserManager<AppUser> userManager,
|
||||||
|
SignInManager<AppUser> signInManager,
|
||||||
|
ITokenService tokenService, IUserRepository userRepository,
|
||||||
|
ILogger<AccountController> logger,
|
||||||
|
IMapper mapper)
|
||||||
{
|
{
|
||||||
_context = context;
|
_userManager = userManager;
|
||||||
|
_signInManager = signInManager;
|
||||||
_tokenService = tokenService;
|
_tokenService = tokenService;
|
||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_mapper = mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
@ -35,24 +42,21 @@ namespace API.Controllers
|
|||||||
{
|
{
|
||||||
return BadRequest("Username is taken.");
|
return BadRequest("Username is taken.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var user = _mapper.Map<AppUser>(registerDto);
|
||||||
|
|
||||||
|
var result = await _userManager.CreateAsync(user, registerDto.Password);
|
||||||
|
|
||||||
|
if (!result.Succeeded) return BadRequest(result.Errors);
|
||||||
|
|
||||||
|
var roleResult = await _userManager.AddToRoleAsync(user, "Pleb");
|
||||||
|
|
||||||
|
if (!roleResult.Succeeded) return BadRequest(result.Errors);
|
||||||
|
|
||||||
using var hmac = new HMACSHA512();
|
|
||||||
var user = new AppUser
|
|
||||||
{
|
|
||||||
UserName = registerDto.Username.ToLower(),
|
|
||||||
PasswordHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(registerDto.Password)),
|
|
||||||
PasswordSalt = hmac.Key,
|
|
||||||
IsAdmin = registerDto.IsAdmin,
|
|
||||||
LastActive = DateTime.Now
|
|
||||||
};
|
|
||||||
|
|
||||||
_context.Users.Add(user);
|
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
|
|
||||||
return new UserDto()
|
return new UserDto()
|
||||||
{
|
{
|
||||||
Username = user.UserName,
|
Username = user.UserName,
|
||||||
Token = _tokenService.CreateToken(user),
|
Token = await _tokenService.CreateToken(user),
|
||||||
IsAdmin = user.IsAdmin
|
IsAdmin = user.IsAdmin
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -60,18 +64,15 @@ namespace API.Controllers
|
|||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
public async Task<ActionResult<UserDto>> Login(LoginDto loginDto)
|
public async Task<ActionResult<UserDto>> Login(LoginDto loginDto)
|
||||||
{
|
{
|
||||||
var user = await _userRepository.GetUserByUsernameAsync(loginDto.Username);
|
var user = await _userManager.Users
|
||||||
|
.SingleOrDefaultAsync(x => x.UserName == loginDto.Username.ToLower());
|
||||||
|
|
||||||
if (user == null) return Unauthorized("Invalid username");
|
if (user == null) return Unauthorized("Invalid username");
|
||||||
|
|
||||||
using var hmac = new HMACSHA512(user.PasswordSalt);
|
|
||||||
|
|
||||||
var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(loginDto.Password));
|
var result = await _signInManager
|
||||||
|
.CheckPasswordSignInAsync(user, loginDto.Password, false);
|
||||||
|
|
||||||
for (int i = 0; i < computedHash.Length; i++)
|
if (!result.Succeeded) return Unauthorized();
|
||||||
{
|
|
||||||
if (computedHash[i] != user.PasswordHash[i]) return Unauthorized("Invalid password");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update LastActive on account
|
// Update LastActive on account
|
||||||
user.LastActive = DateTime.Now;
|
user.LastActive = DateTime.Now;
|
||||||
@ -81,14 +82,14 @@ namespace API.Controllers
|
|||||||
return new UserDto()
|
return new UserDto()
|
||||||
{
|
{
|
||||||
Username = user.UserName,
|
Username = user.UserName,
|
||||||
Token = _tokenService.CreateToken(user),
|
Token = await _tokenService.CreateToken(user),
|
||||||
IsAdmin = user.IsAdmin
|
IsAdmin = user.IsAdmin
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> UserExists(string username)
|
private async Task<bool> UserExists(string username)
|
||||||
{
|
{
|
||||||
return await _context.Users.AnyAsync(user => user.UserName == username.ToLower());
|
return await _userManager.Users.AnyAsync(user => user.UserName == username.ToLower());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -100,8 +100,5 @@ namespace API.Controllers
|
|||||||
|
|
||||||
return BadRequest("Not Implemented");
|
return BadRequest("Not Implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,17 +1,36 @@
|
|||||||
using System;
|
using System;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace API.Data
|
namespace API.Data
|
||||||
{
|
{
|
||||||
public class DataContext : DbContext
|
public class DataContext : IdentityDbContext<AppUser, AppRole, int,
|
||||||
|
IdentityUserClaim<int>, AppUserRole, IdentityUserLogin<int>,
|
||||||
|
IdentityRoleClaim<int>, IdentityUserToken<int>>
|
||||||
{
|
{
|
||||||
public DataContext(DbContextOptions options) : base(options)
|
public DataContext(DbContextOptions options) : base(options)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<AppUser> Users { get; set; }
|
|
||||||
public DbSet<Library> Library { get; set; }
|
public DbSet<Library> Library { get; set; }
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
|
{
|
||||||
|
base.OnModelCreating(builder);
|
||||||
|
|
||||||
|
builder.Entity<AppUser>()
|
||||||
|
.HasMany(ur => ur.UserRoles)
|
||||||
|
.WithOne(u => u.User)
|
||||||
|
.HasForeignKey(ur => ur.UserId)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Entity<AppRole>()
|
||||||
|
.HasMany(ur => ur.UserRoles)
|
||||||
|
.WithOne(u => u.Role)
|
||||||
|
.HasForeignKey(ur => ur.RoleId)
|
||||||
|
.IsRequired();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,37 +16,127 @@ namespace API.Data.Migrations
|
|||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "5.0.1");
|
.HasAnnotation("ProductVersion", "5.0.1");
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.AppRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("RoleNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoles");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.AppUser", b =>
|
modelBuilder.Entity("API.Entities.AppUser", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AccessFailedCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<DateTime>("Created")
|
b.Property<DateTime>("Created")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("EmailConfirmed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<bool>("IsAdmin")
|
b.Property<bool>("IsAdmin")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<DateTime>("LastActive")
|
b.Property<DateTime>("LastActive")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("LockoutEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedEmail")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedUserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<byte[]>("PasswordHash")
|
b.Property<byte[]>("PasswordHash")
|
||||||
.HasColumnType("BLOB");
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
b.Property<byte[]>("PasswordSalt")
|
b.Property<byte[]>("PasswordSalt")
|
||||||
.HasColumnType("BLOB");
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("PhoneNumberConfirmed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<uint>("RowVersion")
|
b.Property<uint>("RowVersion")
|
||||||
.IsConcurrencyToken()
|
.IsConcurrencyToken()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SecurityStamp")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("TwoFactorEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("UserName")
|
b.Property<string>("UserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("Users");
|
b.HasIndex("NormalizedEmail")
|
||||||
|
.HasDatabaseName("EmailIndex");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedUserName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("UserNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUsers");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.AppUserRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("RoleId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "RoleId");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserRoles");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.FolderPath", b =>
|
modelBuilder.Entity("API.Entities.FolderPath", b =>
|
||||||
@ -103,6 +193,109 @@ namespace API.Data.Migrations
|
|||||||
b.ToTable("AppUserLibrary");
|
b.ToTable("AppUserLibrary");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("RoleId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoleClaims");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserClaims");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderKey")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderDisplayName")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("LoginProvider", "ProviderKey");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserLogins");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "LoginProvider", "Name");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserTokens");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.AppUserRole", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.AppRole", "Role")
|
||||||
|
.WithMany("UserRoles")
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("API.Entities.AppUser", "User")
|
||||||
|
.WithMany("UserRoles")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Role");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.FolderPath", b =>
|
modelBuilder.Entity("API.Entities.FolderPath", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Entities.Library", "Library")
|
b.HasOne("API.Entities.Library", "Library")
|
||||||
@ -129,6 +322,52 @@ namespace API.Data.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.AppRole", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.AppUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.AppUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.AppUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.AppRole", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("UserRoles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.AppUser", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("UserRoles");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.Library", b =>
|
modelBuilder.Entity("API.Entities.Library", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Folders");
|
b.Navigation("Folders");
|
||||||
|
24
API/Data/Seed.cs
Normal file
24
API/Data/Seed.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using API.Entities;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace API.Data
|
||||||
|
{
|
||||||
|
public class Seed
|
||||||
|
{
|
||||||
|
public static async Task SeedRoles(RoleManager<AppRole> roleManager)
|
||||||
|
{
|
||||||
|
var roles = new List<AppRole>
|
||||||
|
{
|
||||||
|
new AppRole {Name = "Admin"},
|
||||||
|
new AppRole {Name = "Pleb"}
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var role in roles)
|
||||||
|
{
|
||||||
|
await roleManager.CreateAsync(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
API/Entities/AppRole.cs
Normal file
10
API/Entities/AppRole.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace API.Entities
|
||||||
|
{
|
||||||
|
public class AppRole : IdentityRole<int>
|
||||||
|
{
|
||||||
|
public ICollection<AppUserRole> UserRoles { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using API.Entities.Interfaces;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
|
||||||
namespace API.Entities
|
namespace API.Entities
|
||||||
{
|
{
|
||||||
public class AppUser : IHasConcurrencyToken
|
public class AppUser : IdentityUser<int>
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
|
||||||
public string UserName { get; set; }
|
|
||||||
public byte[] PasswordHash { get; set; }
|
|
||||||
public byte[] PasswordSalt { get; set; }
|
|
||||||
public DateTime Created { get; set; } = DateTime.Now;
|
public DateTime Created { get; set; } = DateTime.Now;
|
||||||
public DateTime LastActive { get; set; }
|
public DateTime LastActive { get; set; }
|
||||||
public bool IsAdmin { get; set; }
|
public bool IsAdmin { get; set; }
|
||||||
@ -20,6 +15,8 @@ namespace API.Entities
|
|||||||
|
|
||||||
[ConcurrencyCheck]
|
[ConcurrencyCheck]
|
||||||
public uint RowVersion { get; set; }
|
public uint RowVersion { get; set; }
|
||||||
|
|
||||||
|
public ICollection<AppUserRole> UserRoles { get; set; }
|
||||||
|
|
||||||
public void OnSavingChanges()
|
public void OnSavingChanges()
|
||||||
{
|
{
|
||||||
|
10
API/Entities/AppUserRole.cs
Normal file
10
API/Entities/AppUserRole.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace API.Entities
|
||||||
|
{
|
||||||
|
public class AppUserRole : IdentityUserRole<int>
|
||||||
|
{
|
||||||
|
public AppUser User { get; set; }
|
||||||
|
public AppRole Role { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
using System.Collections;
|
using System.Text;
|
||||||
using System.Text;
|
using API.Data;
|
||||||
|
using API.Entities;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
@ -11,6 +13,17 @@ namespace API.Extensions
|
|||||||
{
|
{
|
||||||
public static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration config)
|
public static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration config)
|
||||||
{
|
{
|
||||||
|
services.AddIdentityCore<AppUser>(opt =>
|
||||||
|
{
|
||||||
|
// Change password / signin requirements here
|
||||||
|
opt.Password.RequireNonAlphanumeric = false;
|
||||||
|
})
|
||||||
|
.AddRoles<AppRole>()
|
||||||
|
.AddRoleManager<RoleManager<AppRole>>()
|
||||||
|
.AddSignInManager<SignInManager<AppUser>>()
|
||||||
|
.AddRoleValidator<RoleValidator<AppRole>>()
|
||||||
|
.AddEntityFrameworkStores<DataContext>();
|
||||||
|
|
||||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||||
.AddJwtBearer(options =>
|
.AddJwtBearer(options =>
|
||||||
{
|
{
|
||||||
|
@ -19,6 +19,8 @@ namespace API.Helpers
|
|||||||
|
|
||||||
CreateMap<AppUser, MemberDto>()
|
CreateMap<AppUser, MemberDto>()
|
||||||
.AfterMap((ps, pst, context) => context.Mapper.Map(ps.Libraries, pst.Libraries));
|
.AfterMap((ps, pst, context) => context.Mapper.Map(ps.Libraries, pst.Libraries));
|
||||||
|
|
||||||
|
CreateMap<RegisterDto, AppUser>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,10 @@
|
|||||||
using API.Entities;
|
using System.Threading.Tasks;
|
||||||
|
using API.Entities;
|
||||||
|
|
||||||
namespace API.Interfaces
|
namespace API.Interfaces
|
||||||
{
|
{
|
||||||
public interface ITokenService
|
public interface ITokenService
|
||||||
{
|
{
|
||||||
string CreateToken(AppUser user);
|
Task<string> CreateToken(AppUser user);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
|
using API.Entities;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
@ -21,8 +23,10 @@ namespace API
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var context = services.GetRequiredService<DataContext>();
|
var context = services.GetRequiredService<DataContext>();
|
||||||
|
var roleManager = services.GetRequiredService<RoleManager<AppRole>>();
|
||||||
// Apply all migrations on startup
|
// Apply all migrations on startup
|
||||||
await context.Database.MigrateAsync();
|
await context.Database.MigrateAsync();
|
||||||
|
await Seed.SeedRoles(roleManager);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Interfaces;
|
using API.Interfaces;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames;
|
using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames;
|
||||||
@ -14,19 +17,25 @@ namespace API.Services
|
|||||||
{
|
{
|
||||||
public class TokenService : ITokenService
|
public class TokenService : ITokenService
|
||||||
{
|
{
|
||||||
|
private readonly UserManager<AppUser> _userManager;
|
||||||
private readonly SymmetricSecurityKey _key;
|
private readonly SymmetricSecurityKey _key;
|
||||||
|
|
||||||
public TokenService(IConfiguration config)
|
public TokenService(IConfiguration config, UserManager<AppUser> userManager)
|
||||||
{
|
{
|
||||||
|
_userManager = userManager;
|
||||||
_key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"]));
|
_key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CreateToken(AppUser user)
|
public async Task<string> CreateToken(AppUser user)
|
||||||
{
|
{
|
||||||
var claims = new List<Claim>
|
var claims = new List<Claim>
|
||||||
{
|
{
|
||||||
new Claim(JwtRegisteredClaimNames.NameId, user.UserName)
|
new Claim(JwtRegisteredClaimNames.NameId, user.UserName)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var roles = await _userManager.GetRolesAsync(user);
|
||||||
|
|
||||||
|
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
|
||||||
|
|
||||||
var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);
|
var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user