using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Mvc; using System.Security.Claims; using MistoxWebsite.Server.Services; using MistoxWebsite.Server.Services.DatabaseService; using MistoxWebsite.Server.Entities; namespace MistoxWebsite.Server.Controllers { [ApiController] [Route("api/account/[controller]")] public class AuthenticationController : MistoxControllerBase { EmailService _emailContext; public AuthenticationController(DatabaseService db, EmailService emailContext) : base(db) { _emailContext = emailContext; } [Route("login")] [HttpPost] public async Task> Login([FromForm] string UserName, [FromForm] string PasswordHash, [FromForm] bool StayLoggedIn) { try { Account? test = await _databaseService.GetAccount(UserName.ToLower()); if (test != null) { if (test.EmailVerified == true) { if (test.FailedPasswordLock) { if (test.CurrentPasswordAttempts >= test.PasswordAttempts) { return new Account() { Error = "Too many failed password attempts. Please reset your password" }; } } if (BCrypt.Net.BCrypt.Verify(PasswordHash, test.PasswordHash)) { test.CurrentPasswordAttempts = 0; await _databaseService.SetAccount(test); List claims = new List() { new Claim("ID", test.ID.ToString()) }; await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(new ClaimsIdentity(claims, "Auth")), new AuthenticationProperties { ExpiresUtc = DateTime.UtcNow.AddYears(30), // Add 30 years with sliding on IsPersistent = StayLoggedIn, // Is set from the StayLoggedIn } ); return test; } else { test.CurrentPasswordAttempts += 1; await _databaseService.SetAccount(test); return new Account() { Error = "Wrong password" }; } } else { await SendVerify(test.UserName); return new Account() { Error = "A new verify email has been sent. \n Note only 1 email send every 5 mintes" }; } } return new Account() { Error = "User doesn't exist" }; } catch (Exception ex) { return new Account() { Error = ex.Message }; } } [Route("register")] [HttpPost] public async Task> Register([FromForm] string Email, [FromForm] string UserName, [FromForm] string PasswordHash) { try { if (await _databaseService.GetAccount(UserName.ToLower()) == null) { if (await _databaseService.GetAccount(Email.ToLower()) == null) { Account? created = new Account() { UserName = UserName.ToLower(), Email = Email.ToLower(), EmailVerified = false, PasswordHash = BCrypt.Net.BCrypt.HashPassword(PasswordHash), }; await _databaseService.SetAccount(created); created = await _databaseService.GetAccount(Email.ToLower()); if (created != null) { await SendVerify(created.UserName); return created; } return new Account() { Error = "Unknown Error" }; } else { return new Account() { Error = "Email is already in use" }; } } else { return new Account() { Error = "UserName is taken" }; } } catch (Exception ex) { Console.WriteLine("Error: " + ex.Message); return new Account() { Error = ex.Message }; } } [Route("changepassword")] [HttpPost] public async Task> ChangePassword([FromForm] string OldPassword, [FromForm] string NewPassword) { try { if (isLoggedIn()) { Account user = await getLoggedInUser(); if (BCrypt.Net.BCrypt.Verify(OldPassword, user.PasswordHash)) { user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(NewPassword); user.CurrentPasswordAttempts = 0; await _databaseService.SetAccount(user); return true; } } return false; } catch { return false; } } [Route("toggleaccountlock")] [HttpPost] public async Task> ToggleAccountLock([FromForm] bool AccountLock) { try { if (isLoggedIn()) { Account user = await getLoggedInUser(); user.FailedPasswordLock = AccountLock; user.CurrentPasswordAttempts = 0; await _databaseService.SetAccount(user); return "Account Lock Status Updated"; } return "Unknown Error Occurred"; } catch (Exception ex) { return ex.Message; } } [Route("get")] [HttpPost] public async Task> Get() { try { if (isLoggedIn()) { return await getLoggedInUser(); } return Ok(); } catch { return Ok(); } } [Route("logout")] [HttpPost] public async Task Logout() { await HttpContext.SignOutAsync(); } [Route("sendverifyemail")] [HttpPost] public async Task> SendVerify([FromForm] string UserName) { try { string key = "v" + UserName; // Stop from sending multiple emails quickly if (_emailContext._SentEmails.ContainsKey(key)) { DateTime PreviousSentTime = _emailContext._SentEmails.GetValueOrDefault(key); if (PreviousSentTime.AddMinutes(5) > DateTime.Now) { return "Cannot sent another verify email until 5 minutes has elapsed "; } else { _emailContext._SentEmails.Remove(key); } } Account? test = await _databaseService.GetAccount(UserName.ToLower()); if (test != null) { test.EmailToken = Guid.NewGuid().ToString(); await _databaseService.SetAccount(test); string EmailContents = EmailService.VerifyEmailEmail; EmailContents = Substitue(EmailContents, "@UserName", UserName); EmailContents = Substitue(EmailContents, "@UserName", UserName); EmailContents = Substitue(EmailContents, "@VerifyPassword", test.EmailToken); string result = _emailContext.Send(test.Email, EmailService.VerifyEmailSubject, EmailContents); _emailContext._SentEmails.Add(key, DateTime.Now); return result; } return "Account not found"; } catch (Exception) { return "The connection couldn't be established to the email server"; } } [Route("verifyemail")] [HttpPost] public async Task> VerifyEmail([FromForm] string UserName, [FromForm] string EmailToken) { try { Account? test = await _databaseService.GetAccount(UserName.ToLower()); if (test != null) { if (!string.IsNullOrEmpty(test.EmailToken) && test.EmailToken == EmailToken) { test.EmailToken = ""; test.EmailVerified = true; await _databaseService.SetAccount(test); return true; } } return false; } catch { return false; } } [Route("sendresetpassword")] [HttpPost] public async Task> ResetPassword([FromForm] string Email) { try { string key = "p" + Email.ToLower(); // Stop from sending multiple emails quickly if (_emailContext._SentEmails.ContainsKey(key)) { DateTime PreviousSentTime = _emailContext._SentEmails.GetValueOrDefault(key); if (PreviousSentTime.AddMinutes(5) > DateTime.Now) { return "Cannot sent another reset requests until 5 minutes has elapsed"; } else { _emailContext._SentEmails.Remove(key); } } Account? test = await _databaseService.GetAccount(Email.ToLower()); if (test != null) { test.EmailToken = Guid.NewGuid().ToString(); await _databaseService.SetAccount(test); string EmailContents = EmailService.ResetPasswordEmail; EmailContents = Substitue(EmailContents, "@UserName", test.UserName); EmailContents = Substitue(EmailContents, "@UserName", test.UserName); EmailContents = Substitue(EmailContents, "@ResetPassWord", test.EmailToken); string result = _emailContext.Send(test.Email, EmailService.VerifyEmailSubject, EmailContents); _emailContext._SentEmails.Add(key, DateTime.Now); return result; } return "Account Not Found"; } catch (Exception e) { Console.WriteLine("EmailService Error: " + e.ToString()); return "The connection couldn't be established to the email server"; } } [Route("resetpassword")] [HttpPost] public async Task> ResetPwdVerify([FromForm] string UserName, [FromForm] string NewPassword, [FromForm] string ResetToken) { try { Account? test = await _databaseService.GetAccount(UserName.ToLower()); if (test != null && !string.IsNullOrEmpty(test.EmailToken)) { if (!string.IsNullOrEmpty(test.EmailToken) && test.EmailToken == ResetToken) { test.CurrentPasswordAttempts = 0; test.EmailToken = ""; test.PasswordHash = BCrypt.Net.BCrypt.HashPassword(NewPassword); await _databaseService.SetAccount(test); return true; } } return false; } catch { return false; } } [Route("delete")] [HttpPost] public async Task> delete([FromForm] string Password) { try { if (isLoggedIn()) { Account user = await getLoggedInUser(); if (BCrypt.Net.BCrypt.Verify(Password, user.PasswordHash)) { await _databaseService.DeleteAccount(user.ID); return true; } } return false; } catch { return false; } } } }