From 29c33d015df0ecb719b68b350bd26693fc7fadba Mon Sep 17 00:00:00 2001 From: Barbara Post Date: Sun, 18 Jan 2026 15:37:36 +0100 Subject: [PATCH] =?UTF-8?q?r=C3=A9cup=C3=A9ration=20de=20classes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BlazorPolicyAuth/BlazorPolicyAuth.csproj | 1 + .../Models/Entities/UserAccount.cs | 5 + .../Models/ViewModels/LoginViewModel.cs | 7 - .../Models/ViewModels/ServiceResponse.cs | 9 ++ .../Models/ViewModels/UserChangePassword.cs | 12 ++ .../Models/ViewModels/UserLogin.cs | 11 ++ .../Models/ViewModels/UserRegister.cs | 14 ++ .../Services/AuthService/AuthService.cs | 140 ++++++++++++++++++ .../Services/AuthService/IAuthService.cs | 15 ++ 9 files changed, 207 insertions(+), 7 deletions(-) delete mode 100644 BlazorPolicyAuth/Models/ViewModels/LoginViewModel.cs create mode 100644 BlazorPolicyAuth/Models/ViewModels/ServiceResponse.cs create mode 100644 BlazorPolicyAuth/Models/ViewModels/UserChangePassword.cs create mode 100644 BlazorPolicyAuth/Models/ViewModels/UserLogin.cs create mode 100644 BlazorPolicyAuth/Models/ViewModels/UserRegister.cs create mode 100644 BlazorPolicyAuth/Services/AuthService/AuthService.cs create mode 100644 BlazorPolicyAuth/Services/AuthService/IAuthService.cs diff --git a/BlazorPolicyAuth/BlazorPolicyAuth.csproj b/BlazorPolicyAuth/BlazorPolicyAuth.csproj index b82a96e..1b2989d 100644 --- a/BlazorPolicyAuth/BlazorPolicyAuth.csproj +++ b/BlazorPolicyAuth/BlazorPolicyAuth.csproj @@ -19,6 +19,7 @@ + diff --git a/BlazorPolicyAuth/Models/Entities/UserAccount.cs b/BlazorPolicyAuth/Models/Entities/UserAccount.cs index 584701f..8504e39 100644 --- a/BlazorPolicyAuth/Models/Entities/UserAccount.cs +++ b/BlazorPolicyAuth/Models/Entities/UserAccount.cs @@ -18,4 +18,9 @@ public class UserAccount [Column("password")] [MaxLength(100)] public string? Password { get; set; } + + public byte[] PasswordHash { get; set; } + public byte[] PasswordSalt { get; set; } + public DateTime DateCreated { get; set; } = DateTime.Now; + public string Role { get; set; } = "User"; } \ No newline at end of file diff --git a/BlazorPolicyAuth/Models/ViewModels/LoginViewModel.cs b/BlazorPolicyAuth/Models/ViewModels/LoginViewModel.cs deleted file mode 100644 index 24bce9d..0000000 --- a/BlazorPolicyAuth/Models/ViewModels/LoginViewModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BlazorPolicyAuth.Models.ViewModels; - -public class LoginViewModel -{ - public string? UserName { get; set; } - public string? Password { get; set; } -} \ No newline at end of file diff --git a/BlazorPolicyAuth/Models/ViewModels/ServiceResponse.cs b/BlazorPolicyAuth/Models/ViewModels/ServiceResponse.cs new file mode 100644 index 0000000..bb171d3 --- /dev/null +++ b/BlazorPolicyAuth/Models/ViewModels/ServiceResponse.cs @@ -0,0 +1,9 @@ +namespace BlazorPolicyAuth.Models.ViewModels +{ + public class ServiceResponse + { + public T? Data { get; set; } + public bool Success { get; set; } = true; + public string Message { get; set; } = string.Empty; + } +} diff --git a/BlazorPolicyAuth/Models/ViewModels/UserChangePassword.cs b/BlazorPolicyAuth/Models/ViewModels/UserChangePassword.cs new file mode 100644 index 0000000..f32f5ec --- /dev/null +++ b/BlazorPolicyAuth/Models/ViewModels/UserChangePassword.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace BlazorPolicyAuth.Models.ViewModels +{ + public class UserChangePassword + { + [Required, StringLength(100, MinimumLength = 5)] + public string Password { get; set; } = string.Empty; + [Compare("Password", ErrorMessage = "The password do not match")] + public string ConfirmPassword { get; set; } = string.Empty; + } +} diff --git a/BlazorPolicyAuth/Models/ViewModels/UserLogin.cs b/BlazorPolicyAuth/Models/ViewModels/UserLogin.cs new file mode 100644 index 0000000..6c20e0a --- /dev/null +++ b/BlazorPolicyAuth/Models/ViewModels/UserLogin.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace BlazorPolicyAuth.Models.ViewModels; + +public class UserLogin +{ + [Required, EmailAddress] + public string UserName { get; set; } = string.Empty; + [Required] + public string Password { get; set; } +} \ No newline at end of file diff --git a/BlazorPolicyAuth/Models/ViewModels/UserRegister.cs b/BlazorPolicyAuth/Models/ViewModels/UserRegister.cs new file mode 100644 index 0000000..ec3dd3f --- /dev/null +++ b/BlazorPolicyAuth/Models/ViewModels/UserRegister.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace BlazorPolicyAuth.Models.ViewModels +{ + public class UserRegister + { + [Required, EmailAddress] + public string Email { get; set; } = string.Empty; + [Required, StringLength(100, MinimumLength = 5)] + public string Password { get; set; } = string.Empty; + [Compare("Password", ErrorMessage = "The password do not match.")] + public string ConfirmPassword { get; set; } = string.Empty; + } +} diff --git a/BlazorPolicyAuth/Services/AuthService/AuthService.cs b/BlazorPolicyAuth/Services/AuthService/AuthService.cs new file mode 100644 index 0000000..6237c9e --- /dev/null +++ b/BlazorPolicyAuth/Services/AuthService/AuthService.cs @@ -0,0 +1,140 @@ + +using BlazorPolicyAuth.Data; +using BlazorPolicyAuth.Models.Entities; +using BlazorPolicyAuth.Models.ViewModels; +using BlazorPolicyAuth.Services.AuthService; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; + +namespace BlazorAuth.Server.Services.AuthService; + +public class AuthService : IAuthService +{ + private readonly AppDbContext _context; + private readonly IConfiguration _configuration; + private readonly IHttpContextAccessor _httpContextAccessor; + + public AuthService(AppDbContext context, IConfiguration configuration, IHttpContextAccessor httpContextAccessor) + { + _context = context; + _configuration = configuration; + _httpContextAccessor = httpContextAccessor; + } + + public async Task> Register(UserAccount user, string password) + { + if (await UserExists(user.UserName)) + { + return new ServiceResponse { Success = false, Message = "User already exist." }; + } + + CreatePasswordHash(password, out byte[] passwordHash, out byte[] passwordSalt); + + user.PasswordHash = passwordHash; + user.PasswordSalt = passwordSalt; + + _context.UserAccounts.Add(user); + await _context.SaveChangesAsync(); + + return new ServiceResponse { Data = user.Id, Message = "Registration successful" }; + } + + public async Task UserExists(string email) + { + if (await _context.UserAccounts.AnyAsync(user => user.UserName.ToLower().Equals(email.ToLower()))) + { + return true; + } + return false; + } + + public async Task> Login(string email, string password) + { + var response = new ServiceResponse(); + var user = await _context.UserAccounts.FirstOrDefaultAsync(u => u.UserName.ToLower().Equals(email.ToLower())); + if (user == null) + { + response.Success = false; + response.Message = "User not found."; + } + else if (!VeriyPasswordHash(password, user.PasswordHash, user.PasswordSalt)) + { + response.Success = false; + response.Message = "Wrong password."; + } + else + { + response.Data = CreateToken(user); + } + + return response; + } + + public async Task> ChangePassword(int userId, string newPassword) + { + var user = await _context.UserAccounts.FindAsync(userId); + if (user == null) + { + return new ServiceResponse { Success = false, Message = "User not found." }; + } + + CreatePasswordHash(newPassword, out byte[] passwordHash, out byte[] passwordSalt); + + user.PasswordHash = passwordHash; + user.PasswordSalt = passwordSalt; + + await _context.SaveChangesAsync(); + + return new ServiceResponse { Data = true, Message = "Password has been changed." }; + } + + public int GetUserId() => int.Parse(_httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier)); + + public string GetUserEmail() => _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.Name); + + public async Task GetUserByEmail(string email) + { + return await _context.UserAccounts.FirstOrDefaultAsync(u => u.UserName.Equals(email)); + } + + private static void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt) + { + using var hmac = new HMACSHA512(); + passwordSalt = hmac.Key; + passwordHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(password)); + } + + private static bool VeriyPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt) + { + using var hmac = new HMACSHA512(passwordSalt); + var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(password)); + return computedHash.SequenceEqual(passwordHash); + } + + private string CreateToken(UserAccount user) + { + var claims = new List + { + new(ClaimTypes.NameIdentifier, user.Id.ToString()), + new(ClaimTypes.Name, user.UserName), + new(ClaimTypes.Role, user.Role) + }; + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("AppSettings:Token").Value)); + + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature); + + var token = new JwtSecurityToken( + claims: claims, + expires: DateTime.Now.AddDays(1), + signingCredentials: creds); + + var jwt = new JwtSecurityTokenHandler().WriteToken(token); + + return jwt; + } +} \ No newline at end of file diff --git a/BlazorPolicyAuth/Services/AuthService/IAuthService.cs b/BlazorPolicyAuth/Services/AuthService/IAuthService.cs new file mode 100644 index 0000000..ce36661 --- /dev/null +++ b/BlazorPolicyAuth/Services/AuthService/IAuthService.cs @@ -0,0 +1,15 @@ +using BlazorPolicyAuth.Models.Entities; +using BlazorPolicyAuth.Models.ViewModels; + +namespace BlazorPolicyAuth.Services.AuthService; + +public interface IAuthService +{ + Task> Register(UserAccount user, string password); + Task UserExists(string email); + Task> Login(string email, string password); + Task> ChangePassword(int userId, string newPassword); + int GetUserId(); + string GetUserEmail(); + Task GetUserByEmail(string email); +} \ No newline at end of file