From 8d989ee81c98c24d4488af9380a4a8fbc70da924 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 12:50:55 -0700 Subject: [PATCH] added forgot password feature. --- Controllers/AdminController.cs | 5 ++ Controllers/LoginController.cs | 20 ++++++++ Logic/LoginLogic.cs | 82 +++++++++++++++++++++++++++++-- Views/Admin/Index.cshtml | 9 +++- Views/Login/ForgotPassword.cshtml | 26 ++++++++++ Views/Login/Index.cshtml | 3 ++ Views/Login/Registration.cshtml | 2 +- Views/Login/ResetPassword.cshtml | 31 ++++++++++++ wwwroot/js/login.js | 25 ++++++++++ 9 files changed, 198 insertions(+), 5 deletions(-) create mode 100644 Views/Login/ForgotPassword.cshtml create mode 100644 Views/Login/ResetPassword.cshtml diff --git a/Controllers/AdminController.cs b/Controllers/AdminController.cs index fe62c74..4a530a5 100644 --- a/Controllers/AdminController.cs +++ b/Controllers/AdminController.cs @@ -34,5 +34,10 @@ namespace CarCareTracker.Controllers var result = _loginLogic.DeleteUserToken(tokenId); return Json(result); } + public IActionResult DeleteUser(int userId) + { + var result =_loginLogic.DeleteUser(userId); + return Json(result); + } } } diff --git a/Controllers/LoginController.cs b/Controllers/LoginController.cs index 97a0840..1358dd3 100644 --- a/Controllers/LoginController.cs +++ b/Controllers/LoginController.cs @@ -34,6 +34,14 @@ namespace CarCareTracker.Controllers { return View(); } + public IActionResult ForgotPassword() + { + return View(); + } + public IActionResult ResetPassword() + { + return View(); + } [HttpPost] public IActionResult Login(LoginModel credentials) { @@ -72,6 +80,18 @@ namespace CarCareTracker.Controllers var result = _loginLogic.RegisterNewUser(credentials); return Json(result); } + [HttpPost] + public IActionResult RequestResetPassword(LoginModel credentials) + { + var result = _loginLogic.RequestResetPassword(credentials); + return Json(result); + } + [HttpPost] + public IActionResult PerformPasswordReset(LoginModel credentials) + { + var result = _loginLogic.ResetPasswordByUser(credentials); + return Json(result); + } [Authorize] //User must already be logged in to do this. [HttpPost] public IActionResult CreateLoginCreds(LoginModel credentials) diff --git a/Logic/LoginLogic.cs b/Logic/LoginLogic.cs index 30f8e21..7d6363c 100644 --- a/Logic/LoginLogic.cs +++ b/Logic/LoginLogic.cs @@ -2,6 +2,7 @@ using CarCareTracker.Helper; using CarCareTracker.Models; using System.Net; +using System.Net.Mail; using System.Security.Cryptography; using System.Text; using System.Text.Json; @@ -12,7 +13,10 @@ namespace CarCareTracker.Logic { OperationResponse GenerateUserToken(string emailAddress, bool autoNotify); bool DeleteUserToken(int tokenId); + bool DeleteUser(int userId); OperationResponse RegisterNewUser(LoginModel credentials); + OperationResponse RequestResetPassword(LoginModel credentials); + OperationResponse ResetPasswordByUser(LoginModel credentials); OperationResponse ResetUserPassword(LoginModel credentials); UserData ValidateUserCredentials(LoginModel credentials); bool CreateRootUserCredentials(LoginModel credentials); @@ -32,6 +36,7 @@ namespace CarCareTracker.Logic _tokenData = tokenData; _mailHelper = mailHelper; } + //handles user registration public OperationResponse RegisterNewUser(LoginModel credentials) { //validate their token. @@ -60,7 +65,8 @@ namespace CarCareTracker.Logic var newUser = new UserData() { UserName = credentials.UserName, - Password = GetHash(credentials.Password) + Password = GetHash(credentials.Password), + EmailAddress = credentials.EmailAddress }; var result = _userData.SaveUserRecord(newUser); if (result) @@ -73,6 +79,66 @@ namespace CarCareTracker.Logic } } /// + /// Generates a token and notifies user via email so they can reset their password. + /// + /// + /// + public OperationResponse RequestResetPassword(LoginModel credentials) + { + var existingUser = _userData.GetUserRecordByUserName(credentials.UserName); + if (existingUser.Id != default) + { + //user exists, generate a token and send email. + //check to see if there is an existing token sent to the user. + var existingToken = _tokenData.GetTokenRecordByEmailAddress(existingUser.EmailAddress); + if (existingToken.Id == default) + { + var token = new Token() + { + Body = NewToken(), + EmailAddress = existingUser.EmailAddress + }; + var result = _tokenData.CreateNewToken(token); + if (result) + { + result = _mailHelper.NotifyUserForPasswordReset(existingUser.EmailAddress, token.Body).Success; + } + } + } + //for security purposes we want to always return true for this method. + //otherwise someone can spam the reset password method to sniff out users. + return new OperationResponse { Success = true, Message = "If your user exists in the system you should receive an email shortly with instructions on how to proceed." }; + } + public OperationResponse ResetPasswordByUser(LoginModel credentials) + { + var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token); + if (existingToken.Id == default || existingToken.EmailAddress != credentials.EmailAddress) + { + return new OperationResponse { Success = false, Message = "Invalid Token" }; + } + if (string.IsNullOrWhiteSpace(credentials.Password)) + { + return new OperationResponse { Success = false, Message = "New Password cannot be blank" }; + } + //if token is valid. + var existingUser = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress); + if (existingUser.Id == default) + { + return new OperationResponse { Success = false, Message = "Unable to locate user" }; + } + existingUser.Password = GetHash(credentials.Password); + var result = _userData.SaveUserRecord(existingUser); + //delete token + _tokenData.DeleteToken(existingToken.Id); + if (result) + { + return new OperationResponse { Success = true, Message = "Password resetted, you will be redirected to login page shortly." }; + } else + { + return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage }; + } + } + /// /// Returns an empty user if can't auth against neither root nor db user. /// /// credentials from login page @@ -125,7 +191,7 @@ namespace CarCareTracker.Logic } var token = new Token() { - Body = Guid.NewGuid().ToString().Substring(0, 8), + Body = NewToken(), EmailAddress = emailAddress }; var result = _tokenData.CreateNewToken(token); @@ -140,7 +206,8 @@ namespace CarCareTracker.Logic if (result) { return new OperationResponse { Success = true, Message = "Token Generated!" }; - } else + } + else { return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage }; } @@ -150,6 +217,11 @@ namespace CarCareTracker.Logic var result = _tokenData.DeleteToken(tokenId); return result; } + public bool DeleteUser(int userId) + { + var result = _userData.DeleteUserRecord(userId); + return result; + } public OperationResponse ResetUserPassword(LoginModel credentials) { //user might have forgotten their password. @@ -237,5 +309,9 @@ namespace CarCareTracker.Logic return Sb.ToString(); } + private string NewToken() + { + return Guid.NewGuid().ToString().Substring(0, 8); + } } } diff --git a/Views/Admin/Index.cshtml b/Views/Admin/Index.cshtml index d92583b..9f24c23 100644 --- a/Views/Admin/Index.cshtml +++ b/Views/Admin/Index.cshtml @@ -51,7 +51,7 @@ @userData.UserName @userData.Id @userData.Id - @userData.Id + } @@ -70,6 +70,13 @@ } }); } + function deleteUser(userId){ + $.post(`/Admin/DeleteUser?userId=${userId}`, function(data){ + if (data){ + reloadPage(); + } + }) + } function copyToClipboard(e) { var textToCopy = e.textContent; navigator.clipboard.writeText(textToCopy); diff --git a/Views/Login/ForgotPassword.cshtml b/Views/Login/ForgotPassword.cshtml new file mode 100644 index 0000000..b4a9498 --- /dev/null +++ b/Views/Login/ForgotPassword.cshtml @@ -0,0 +1,26 @@ +@{ + ViewData["Title"] = "LubeLogger - Login"; +} +@section Scripts { + +} +
+
+
+ +
+ + +
+
+ +
+ + +
+
+
\ No newline at end of file diff --git a/Views/Login/Index.cshtml b/Views/Login/Index.cshtml index ab462d3..0885abc 100644 --- a/Views/Login/Index.cshtml +++ b/Views/Login/Index.cshtml @@ -23,6 +23,9 @@
+
+ Forgot Password +
Register
diff --git a/Views/Login/Registration.cshtml b/Views/Login/Registration.cshtml index 860e342..131272f 100644 --- a/Views/Login/Registration.cshtml +++ b/Views/Login/Registration.cshtml @@ -22,7 +22,7 @@
- +
diff --git a/Views/Login/ResetPassword.cshtml b/Views/Login/ResetPassword.cshtml new file mode 100644 index 0000000..f15af72 --- /dev/null +++ b/Views/Login/ResetPassword.cshtml @@ -0,0 +1,31 @@ +@{ + ViewData["Title"] = "LubeLogger - Register"; +} +@section Scripts { + +} +
+
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/wwwroot/js/login.js b/wwwroot/js/login.js index d737d40..f88daf9 100644 --- a/wwwroot/js/login.js +++ b/wwwroot/js/login.js @@ -24,6 +24,31 @@ function performRegistration() { } }); } +function requestPasswordReset() { + var userName = $("#inputUserName").val(); + $.post('/Login/RequestResetPassword', { userName: userName }, function (data) { + if (data.success) { + successToast(data.message); + setTimeout(function () { window.location.href = '/Login/Index' }, 500); + } else { + errorToast(data.message); + } + }) +} +function performPasswordReset() { + var token = $("#inputToken").val(); + var userPassword = $("#inputUserPassword").val(); + var userEmail = $("#inputEmail").val(); + $.post('/Login/PerformPasswordReset', { password: userPassword, token: token, emailAddress: userEmail }, function (data) { + if (data.success) { + successToast(data.message); + setTimeout(function () { window.location.href = '/Login/Index' }, 500); + } else { + errorToast(data.message); + } + }); +} + function handlePasswordKeyPress(event) { if (event.keyCode == 13) { performLogin();