Unverified Commit 2ddc9eb6 authored by Almouhannad Hafez's avatar Almouhannad Hafez Committed by GitHub

Merge pull request #11 from Almouhannad/B_Add-identity

B add identity
parents 1c65ec94 66384a51
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Persistence.Identity.Authentication.JWT;
using System.Text;
namespace API.Options.JWT;
public class JWTBearerOptionsSetup : IPostConfigureOptions<JwtBearerOptions>
{
#region JWT Options CTOR DI
private readonly JWTOptions _jwtOptions;
public JWTBearerOptionsSetup(IOptions<JWTOptions> jwtOptions)
{
_jwtOptions = jwtOptions.Value;
}
#endregion
public void PostConfigure(string? name, JwtBearerOptions options)
{
options.TokenValidationParameters.ValidIssuer = _jwtOptions.Issuer;
options.TokenValidationParameters.ValidAudience = _jwtOptions.Audience;
options.TokenValidationParameters.IssuerSigningKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.SecretKey));
}
}
using Microsoft.Extensions.Options;
using Persistence.Identity.Authentication.JWT;
namespace API.Options.JWT;
public class JWTOptionsSetup : IConfigureOptions<JWTOptions>
{
private const string _cofigurationSectionName = "JWTOptions"; // From appsettings.json
#region Using ctor DI to access configuration
private readonly IConfiguration _configuration;
public JWTOptionsSetup(IConfiguration configuration)
{
_configuration = configuration;
}
#endregion
public void Configure(JWTOptions options)
{
_configuration.GetSection(_cofigurationSectionName).Bind(options);
}
}
using API.Options.Database;
using API.Options.JWT;
using API.SeedDatabaseHelper;
using Application.Behaviors;
using FluentValidation;
using MediatR;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Persistence.Context;
var builder = WebApplication.CreateBuilder(args);
......@@ -65,14 +68,53 @@ builder.Services.AddControllers()
.AddApplicationPart(Presentation.AssemblyReference.Assembly);
#endregion
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
#region Swagger with JWT authorization
builder.Services.AddSwaggerGen(opt =>
{
opt.SwaggerDoc("v1", new OpenApiInfo { Title = "MyAPI", Version = "v1" });
opt.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please enter token",
Name = "Authorization",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT",
Scheme = "bearer"
});
opt.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type=ReferenceType.SecurityScheme,
Id="Bearer"
}
},
new string[]{}
}
});
});
#endregion
#region Authentication options
builder.Services.ConfigureOptions<JWTOptionsSetup>();
builder.Services.ConfigureOptions<JWTBearerOptionsSetup>();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer();
#endregion
var app = builder.Build();
#region Seed database
await SeedHelper.Seed(app);
await SeedAdminUserHelper.Seed(app);
#endregion
// Configure the HTTP request pipeline.
......@@ -84,6 +126,8 @@ if (app.Environment.IsDevelopment())
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
......
using Domain.Entities.Identity.UserRoles;
using Domain.Entities.Medicals.Medicines.MedicineFormValues;
using Domain.Entities.People.Doctors.Shared.Constants.DoctorStatusValues;
using Domain.Entities.People.Employees.Relations.EmployeeFamilyMembers.FamilyRoleValues;
using Domain.Entities.People.Shared.GenderValues;
using Persistence.SeedDatabase;
using Persistence.SeedDatabase.AdminUser;
namespace API.SeedDatabaseHelper;
public class SeedAdminUserHelper
{
public static async Task Seed(IApplicationBuilder applicationBuilder)
{
using (var serviceScope = applicationBuilder.ApplicationServices.CreateScope())
{
var seedAdminUser = serviceScope.ServiceProvider.GetRequiredService<ISeedAdminUser>();
await seedAdminUser.Seed();
}
}
}
using Domain.Entities.Medicals.Medicines.MedicineFormValues;
using Domain.Entities.Identity.UserRoles;
using Domain.Entities.Medicals.Medicines.MedicineFormValues;
using Domain.Entities.People.Doctors.Shared.Constants.DoctorStatusValues;
using Domain.Entities.People.Employees.Relations.EmployeeFamilyMembers.FamilyRoleValues;
using Domain.Entities.People.Shared.GenderValues;
......@@ -23,6 +24,9 @@ public class SeedHelper
var seedMedicineForms = serviceScope.ServiceProvider.GetRequiredService<ISeed<MedicineForm>>();
await seedMedicineForms.Seed();
var seedUserRoles = serviceScope.ServiceProvider.GetRequiredService<ISeed<Role>>();
await seedUserRoles.Seed();
}
}
}
......@@ -15,5 +15,10 @@
"MaxRetryCount": 3,
"CommandTimeout": 30,
"EnableDetailedErrors": true
},
"JWTOptions": {
"SecretKey": "MoHafez11@SecretKeyAPIVeryLongSecretKeyMoHafez11@SecretKeyAPIVeryLongSecretKey",
"Audience": "clinics-front-end.hiast.edy.sy",
"Issuer": "clinics-back-end.hiast.edy.sy"
}
}
using Domain.Entities.Identity.Users;
namespace Application.Abstractions.JWT;
public interface IJWTProvider
{
string Generate(User user);
}
using Application.Abstractions.CQRS.Commands;
namespace Application.Users.Commands.Login;
public class LoginCommand : ICommand<string>
{
public string UserName { get; set; } = null!;
public string Password { get; set; } = null!;
}
using Application.Abstractions.CQRS.Commands;
using Application.Abstractions.JWT;
using Domain.Entities.Identity.Users;
using Domain.Errors;
using Domain.Repositories;
using Domain.Shared;
using Domain.UnitOfWork;
namespace Application.Users.Commands.Login;
public class LoginCommandHandler : CommandHandlerBase<LoginCommand, string>
{
#region CTOR DI
private readonly IUserRepository _userRepository;
private readonly IJWTProvider _jwtProvider;
public LoginCommandHandler(IUnitOfWork unitOfWork, IUserRepository userRepository, IJWTProvider jwtProvider) : base(unitOfWork)
{
_userRepository = userRepository;
_jwtProvider = jwtProvider;
}
#endregion
public override async Task<Result<string>> HandleHelper(LoginCommand request, CancellationToken cancellationToken)
{
#region 1. Check username and password are correct
Result<User?> loginResult = await _userRepository.VerifyPasswordAsync(request.UserName, request.Password);
if (loginResult.IsFailure)
return Result.Failure<string>(loginResult.Error); // Not found username
if (loginResult.Value is null) // Invalid password
return Result.Failure<string>(IdentityErrors.PasswordMismatch);
#endregion
#region 2. Generate JWT
User user = loginResult.Value!;
string token = _jwtProvider.Generate(user);
#endregion
return Result.Success<string>(token);
}
}
using Domain.Primitives;
namespace Domain.Entities.Identity.UserRoles;
public sealed class Role : Entity
{
#region Private ctor
private Role(int id) : base(id)
{
}
private Role(int id, string name) : base(id)
{
Name = name;
}
#endregion
#region Properties
public string Name { get; private set; } = null!;
#endregion
#region Methods
#region Static factory
internal static Role Create(int id, string name)
{
return new Role(id, name);
}
#endregion
#endregion
}
namespace Domain.Entities.Identity.UserRoles;
public static class Roles
{
#region Constant values
public static int Count => 3;
public const string AdminName = "admin";
public const string DoctorName = "doctor";
public const string ReceptionistName = "receptionist";
public static Role Admin => Role.Create(1, AdminName);
public static Role Doctor => Role.Create(2, DoctorName);
public static Role Receptionist => Role.Create(3, ReceptionistName);
public static List<Role> GetAll()
{
List<Role> roles = new();
roles.Add(Admin);
roles.Add(Doctor);
roles.Add(Receptionist);
return roles;
}
#endregion
}
using Domain.Entities.Identity.UserRoles;
using Domain.Entities.People.Doctors;
using Domain.Primitives;
using Domain.Shared;
namespace Domain.Entities.Identity.Users;
public sealed class DoctorUser : Entity
{
#region Private ctor
private DoctorUser(int id) : base(id)
{
}
private DoctorUser(int id, User user, Doctor doctor) : base(id)
{
User = user;
Doctor = doctor;
}
#endregion
#region Properties
public User User { get; private set; } = null!;
public Doctor Doctor { get; private set; } = null!;
#endregion
#region Methods
#region Static factory
public static Result<DoctorUser> Create (
string username, string hashedPassword, // User details
string firstName, string middleName, string lastName // Doctor details
)
{
Result<Doctor> doctorResult = Doctor.Create(firstName, middleName, lastName);
if (doctorResult.IsFailure)
return Result.Failure<DoctorUser>(doctorResult.Error);
Result<User> userResult = User.Create(username, hashedPassword, Roles.Doctor.Name);
if (userResult.IsFailure)
return Result.Failure<DoctorUser>(userResult.Error);
return new DoctorUser(0, userResult.Value, doctorResult.Value);
}
#endregion
#endregion
}
using Domain.Entities.Identity.UserRoles;
using Domain.Entities.People.Shared;
using Domain.Primitives;
using Domain.Shared;
namespace Domain.Entities.Identity.Users;
public sealed class ReceptionistUser : Entity
{
#region Private ctor
private ReceptionistUser(int id) : base(id)
{
}
private ReceptionistUser(int id, User user, PersonalInfo personalInfo) : base(id)
{
User = user;
PersonalInfo = personalInfo;
}
#endregion
#region Properties
public User User { get; private set; } = null!;
public PersonalInfo PersonalInfo { get; private set; } = null!;
#endregion
#region Methods
#region Static factory
public static Result<ReceptionistUser> Create(
string userName, string hashedPassword,
string firstName, string middleName, string lastName
)
{
Result<PersonalInfo> personalInfoResult = PersonalInfo.Create(firstName, middleName, lastName);
if (personalInfoResult.IsFailure)
return Result.Failure<ReceptionistUser>(personalInfoResult.Error);
Result<User> userResult = User.Create(userName, hashedPassword, Roles.Receptionist.Name);
if (userResult.IsFailure)
return Result.Failure<ReceptionistUser>(userResult.Error);
return new ReceptionistUser(0, userResult.Value, personalInfoResult.Value);
}
#endregion
#endregion
}
using Domain.Entities.Identity.UserRoles;
using Domain.Errors;
using Domain.Primitives;
using Domain.Shared;
namespace Domain.Entities.Identity.Users;
public sealed class User : Entity
{
#region Private ctor
private User(int id) : base(id)
{
}
private User(int id, string userName, string hashedPassword, Role role) : base(id)
{
UserName = userName;
HashedPassword = hashedPassword;
Role = role;
}
#endregion
#region Properties
public string UserName { get; private set; } = null!;
public string HashedPassword { get; private set; } = null!;
public Role Role { get; private set; } = null!;
#endregion
#region Methods
public static Result<User> Create(string userName, string hashedPassword, string role)
{
if (userName is null || hashedPassword is null || role is null)
{
return Result.Failure<User>(DomainErrors.InvalidValuesError);
}
#region Check role
Result<Role> selectedRole = Result.Failure<Role>(IdentityErrors.InvalidRole);
List<Role> roles = Roles.GetAll();
foreach (Role roleItem in roles)
{
if (roleItem.Name == role)
selectedRole = roleItem;
}
if (selectedRole.IsFailure)
return Result.Failure<User>(selectedRole.Error);
#endregion
return new User(0, userName, hashedPassword, selectedRole.Value);
}
#endregion
}
using Domain.Shared;
namespace Domain.Errors;
public static class IdentityErrors
{
public static Error InvalidRole => new("Identity.InvalidRole", "Role specified for user is invalid");
public static Error NotFound => new("Identity.NotFound", "المستخدم غير مسجّل في النظام");
public static Error PasswordMismatch => new("Identity.PasswordMismatch", "كلمة السر غير صحيحة");
}
using Domain.Entities.Identity.Users;
using Domain.Repositories.Base;
using Domain.Shared;
namespace Domain.Repositories;
public interface IUserRepository : IRepository<User>
{
public Task<Result<User>> GetByUserNameFullAsync(string userName);
public Task<Result<User?>> VerifyPasswordAsync(string userName, string password);
}
using Domain.Entities.Identity.UserRoles;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Persistence.Configurations.Identity.Roles;
public class RoleConfiguration : IEntityTypeConfiguration<Role>
{
public void Configure(EntityTypeBuilder<Role> builder)
{
builder.ToTable(nameof(Role));
builder.Property(role => role.Id).ValueGeneratedNever();
builder.Property(role => role.Name)
.HasMaxLength(50);
builder.HasIndex(role => role.Name)
.IsUnique();
}
}
using Domain.Entities.Identity.Users;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Persistence.Configurations.Identity.Users;
public class DoctorUserConfiguration : IEntityTypeConfiguration<DoctorUser>
{
public void Configure(EntityTypeBuilder<DoctorUser> builder)
{
builder.ToTable(nameof(DoctorUser));
builder.Property(doctorUser => doctorUser.Id).ValueGeneratedOnAdd();
builder.HasOne(doctorUser => doctorUser.User)
.WithOne()
.HasForeignKey<DoctorUser>(doctorUser => doctorUser.Id)
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(doctorUser => doctorUser.Doctor)
.WithOne()
.HasForeignKey<DoctorUser>("DoctorId")
.OnDelete(DeleteBehavior.NoAction);
}
}
using Domain.Entities.Identity.Users;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Persistence.Configurations.Identity.Users;
public class ReceptionistUserConfiguration : IEntityTypeConfiguration<ReceptionistUser>
{
public void Configure(EntityTypeBuilder<ReceptionistUser> builder)
{
builder.ToTable(nameof(ReceptionistUser));
builder.Property(receptionistUser => receptionistUser.Id).ValueGeneratedOnAdd();
builder.HasOne(receptionistUser => receptionistUser.User)
.WithOne()
.HasForeignKey<ReceptionistUser>(receptionistUser => receptionistUser.Id)
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(receptionistUser => receptionistUser.PersonalInfo)
.WithOne()
.HasForeignKey<ReceptionistUser>("PersonalInfoId")
.OnDelete(DeleteBehavior.NoAction);
}
}
using Domain.Entities.Identity.Users;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Persistence.Configurations.Identity.Users;
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable(nameof(User));
builder.HasIndex(user => user.UserName)
.IsUnique();
builder.HasOne(user => user.Role)
.WithMany()
.HasForeignKey("RoleId");
}
}
namespace Persistence.Identity.Authentication.JWT;
// Using option pattern
public class JWTOptions
{
public string Issuer { get; init; } = null!;
public string Audience { get; init; } = null!;
public string SecretKey { get; init; } = null!;
}
using Application.Abstractions.JWT;
using Domain.Entities.Identity.Users;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace Persistence.Identity.Authentication.JWT;
public sealed class JWTProvider : IJWTProvider
{
private readonly JWTOptions _options;
public JWTProvider(IOptions<JWTOptions> options)
{
_options = options.Value;
}
public string Generate(User user)
{
var claims = new Claim[]
{
new(ClaimTypes.Name, user.UserName),
new(ClaimTypes.Role, user.Role.Name)
};
var signingCredentials = new SigningCredentials(
new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_options.SecretKey)),
SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
_options.Issuer,
_options.Audience,
claims,
null,
DateTime.UtcNow.AddDays(30),
signingCredentials);
var tokenValue = new JwtSecurityTokenHandler()
.WriteToken(token);
return tokenValue;
}
}
namespace Persistence.Identity.PasswordsHashing;
public interface IPasswordHasher
{
public string Hash(string password);
public bool Verify(string password, string passwordHash);
}
using System.Security.Cryptography;
namespace Persistence.Identity.PasswordsHashing;
public class PasswordHasher : IPasswordHasher
{
#region Hashing
// Salting is adding a random number to the hash
// to ensure that users with same passwords have
// different hash values.
// Best pactice is to use 128 bits = 16 bytes
private const int SaltSize = 16;
// Output size
// Best practice is to use 256 bits
private const int HashSize = 32;
private const int Iterations = 100000;
private readonly HashAlgorithmName Algorithm = HashAlgorithmName.SHA512;
public string Hash(string password)
{
byte[] salt = RandomNumberGenerator.GetBytes(SaltSize);
byte[] hash = Rfc2898DeriveBytes.Pbkdf2(password, salt, Iterations, Algorithm, HashSize);
// Pdkdf ~ Password Based Key Derivation Function
return $"{Convert.ToHexString(hash)}-{Convert.ToHexString(salt)}";
}
#endregion
#region Verification
public bool Verify(string password, string passwordHash)
{
string[] parts = passwordHash.Split('-');
byte[] hash = Convert.FromHexString(parts[0]);
byte[] salt = Convert.FromHexString(parts[1]);
byte[] inputHash = Rfc2898DeriveBytes.Pbkdf2(password, salt, Iterations, Algorithm, HashSize);
return CryptographicOperations.FixedTimeEquals(hash, inputHash);
}
#endregion
}
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Persistence.Migrations
{
/// <inheritdoc />
public partial class Add_Identity : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Role",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Role", x => x.Id);
});
migrationBuilder.CreateTable(
name: "User",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
UserName = table.Column<string>(type: "nvarchar(450)", nullable: false),
HashedPassword = table.Column<string>(type: "nvarchar(max)", nullable: false),
RoleId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_User", x => x.Id);
table.ForeignKey(
name: "FK_User_Role_RoleId",
column: x => x.RoleId,
principalTable: "Role",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "DoctorUser",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false),
DoctorId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DoctorUser", x => x.Id);
table.ForeignKey(
name: "FK_DoctorUser_Doctor_DoctorId",
column: x => x.DoctorId,
principalTable: "Doctor",
principalColumn: "Id");
table.ForeignKey(
name: "FK_DoctorUser_User_Id",
column: x => x.Id,
principalTable: "User",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "ReceptionistUser",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false),
PersonalInfoId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ReceptionistUser", x => x.Id);
table.ForeignKey(
name: "FK_ReceptionistUser_PersonalInfo_PersonalInfoId",
column: x => x.PersonalInfoId,
principalTable: "PersonalInfo",
principalColumn: "Id");
table.ForeignKey(
name: "FK_ReceptionistUser_User_Id",
column: x => x.Id,
principalTable: "User",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_DoctorUser_DoctorId",
table: "DoctorUser",
column: "DoctorId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ReceptionistUser_PersonalInfoId",
table: "ReceptionistUser",
column: "PersonalInfoId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Role_Name",
table: "Role",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_User_RoleId",
table: "User",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "IX_User_UserName",
table: "User",
column: "UserName",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "DoctorUser");
migrationBuilder.DropTable(
name: "ReceptionistUser");
migrationBuilder.DropTable(
name: "User");
migrationBuilder.DropTable(
name: "Role");
}
}
}
......@@ -22,6 +22,87 @@ namespace Persistence.Migrations
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Domain.Entities.Identity.UserRoles.Role", b =>
{
b.Property<int>("Id")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("Role", (string)null);
});
modelBuilder.Entity("Domain.Entities.Identity.Users.DoctorUser", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("DoctorId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("DoctorId")
.IsUnique();
b.ToTable("DoctorUser", (string)null);
});
modelBuilder.Entity("Domain.Entities.Identity.Users.ReceptionistUser", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("PersonalInfoId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("PersonalInfoId")
.IsUnique();
b.ToTable("ReceptionistUser", (string)null);
});
modelBuilder.Entity("Domain.Entities.Identity.Users.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("HashedPassword")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("RoleId")
.HasColumnType("int");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.HasIndex("UserName")
.IsUnique();
b.ToTable("User", (string)null);
});
modelBuilder.Entity("Domain.Entities.Medicals.Diseases.Disease", b =>
{
b.Property<int>("Id")
......@@ -624,6 +705,55 @@ namespace Persistence.Migrations
b.ToTable("EmployeeEmployee");
});
modelBuilder.Entity("Domain.Entities.Identity.Users.DoctorUser", b =>
{
b.HasOne("Domain.Entities.People.Doctors.Doctor", "Doctor")
.WithOne()
.HasForeignKey("Domain.Entities.Identity.Users.DoctorUser", "DoctorId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.HasOne("Domain.Entities.Identity.Users.User", "User")
.WithOne()
.HasForeignKey("Domain.Entities.Identity.Users.DoctorUser", "Id")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.Navigation("Doctor");
b.Navigation("User");
});
modelBuilder.Entity("Domain.Entities.Identity.Users.ReceptionistUser", b =>
{
b.HasOne("Domain.Entities.Identity.Users.User", "User")
.WithOne()
.HasForeignKey("Domain.Entities.Identity.Users.ReceptionistUser", "Id")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.HasOne("Domain.Entities.People.Shared.PersonalInfo", "PersonalInfo")
.WithOne()
.HasForeignKey("Domain.Entities.Identity.Users.ReceptionistUser", "PersonalInfoId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.Navigation("PersonalInfo");
b.Navigation("User");
});
modelBuilder.Entity("Domain.Entities.Identity.Users.User", b =>
{
b.HasOne("Domain.Entities.Identity.UserRoles.Role", "Role")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Role");
});
modelBuilder.Entity("Domain.Entities.Medicals.Medicines.Medicine", b =>
{
b.HasOne("Domain.Entities.Medicals.Medicines.MedicineFormValues.MedicineForm", "MedicineForm")
......
......@@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<PrivateAssets>all</PrivateAssets>
......
......@@ -18,7 +18,8 @@ public static class SpecificationEvaluator
queryable = queryable.Where(specification.Criteria);
}
specification.IncludeExpressions.Aggregate(
queryable = specification.IncludeExpressions.Aggregate(
queryable,
(current, includeExpression) =>
current.Include(includeExpression)
......
using Domain.Entities.Identity.Users;
using Persistence.Repositories.Specifications.Base;
using System.Linq.Expressions;
namespace Persistence.Repositories.Users;
public class FullUserSpecification : Specification<User>
{
public FullUserSpecification(Expression<Func<User, bool>>? criteria) : base(criteria)
{
AddInclude(user => user.Role);
}
}
using Domain.Entities.Identity.Users;
using Domain.Errors;
using Domain.Repositories;
using Domain.Shared;
using Microsoft.EntityFrameworkCore;
using Persistence.Context;
using Persistence.Identity.PasswordsHashing;
using Persistence.Repositories.Base;
namespace Persistence.Repositories.Users;
public class UserRepository : Repositroy<User>, IUserRepository
{
#region Ctor DI
private readonly IPasswordHasher _passwordHasher;
public UserRepository(ClinicsDbContext context, IPasswordHasher passwordHasher) : base(context)
{
_passwordHasher = passwordHasher;
}
#endregion
#region Create method
public override Task<Result<User>> CreateAsync(User entity)
{
_context.Entry(entity.Role).State = EntityState.Unchanged;
return base.CreateAsync(entity);
}
#endregion
#region Get by username
public async Task<Result<User>> GetByUserNameFullAsync(string userName)
{
var query = ApplySpecification(new FullUserSpecification(user => user.UserName == userName));
var result = await query.ToListAsync();
if (result.Count == 0)
{
return Result.Failure<User>(IdentityErrors.NotFound);
}
return result.First();
}
#endregion
#region Verify password
public async Task<Result<User?>> VerifyPasswordAsync(string userName, string password)
{
var userResult = await GetByUserNameFullAsync(userName);
if (userResult.IsFailure)
return Result.Failure<User?>(userResult.Error);
if (!_passwordHasher.Verify(password, userResult.Value.HashedPassword))
return Result.Success<User?>(null);
return Result.Success<User?>(userResult.Value);
}
#endregion
}
namespace Persistence.SeedDatabase.AdminUser;
public interface ISeedAdminUser
{
public Task Seed();
}

using Domain.Entities.Identity.UserRoles;
using Domain.Entities.Identity.Users;
using Domain.Exceptions.InvalidValue;
using Domain.Shared;
using Microsoft.EntityFrameworkCore;
using Persistence.Context;
using Persistence.Identity.PasswordsHashing;
namespace Persistence.SeedDatabase.AdminUser;
public class SeedAdminUser : ISeedAdminUser
{
#region CTOR DI
private readonly ClinicsDbContext _context;
private readonly IPasswordHasher _passwordHasher;
public SeedAdminUser(ClinicsDbContext context, IPasswordHasher passwordHasher)
{
_context = context;
_passwordHasher = passwordHasher;
}
#endregion
public async Task Seed()
{
DbSet<User> users = _context.Set<User>();
Result<User> adminUserResult = User.Create(
"admin",
_passwordHasher.Hash("123"),
Roles.Admin.Name
);
if (adminUserResult.IsFailure)
throw new Exception("Unable to seed admin user");
if (users.Include(user => user.Role).Where(user => user.Role == Roles.Admin).ToList().Count != 1)
{
var adminUser = adminUserResult.Value;
_context.Entry(adminUser.Role).State = EntityState.Unchanged;
users.Add(adminUserResult.Value);
await _context.SaveChangesAsync();
}
}
}
using Domain.Entities.Identity.UserRoles;
using Microsoft.EntityFrameworkCore;
using Persistence.Context;
namespace Persistence.SeedDatabase;
public class UserRoles : ISeed<Role>
{
#region CTOR DI
private readonly ClinicsDbContext _context;
public UserRoles(ClinicsDbContext context)
{
_context = context;
}
#endregion
public async Task Seed()
{
DbSet<Role> roles = _context.Set<Role>();
var current = await roles.ToListAsync();
// TODO: perform deep check on all seed operations
if (current.Count != Roles.Count)
{
roles.RemoveRange(current);
roles.Add(Roles.Admin);
roles.Add(Roles.Doctor);
roles.Add(Roles.Receptionist);
await _context.SaveChangesAsync();
}
}
}
using Application.Employees.Commands.AttachFamilyMemberToEmployee;
using Application.Employees.Commands.CreateEmployee;
using Domain.Entities.Identity.UserRoles;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Presentation.Controllers.Base;
......@@ -16,7 +18,7 @@ public class EmployeesController : ApiController
}
#endregion
[Authorize(Roles = Roles.AdminName)]
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateEmployeeCommand command)
{
......@@ -25,7 +27,7 @@ public class EmployeesController : ApiController
return HandleFailure(result);
return Created();
}
[Authorize(Roles = Roles.DoctorName)]
[HttpPut("FamilyMembers")]
public async Task<IActionResult> AttachFamilyMember([FromBody] AttachFamilyMemberToEmployeeCommand command)
{
......
using Application.Users.Commands.Login;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Presentation.Controllers.Base;
namespace Presentation.Controllers;
[Route("api/Users")]
public class UsersController : ApiController
{
#region CTOR DI
public UsersController(ISender sender) : base(sender)
{
}
#endregion
[HttpPost]
public async Task<IActionResult> LoginUser([FromBody] LoginCommand command)
{
var result = await _sender.Send(command);
if (result.IsFailure)
return HandleFailure(result);
return Ok(result.Value);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment