Trying to integrate 2FA and messing it up

This commit is contained in:
OlgunR 2024-08-14 10:34:57 +02:00
parent 31dde9f33c
commit 8f54854822
33 changed files with 1312 additions and 35 deletions

View File

@ -1,4 +1,4 @@
namespace Project.Application.DTOs.Auth
{
public record LoginDto(string Username, string Password);
public record LoginDto(string Email, string Password);
}

View File

@ -2,6 +2,8 @@
{
public class CreatingUserDto
{
public string Email { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }

View File

@ -4,6 +4,8 @@
{
public int Id { get; set; }
public string Email { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }

View File

@ -4,11 +4,14 @@
{
public int Id { get; set; }
public string Email { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ReadingRoleDto? Role { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace Project.Application.DTOs.TwoFactorAuth
{
public class TwoFactorSetupDto
{
public string SecretKey { get; set; }
public string QrCodeImageUrl { get; set; }
public string ManualEntryKey { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace Project.Application.DTOs.TwoFactorAuth
{
public class TwoFactorVerificationDto
{
public string Email { get; set; }
public string Code { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using Project.Application.DTOs.TwoFactorAuth;
namespace Project.Application.Interfaces
{
public interface ITwoFactorAuthService
{
// GENERATE TWO FACTOR AUTH SETUP
public Task<TwoFactorSetupDto> GenerateSetupCodeAsync(string userEmail);
// VALIDATE OTP
public Task<bool> ValidateCodeAsync(string userEmail, string code);
//// SAVE SECRET KEY
//public Task SaveSecretKeyAsync(string userEmail, string userSecretKey);
// GET SECRET KEY
public Task<string> GetSecretKeyAsync(string userEmail);
}
}

View File

@ -15,8 +15,8 @@ namespace Project.Application.Interfaces
// READ BY ID
Task<ReadingUserDto> GetByIdAsync(int id);
// READ BY USERNAME
Task<ReadingUserDto> GetByUsernameAsync(string username);
// READ BY EMAIL
Task<ReadingUserDto> GetByEmailAsync(string email);
// UPDATE
Task<bool> UpdateUserAsync(UpdatingUserDto updatingUserDto);

View File

@ -1,6 +1,7 @@
using AutoMapper;
using Project.Application.DTOs.Incoming;
using Project.Application.DTOs.Outgoing;
using Project.Application.DTOs.TwoFactorAuth;
using Project.Domain.Entities;
namespace Project.Application.MappingProfiles

View File

@ -0,0 +1,9 @@
namespace Project.Application.Options
{
public class AuthOptions
{
public required string ApiKey { get; init; }
public required string Issuer { get; init; }
public required string SecretKey { get; init; }
}
}

View File

@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GoogleAuthenticator" Version="3.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.6" />

View File

@ -1,5 +1,4 @@
using Project.Application.Interfaces;
using Project.Domain.Entities;
using Project.Infrastructure.Interfaces;
namespace Project.Application.Services
@ -8,19 +7,45 @@ namespace Project.Application.Services
{
// FIELDS FOR CTOR
private IUserRepository _userRepository;
private readonly ITwoFactorAuthService _twoFactorAuthService;
// CTOR
public AuthService(IUserRepository userRepository)
public AuthService(IUserRepository userRepository, ITwoFactorAuthService twoFactorAuthService)
{
_userRepository = userRepository;
_twoFactorAuthService = twoFactorAuthService;
}
// AUTHENTICATE
public async Task<bool> ValidateAsync(string username, string password)
public async Task<bool> ValidateAsync(string email, string password)
{
var user = await _userRepository.GetByUsernameAsync(username);
var user = await _userRepository.GetByEmailAsync(email);
return user?.Password == password;
if (user == null || user.Password != password)
{
return false;
}
// Check if 2FA is enabled
if (!string.IsNullOrEmpty(user.SecretKey))
{
return false; // 2FA is enabled and additional validation is required
}
return true;
}
// VALIDATE TWO FACTOR AUTHENTICATION
public async Task<bool> ValidateTwoFactorAsync(string email, string code)
{
var user = await _userRepository.GetByEmailAsync(email);
if (user == null || string.IsNullOrEmpty(user.SecretKey))
{
return false;
}
return await _twoFactorAuthService.ValidateCodeAsync(user.Email, code);
}
}
}

View File

@ -0,0 +1,97 @@
using Google.Authenticator;
using Microsoft.Extensions.Options;
using Project.Application.DTOs.TwoFactorAuth;
using Project.Application.Interfaces;
using Project.Application.Options;
using Project.Infrastructure.Interfaces;
namespace Project.Application.Services
{
public class TwoFactorAuthService : ITwoFactorAuthService
{
// FIELDS FOR CTOR
private readonly TwoFactorAuthenticator _twoFactorAuthenticator;
private readonly ITwoFactorAuthRepository _twoFactorAuthRepository;
private readonly string _issuer;
// CTOR
public TwoFactorAuthService(IOptions<AuthOptions> options, ITwoFactorAuthRepository twoFactorAuthRepository)
{
_twoFactorAuthenticator = new TwoFactorAuthenticator();
_twoFactorAuthRepository = twoFactorAuthRepository;
_issuer = options.Value.Issuer;
}
//public void TwoFactorAuthenticatorMethods()
//{
// _twoFactorAuthenticator.Equals();
// _twoFactorAuthenticator.GeneratePINAtInterval();
// _twoFactorAuthenticator.GenerateSetupCode();
// _twoFactorAuthenticator.GetCurrentPIN();
// _twoFactorAuthenticator.GetCurrentPINs();
// _twoFactorAuthenticator.GetHashCode();
// _twoFactorAuthenticator.GetType();
// _twoFactorAuthenticator.ToString();
// _twoFactorAuthenticator.ValidateTwoFactorPIN();
//}
// GENERATE TWO FACTOR AUTH SETUP
public async Task<TwoFactorSetupDto> GenerateSetupCodeAsync(string userEmail)
{
var userSecretKey = Guid.NewGuid().ToString();
var setupInfo = _twoFactorAuthenticator.GenerateSetupCode(
_issuer, // Envelope.Configuration.ApplicationName
userEmail, // user.Email
userSecretKey,
false,
10);
string manualEntryKey = setupInfo.ManualEntryKey;
string qrCodeImageUrl = setupInfo.QrCodeSetupImageUrl;
await _twoFactorAuthRepository.SaveSecretKeyAsync(userEmail, userSecretKey);
return new TwoFactorSetupDto
{
SecretKey = userSecretKey,
QrCodeImageUrl = qrCodeImageUrl,
ManualEntryKey = manualEntryKey
};
}
// VALIDATE OTP
public async Task<bool> ValidateCodeAsync(string userEmail, string userInputCode)
{
var secretKey = await _twoFactorAuthRepository.GetSecretKeyAsync(userEmail);
if (string.IsNullOrEmpty(secretKey))
{
throw new InvalidOperationException("Secret key not found!");
}
return _twoFactorAuthenticator.ValidateTwoFactorPIN(secretKey, userInputCode);
}
//// SAVE SECRET KEY
//public async Task SaveSecretKeyAsync(string userEmail, string userSecretKey)
//{
// var user = await _twoFactorAuthRepository.GetUserByEmailAsync(userEmail);
// if (user == null)
// {
// throw new InvalidOperationException("User not found!");
// }
// await _twoFactorAuthRepository.SaveSecretKeyAsync(userEmail, userSecretKey);
//}
// GET SECRET KEY
public async Task<string> GetSecretKeyAsync(string userEmail)
{
var secretKey = await _twoFactorAuthRepository.GetSecretKeyAsync(userEmail);
return secretKey;
}
}
}

View File

@ -54,10 +54,10 @@ namespace Project.Application.Services
return readDto;
}
// READ BY USERNAME
public async Task<ReadingUserDto> GetByUsernameAsync(string username)
// READ BY EMAIL
public async Task<ReadingUserDto> GetByEmailAsync(string email)
{
var user = await _userRepository.GetByUsernameAsync(username);
var user = await _userRepository.GetByEmailAsync(email);
var readDto = _mapper.Map<ReadingUserDto>(user);
return readDto;
}

View File

@ -6,11 +6,15 @@ namespace Project.Domain.Entities
[Table("USER", Schema = "dbo")]
public class User
{
[Column("ID")]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("ID")]
public int Id { get; set; }
[Required]
[Column("EMAIL")]
public string Email { get; set; }
[Required]
[Column("USER_NAME")]
public string UserName { get; set; }
@ -27,9 +31,13 @@ namespace Project.Domain.Entities
[Column("PASSWORD")]
public string Password { get; init; }
[Column("ROLE_ID")]
public int RoleId { get; set; }
[ForeignKey("RoleId")]
[ForeignKey("ROLE")]
public Role? Role { get; set; }
[Column("SECRET_KEY")]
public string SecretKey { get; set; }
}
}

View File

@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GoogleAuthenticator" Version="3.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.6" />

View File

@ -0,0 +1,16 @@
using Project.Domain.Entities;
namespace Project.Infrastructure.Interfaces
{
public interface ITwoFactorAuthRepository
{
// GET USER BY EMAIL
Task<User> GetUserByEmailAsync(string email);
// GET SECRET KEY
Task<string> GetSecretKeyAsync(string email);
// SAVE SECRET KEY
Task<bool> SaveSecretKeyAsync(string email, string secretKey);
}
}

View File

@ -13,8 +13,8 @@ namespace Project.Infrastructure.Interfaces
// READ BY ID
Task<User?> GetByIdAsync(int id);
// READ BY USERNAME
Task<User?> GetByUsernameAsync(string username);
// READ BY EMAIL
Task<User?> GetByEmailAsync(string email);
// UPDATE
Task<bool> UpdateAsync(User user);

View File

@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="GoogleAuthenticator" Version="3.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.6" />

View File

@ -0,0 +1,45 @@
using Microsoft.EntityFrameworkCore;
using Project.Domain.Entities;
using Project.Infrastructure.Interfaces;
namespace Project.Infrastructure.Repositories
{
public class TwoFactorAuthRepository : ITwoFactorAuthRepository
{
// FIELDS FOR CTOR
private readonly ApplicationDbContext _context;
// CTOR
public TwoFactorAuthRepository(ApplicationDbContext context)
{
_context = context;
}
// GET USER BY EMAIL
public async Task<User> GetUserByEmailAsync(string email)
{
return await _context.Users.FirstAsync(user => user.Email == email);
}
// GET SECRET KEY
public async Task<string> GetSecretKeyAsync(string email)
{
return await _context.Users
.Where(user => user.Email == email)
.Select(user => user.SecretKey)
.FirstOrDefaultAsync();
}
// SAVE SECRET KEY
public async Task<bool> SaveSecretKeyAsync(string email, string secretKey)
{
User user = await _context.Users.FirstAsync(user => user.Email == email);
user.SecretKey = secretKey;
_context.Entry(user).State = EntityState.Modified;
var results = await _context.SaveChangesAsync();
return results > 0;
}
}
}

View File

@ -35,10 +35,10 @@ namespace Project.Infrastructure.Repositories
return await _context.Users.Where(user => user.Id == id).Include(user => user.Role).FirstAsync(); // Role important for authorization
}
// READ BY USERNAME
public async Task<User?> GetByUsernameAsync(string username)
// READ BY EMAIL
public async Task<User?> GetByEmailAsync(string email)
{
return await _context.Users.Include(user => user.Role).FirstOrDefaultAsync(u => u.UserName == username); // Role important for authorization
return await _context.Users.Include(user => user.Role).FirstOrDefaultAsync(u => u.Email == email); // Role important for authorization
}
// UPDATE

View File

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Project.Application.DTOs.Auth;
using Project.Application.DTOs.TwoFactorAuth;
using Project.Application.Interfaces;
using System.Security.Claims;
@ -15,38 +16,49 @@ namespace Project.Web.Controllers
// FIELDS FOR CTOR
private readonly IUserService _userService;
private readonly IAuthService _authService;
private readonly ITwoFactorAuthService _twoFactorAuthService;
// CTOR
public AuthController(IUserService userService, IAuthService authService)
public AuthController(IUserService userService, IAuthService authService, ITwoFactorAuthService twoFactorAuthService)
{
_userService = userService;
_authService = authService;
_twoFactorAuthService = twoFactorAuthService;
}
// LOGIN
[AllowAnonymous]
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginDto login)
public async Task<IActionResult> Login([FromBody] LoginDto loginDto)
{
// Validate login credentials
var isValid = await _authService.ValidateAsync(login.Username, login.Password);
bool isValid = await _authService.ValidateAsync(loginDto.Email, loginDto.Password);
if (!isValid)
{
return Unauthorized();
}
// Validate user
var user = await _userService.GetByUsernameAsync(login.Username);
var user = await _userService.GetByEmailAsync(loginDto.Email);
if (user == null)
{
return Unauthorized(user);
}
// Check if 2FA is required
var secretKey = await _twoFactorAuthService.GetSecretKeyAsync(loginDto.Email);
if (!string.IsNullOrEmpty(secretKey))
{
// Promt for 2FA
return Ok(new { RequiresTwoFactor = true });
}
// Create claims based on the user information
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.Name, user.Email),
new Claim(ClaimTypes.Surname, user.LastName ?? ""),
new Claim(ClaimTypes.GivenName, user.FirstName ?? ""),
new Claim(ClaimTypes.Role, user?.Role?.Name.ToString() ?? "") // role is important for authorization
@ -73,13 +85,65 @@ namespace Project.Web.Controllers
return Ok();
}
[AllowAnonymous]
[HttpPost("verify2fa")]
public async Task<IActionResult> Verify2Fa([FromBody] TwoFactorVerificationDto verifyDto)
{
var isValid = await _twoFactorAuthService.ValidateCodeAsync(verifyDto.Email, verifyDto.Code);
if (!isValid)
{
return Unauthorized();
}
var user = await _userService.GetByEmailAsync(verifyDto.Email);
if (user == null)
{
return Unauthorized();
}
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.Surname, user.LastName ?? ""),
new Claim(ClaimTypes.GivenName, user.FirstName ?? ""),
new Claim(ClaimTypes.Role, user?.Role?.Name ?? "")
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
IsPersistent = true,
AllowRefresh = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(60)
};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties
);
return Ok();
}
// LOGOUT
[HttpPost("logout")]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); // Logout is basically deleting cookies
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return Ok();
}
//// SETUP 2FA
//[AllowAnonymous]
//[HttpPost("setup-2fa")]
//public async Task<IActionResult> SetupTwoFactorAuth([FromBody] SetupTwoFactorDto setupDto)
//{
// var result = await _twoFactorAuthService.GenerateSetupCodeAsync(setupDto.Email);
// return Ok(result);
//}
}
}

View File

@ -0,0 +1,52 @@
using Microsoft.AspNetCore.Mvc;
using Project.Application.DTOs.TwoFactorAuth;
using Project.Application.Interfaces;
namespace Project.Web.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TwoFactorAuthController : ControllerBase
{
// FEILDS FOR CTOR
private readonly ITwoFactorAuthService _twoFactorAuthService;
private readonly IUserService _userService;
// CTOR
public TwoFactorAuthController(ITwoFactorAuthService twoFactorAuthService, IUserService userService)
{
_twoFactorAuthService = twoFactorAuthService;
_userService = userService;
}
// SETUP 2FA
[HttpPost("setup")]
public async Task<IActionResult> Setup([FromBody] string email)
{
var user = await _userService.GetByEmailAsync(email);
if (user == null)
{
return NotFound();
}
var setupInfo = await _twoFactorAuthService.GenerateSetupCodeAsync(email);
return Ok(setupInfo);
}
// VERIFY
[HttpPost("verify")]
public async Task<IActionResult> Verify([FromBody] TwoFactorVerificationDto verifyDto)
{
var isValid = await _twoFactorAuthService.ValidateCodeAsync(verifyDto.Email, verifyDto.Code);
if (!isValid)
{
return Unauthorized();
}
return Ok();
}
}
}

View File

@ -7,7 +7,7 @@ namespace Project.Web.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize(Roles = "Admin")] // Authorize Admins only to use this controller
//[Authorize(Roles = "Admin")] // Authorize Admins only to use this controller
public class UserController : Controller
{
// FIELDS FOR CTOR
@ -85,18 +85,18 @@ namespace Project.Web.Controllers
return Ok(user);
}
// READ BY USERNAME
[HttpGet("username/{username}", Name = "GetUserByUsername")]
// READ BY EMAIL
[HttpGet("email/{email}", Name = "GetUserByEmail")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetUserByUsername(string username)
public async Task<IActionResult> GetUserByEmail(string email)
{
if (string.IsNullOrEmpty(username))
if (string.IsNullOrEmpty(email))
{
return BadRequest("Username connot be empty");
return BadRequest("Email connot be empty");
}
var user = await _userService.GetByUsernameAsync(username);
var user = await _userService.GetByEmailAsync(email);
if(user == null)
{
return NotFound();

View File

@ -0,0 +1,222 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Project.Infrastructure;
#nullable disable
namespace Project.Web.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240813080158_Zehnte")]
partial class Zehnte
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.6")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Project.Domain.Entities.Category", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreationDate")
.HasColumnType("datetime")
.HasColumnName("CREATION_DATE");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("CATEGORY_NAME");
b.HasKey("Id");
b.ToTable("CATEGORY", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.CategoryRole", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("CategoryId")
.HasColumnType("int");
b.Property<int>("RoleId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.HasIndex("RoleId");
b.ToTable("CATEGORY_ROLE", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.Product", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("CategoryId")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("PRODUCT_NAME");
b.Property<decimal>("Price")
.HasColumnType("decimal(18,2)")
.HasColumnName("PRICE");
b.Property<int>("Quantity")
.HasColumnType("int")
.HasColumnName("QUANTITY");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.ToTable("PRODUCT", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.Role", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreationDate")
.HasColumnType("datetime")
.HasColumnName("CREATION_DATE");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("ROLE");
b.HasKey("Id");
b.ToTable("ROLE", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("E-MAIL");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("FIRST_NAME");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("LAST_NAME");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("PASSWORD");
b.Property<int>("RoleId")
.HasColumnType("int");
b.Property<string>("SecretKey")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("SECRET KEY");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("USER_NAME");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("USER", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.CategoryRole", b =>
{
b.HasOne("Project.Domain.Entities.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Project.Domain.Entities.Role", "Role")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Category");
b.Navigation("Role");
});
modelBuilder.Entity("Project.Domain.Entities.Product", b =>
{
b.HasOne("Project.Domain.Entities.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Category");
});
modelBuilder.Entity("Project.Domain.Entities.User", b =>
{
b.HasOne("Project.Domain.Entities.Role", "Role")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Role");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,44 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Project.Web.Migrations
{
/// <inheritdoc />
public partial class Zehnte : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "E-MAIL",
schema: "dbo",
table: "USER",
type: "nvarchar(max)",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "SECRET KEY",
schema: "dbo",
table: "USER",
type: "nvarchar(max)",
nullable: false,
defaultValue: "");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "E-MAIL",
schema: "dbo",
table: "USER");
migrationBuilder.DropColumn(
name: "SECRET KEY",
schema: "dbo",
table: "USER");
}
}
}

View File

@ -0,0 +1,225 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Project.Infrastructure;
#nullable disable
namespace Project.Web.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240813083139_Elfte")]
partial class Elfte
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.6")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Project.Domain.Entities.Category", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreationDate")
.HasColumnType("datetime")
.HasColumnName("CREATION_DATE");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("CATEGORY_NAME");
b.HasKey("Id");
b.ToTable("CATEGORY", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.CategoryRole", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("CategoryId")
.HasColumnType("int");
b.Property<int>("RoleId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.HasIndex("RoleId");
b.ToTable("CATEGORY_ROLE", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.Product", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("CategoryId")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("PRODUCT_NAME");
b.Property<decimal>("Price")
.HasColumnType("decimal(18,2)")
.HasColumnName("PRICE");
b.Property<int>("Quantity")
.HasColumnType("int")
.HasColumnName("QUANTITY");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.ToTable("PRODUCT", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.Role", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreationDate")
.HasColumnType("datetime")
.HasColumnName("CREATION_DATE");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("ROLE");
b.HasKey("Id");
b.ToTable("ROLE", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("EMAIL");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("FIRST_NAME");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("LAST_NAME");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("PASSWORD");
b.Property<int?>("ROLE_ID")
.HasColumnType("int");
b.Property<int>("RoleId")
.HasColumnType("int");
b.Property<string>("SecretKey")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("SECRET_KEY");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("USER_NAME");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("USER", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.CategoryRole", b =>
{
b.HasOne("Project.Domain.Entities.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Project.Domain.Entities.Role", "Role")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Category");
b.Navigation("Role");
});
modelBuilder.Entity("Project.Domain.Entities.Product", b =>
{
b.HasOne("Project.Domain.Entities.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Category");
});
modelBuilder.Entity("Project.Domain.Entities.User", b =>
{
b.HasOne("Project.Domain.Entities.Role", "Role")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Role");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,54 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Project.Web.Migrations
{
/// <inheritdoc />
public partial class Elfte : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "SECRET KEY",
schema: "dbo",
table: "USER",
newName: "SECRET_KEY");
migrationBuilder.RenameColumn(
name: "E-MAIL",
schema: "dbo",
table: "USER",
newName: "EMAIL");
migrationBuilder.AddColumn<int>(
name: "ROLE_ID",
schema: "dbo",
table: "USER",
type: "int",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ROLE_ID",
schema: "dbo",
table: "USER");
migrationBuilder.RenameColumn(
name: "SECRET_KEY",
schema: "dbo",
table: "USER",
newName: "SECRET KEY");
migrationBuilder.RenameColumn(
name: "EMAIL",
schema: "dbo",
table: "USER",
newName: "E-MAIL");
}
}
}

View File

@ -0,0 +1,226 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Project.Infrastructure;
#nullable disable
namespace Project.Web.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240813083456_Zwoelfte")]
partial class Zwoelfte
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.6")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Project.Domain.Entities.Category", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreationDate")
.HasColumnType("datetime")
.HasColumnName("CREATION_DATE");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("CATEGORY_NAME");
b.HasKey("Id");
b.ToTable("CATEGORY", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.CategoryRole", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("CategoryId")
.HasColumnType("int");
b.Property<int>("RoleId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.HasIndex("RoleId");
b.ToTable("CATEGORY_ROLE", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.Product", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("CategoryId")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("PRODUCT_NAME");
b.Property<decimal>("Price")
.HasColumnType("decimal(18,2)")
.HasColumnName("PRICE");
b.Property<int>("Quantity")
.HasColumnType("int")
.HasColumnName("QUANTITY");
b.HasKey("Id");
b.HasIndex("CategoryId");
b.ToTable("PRODUCT", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.Role", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreationDate")
.HasColumnType("datetime")
.HasColumnName("CREATION_DATE");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("ROLE");
b.HasKey("Id");
b.ToTable("ROLE", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("ID");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("EMAIL");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("FIRST_NAME");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("LAST_NAME");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("PASSWORD");
b.Property<int?>("ROLE")
.HasColumnType("int");
b.Property<int>("RoleId")
.HasColumnType("int")
.HasColumnName("ROLE_ID");
b.Property<string>("SecretKey")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("SECRET_KEY");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("USER_NAME");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("USER", "dbo");
});
modelBuilder.Entity("Project.Domain.Entities.CategoryRole", b =>
{
b.HasOne("Project.Domain.Entities.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Project.Domain.Entities.Role", "Role")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Category");
b.Navigation("Role");
});
modelBuilder.Entity("Project.Domain.Entities.Product", b =>
{
b.HasOne("Project.Domain.Entities.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Category");
});
modelBuilder.Entity("Project.Domain.Entities.User", b =>
{
b.HasOne("Project.Domain.Entities.Role", "Role")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Role");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,115 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Project.Web.Migrations
{
/// <inheritdoc />
public partial class Zwoelfte : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_USER_ROLE_RoleId",
schema: "dbo",
table: "USER");
migrationBuilder.DropIndex(
name: "IX_USER_RoleId",
schema: "dbo",
table: "USER");
migrationBuilder.DropColumn(
name: "RoleId",
schema: "dbo",
table: "USER");
migrationBuilder.AlterColumn<int>(
name: "ROLE_ID",
schema: "dbo",
table: "USER",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
migrationBuilder.AddColumn<int>(
name: "ROLE",
schema: "dbo",
table: "USER",
type: "int",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_USER_ROLE_ID",
schema: "dbo",
table: "USER",
column: "ROLE_ID");
migrationBuilder.AddForeignKey(
name: "FK_USER_ROLE_ROLE_ID",
schema: "dbo",
table: "USER",
column: "ROLE_ID",
principalSchema: "dbo",
principalTable: "ROLE",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_USER_ROLE_ROLE_ID",
schema: "dbo",
table: "USER");
migrationBuilder.DropIndex(
name: "IX_USER_ROLE_ID",
schema: "dbo",
table: "USER");
migrationBuilder.DropColumn(
name: "ROLE",
schema: "dbo",
table: "USER");
migrationBuilder.AlterColumn<int>(
name: "ROLE_ID",
schema: "dbo",
table: "USER",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AddColumn<int>(
name: "RoleId",
schema: "dbo",
table: "USER",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateIndex(
name: "IX_USER_RoleId",
schema: "dbo",
table: "USER",
column: "RoleId");
migrationBuilder.AddForeignKey(
name: "FK_USER_ROLE_RoleId",
schema: "dbo",
table: "USER",
column: "RoleId",
principalSchema: "dbo",
principalTable: "ROLE",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
}
}
}

View File

@ -133,6 +133,11 @@ namespace Project.Web.Migrations
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("EMAIL");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("nvarchar(max)")
@ -148,9 +153,18 @@ namespace Project.Web.Migrations
.HasColumnType("nvarchar(max)")
.HasColumnName("PASSWORD");
b.Property<int>("RoleId")
b.Property<int?>("ROLE")
.HasColumnType("int");
b.Property<int>("RoleId")
.HasColumnType("int")
.HasColumnName("ROLE_ID");
b.Property<string>("SecretKey")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("SECRET_KEY");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("nvarchar(max)")

View File

@ -12,33 +12,45 @@ var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Automapper
builder.Services.AddAutoMapper(typeof(BasicDtoMappingProfile).Assembly);
// ProductService, ProductRepository
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
// CategoryService, CategoryRepository
builder.Services.AddScoped<ICategoryService, CategoryService>();
builder.Services.AddScoped<ICategoryRepository, CategoryRepository>();
// UserService, UserRepository
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
// RoleService, RoleRepository
builder.Services.AddScoped<IRoleService, RoleService>();
builder.Services.AddScoped<IRoleRepository, RoleRepository>();
// AuthService
builder.Services.AddScoped<IAuthService, AuthService>();
//builder.Services.AddScoped<IAuthRepository, AuthRepository>();
// TwoFactorAuthService, TwoFactorAuthReposittory
builder.Services.AddScoped<ITwoFactorAuthService, TwoFactorAuthService>();
builder.Services.AddScoped<ITwoFactorAuthRepository, TwoFactorAuthRepository>();
// DatabaseContext
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly("Project.Web"));
});
builder.Services.AddMemoryCache();
// CookieAuth
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
@ -49,6 +61,7 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
options.LogoutPath = "/api/auth/logout";
});
// Authorization
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>

View File

@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GoogleAuthenticator" Version="3.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.6" />