using Api.DBAccess; using Api.Models; using Microsoft.AspNetCore.Http.HttpResults; 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; namespace Api.BusinessLogic { public class UserLogic { private readonly DbAccess _dbAccess; private readonly IConfiguration _configuration; public UserLogic(IConfiguration configuration, DbAccess dbAccess) { _dbAccess = dbAccess; _configuration = configuration; } /// /// First checks if the mail is a valid one with regex so if there is something before the @ and after and it has a domain /// Then it checks if the password is to our security standard /// Then it makes sure the user has a device list /// The last thing before it saves the user is creating a salt and then hashing of the password /// /// The new user /// returns true in a OkObjectResult and if there is some error it returns a ConflictObjectResult and a message that explain the reason public async Task RegisterUser(User user) { if (!new Regex(@".+@.+\..+").IsMatch(user.Email)) { return new ConflictObjectResult(new { message = "Invalid email address" }); } if (!PasswordSecurity(user.Password)) { return new ConflictObjectResult(new { message = "Password is not up to the security standard" }); } if (user.Devices == null) { user.Devices = new List(); } string salt = Guid.NewGuid().ToString(); string hashedPassword = ComputeHash(user.Password, SHA256.Create(), salt); user.Salt = salt; user.Password = hashedPassword; return await _dbAccess.CreateUser(user); } /// /// Gets the user that matches the login /// Hashes the login password with the users salt /// checks if the hashed password that the login has is the same as the one saved in the database /// /// Has a username or email and a password /// Returns a jwt token, username and userid public async Task Login(Login login) { User user = await _dbAccess.Login(login); if (user == null || user.Id == 0) { return new ConflictObjectResult(new { message = "Could not find user" }); } string hashedPassword = ComputeHash(login.Password, SHA256.Create(), user.Salt); if (user.Password == hashedPassword) { var token = GenerateJwtToken(user); user.RefreshToken = Guid.NewGuid().ToString(); _dbAccess.UpdatesRefreshToken(user.RefreshToken, user.Id); return new OkObjectResult(new { token, user.UserName, user.Id, refreshToken = user.RefreshToken }); } return new ConflictObjectResult(new { message = "Invalid password" }); } /// /// First checks if the mail is a valid one with regex so if there is something before the @ and after and it has a domain /// Then it checks if the password is to our security standard /// Finds the user that matches the userId and hashes a new hash with the old salt /// Then the updated user and the userId is being send to dbaccess /// /// Contains the updated user info /// Has the id for the user that is to be updated /// returns the updated user in a OkObjectResult and if there is some error it returns a ConflictObjectResult and a message that explain the reason public async Task EditProfile(User user, int userId) { if (!new Regex(@".+@.+\..+").IsMatch(user.Email)) { return new ConflictObjectResult(new { message = "Invalid email address" }); } if (!PasswordSecurity(user.Password)) { return new ConflictObjectResult(new { message = "Password is not up to the security standard" }); } var profile = await _dbAccess.ReadUser(userId); string hashedPassword = ComputeHash(user.Password, SHA256.Create(), profile.Salt); user.Password = hashedPassword; return await _dbAccess.UpdateUser(user, userId); } /// /// Just sends the userid of the user that is to be deleted /// /// The Id of the user that is to be deleted /// returns the true in a OkObjectResult and if there is some error it returns a ConflictObjectResult and a message that explain the reason public async Task DeleteUser(int userId) { return await _dbAccess.DeleteUser(userId); } public async Task RefreshToken(string refreshToken) { User user = await _dbAccess.ReadUser(refreshToken); if (user == null) { return new ConflictObjectResult(new { message = "Could not match refreshtoken" }); } return new OkObjectResult(GenerateJwtToken(user)); } /// /// Generates a hash from a salt and input using the algorithm that is provided /// /// This is the input that is supposed to be hashed /// This is the alogorithm that is used to encrypt the input /// This is something extra added to make the hashed input more unpredictable /// The hashed input 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); } /// /// Checks if password is up to our security standard /// /// The password that is to be checked /// true or false dependeing on if the password is up to standard public bool PasswordSecurity(string password) { var hasMinimum8Chars = new Regex(@".{8,}"); return hasMinimum8Chars.IsMatch(password); } /// /// Generates a JWT token that last 2 hours /// /// Used for sending the userid and username with the token /// Returns a valid JWTToken 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(2), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } } }