using API.DBAccess; using API.Models.UserModels; using API.Models.RecipeModels; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using API.Models.ShoppingListModels; namespace API.BusinessLogic { public class UserLogic { private readonly UserDBAccess _dbAccess; private readonly IConfiguration _configuration; public UserLogic(IConfiguration configuration, UserDBAccess dbAccess) { _dbAccess = dbAccess; _configuration = configuration; } public async Task GetUser(int userId) { User user = await _dbAccess.ReadUser(userId); if (user == null || user.Id == 0) { return new ConflictObjectResult(new { message = "Could not find user" }); } return new OkObjectResult(new { user.Id, user.UserName, user.Email }); } public async Task RegisterUser(CreateUserDTO userDTO) { if (!EmailCheck(userDTO.Email)) { return new ConflictObjectResult(new { message = "Invalid email address" }); } if (!PasswordSecurity(userDTO.Password)) { return new ConflictObjectResult(new { message = "Password is not up to the security standard" }); } if (await _dbAccess.UserNameInUse(userDTO.UserName)) { return new ConflictObjectResult(new { message = "Username is already in use." }); } if (await _dbAccess.EmailInUse(userDTO.Email)) { return new ConflictObjectResult(new { message = "Email is already in use." }); } string salt = Guid.NewGuid().ToString(); string hashedPassword = ComputeHash(userDTO.Password, SHA256.Create(), salt); User user = new User { UserName = userDTO.UserName, Email = userDTO.Email, Password = hashedPassword, Salt = salt, Recipes = new List(), ShoppingList = new List(), }; return await _dbAccess.CreateUser(user); } public async Task Login(LoginDTO loginDTO) { var user = await _dbAccess.ReadUserForLogin(loginDTO.EmailUsr); if (user == null || user.Id == 0) { return new ConflictObjectResult(new { message = "Could not find user" }); } string hashedPassword = ComputeHash(loginDTO.Password, SHA256.Create(), user.Salt); if (user.Password == hashedPassword) { var token = GenerateJwtToken(user); user = await UpdateRefreshToken(user); return new OkObjectResult(new { token, user.UserName, user.Id, refreshToken = user.RefreshToken }); } return new ConflictObjectResult(new { message = "Invalid password" }); } public async Task EditProfile(UpdateUserDTO userDTO, int userId) { var profile = await _dbAccess.ReadUser(userId); if (profile == null) { return new ConflictObjectResult(new { message = "User does not exist" }); } if (!EmailCheck(userDTO.Email)) { return new ConflictObjectResult(new { message = "Invalid email address" }); } if (userDTO.Email == "" || userDTO.Email == null) { return new ConflictObjectResult(new { message = "Please enter an email" }); } if (userDTO.UserName == "" || userDTO.UserName == null) { return new ConflictObjectResult(new { message = "Please enter an userName" }); } if (await _dbAccess.UserNameInUse(userDTO.UserName)) { return new ConflictObjectResult(new { message = "Username is already in use." }); } if (await _dbAccess.EmailInUse(userDTO.Email)) { return new ConflictObjectResult(new { message = "Email is already in use." }); } profile.Email = userDTO.Email; profile.UserName = userDTO.UserName; return await _dbAccess.UpdateUser(profile); } public async Task ChangePassword(ChangePasswordDTO passwordDTO, int userId) { var user = await _dbAccess.ReadUser(userId); if (user == null) { return new ConflictObjectResult(new { message = "User does not exist" }); } string hashedPassword = ComputeHash(passwordDTO.OldPassword, SHA256.Create(), user.Salt); if (user.Password != hashedPassword) { return new ConflictObjectResult(new { message = "Old password is incorrect" }); } if (!PasswordSecurity(passwordDTO.NewPassword)) { return new ConflictObjectResult(new { message = "New password is not up to the security standard" }); } string hashedNewPassword = ComputeHash(passwordDTO.NewPassword, SHA256.Create(), user.Salt); user.Password = hashedNewPassword; return await _dbAccess.UpdatePassword(user); } public async Task DeleteUser(int userId) { var user = await _dbAccess.ReadUserForDelete(userId); if (user != null) { return await _dbAccess.DeleteUser(user); } return new ConflictObjectResult(new { message = "Invalid user" }); } public async Task RefreshToken(string refreshToken) { User user = await _dbAccess.ReadUserByRefreshToken(refreshToken); if (user == null) { return new ConflictObjectResult(new { message = "Could not match refreshtoken" }); } user = await UpdateRefreshToken(user); string jwtToken = GenerateJwtToken(user); return new OkObjectResult(new { token = jwtToken, refreshToken = user.RefreshToken }); } private bool PasswordSecurity(string password) { var hasMinimum8Chars = new Regex(@".{8,}"); return hasMinimum8Chars.IsMatch(password); } private bool EmailCheck(string email) { return new Regex(@".+@.+\..+").IsMatch(email); } private static string ComputeHash(string input, HashAlgorithm algorithm, string salt) { Byte[] inputBytes = Encoding.UTF8.GetBytes(input); Byte[] saltBytes = Encoding.UTF8.GetBytes(salt); // Combine salt and input bytes Byte[] saltedInput = new Byte[saltBytes.Length + inputBytes.Length]; saltBytes.CopyTo(saltedInput, 0); inputBytes.CopyTo(saltedInput, saltBytes.Length); Byte[] hashedBytes = algorithm.ComputeHash(saltedInput); return BitConverter.ToString(hashedBytes); } private string GenerateJwtToken(User user) { var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(ClaimTypes.Name, user.UserName) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes (_configuration["JwtSettings:Key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( _configuration["JwtSettings:Issuer"], _configuration["JwtSettings:Audience"], claims, expires: DateTime.Now.AddHours(1), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } private async Task UpdateRefreshToken(User user) { user.RefreshToken = Guid.NewGuid().ToString(); user.RefreshTokenExpireAt = DateTime.Now.AddDays(30); await _dbAccess.UpdateUser(user); return user; } } }