added forgot password feature.

This commit is contained in:
DESKTOP-GENO133\IvanPlex
2024-01-13 12:50:55 -07:00
parent 2247b1b1db
commit 8d989ee81c
9 changed files with 198 additions and 5 deletions

View File

@@ -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);
}
}
}

View File

@@ -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)

View File

@@ -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
}
}
/// <summary>
/// Generates a token and notifies user via email so they can reset their password.
/// </summary>
/// <param name="credentials"></param>
/// <returns></returns>
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 };
}
}
/// <summary>
/// Returns an empty user if can't auth against neither root nor db user.
/// </summary>
/// <param name="credentials">credentials from login page</param>
@@ -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);
}
}
}

View File

@@ -51,7 +51,7 @@
<td class="col-4">@userData.UserName</td>
<td class="col-2">@userData.Id</td>
<td class="col-4">@userData.Id</td>
<td class="col-2">@userData.Id</td>
<td class="col-2"><button type="button" class="btn btn-danger" onclick="deleteUser(@userData.Id, this)"><i class="bi bi-trash"></i></button></td>
</tr>
}
</tbody>
@@ -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);

View File

@@ -0,0 +1,26 @@
@{
ViewData["Title"] = "LubeLogger - Login";
}
@section Scripts {
<script src="~/js/login.js" asp-append-version="true"></script>
}
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
<div class="row">
<div class="col-12">
<img src="/defaults/lubelogger_logo.png" />
<div class="form-group">
<label for="inputUserName">Username</label>
<input type="text" id="inputUserName" class="form-control">
</div>
<div class="d-grid">
<button type="button" class="btn btn-warning mt-2" onclick="requestPasswordReset()"><i class="bi bi-box-arrow-in-right me-2"></i>Request</button>
</div>
<div class="d-grid">
<a href="/Login/Index" class="btn btn-link mt-2">Back to Login</a>
</div>
<div class="d-grid">
<a href="/Login/ResetPassword" class="btn btn-link mt-2">I Have a Token</a>
</div>
</div>
</div>
</div>

View File

@@ -23,6 +23,9 @@
<div class="d-grid">
<button type="button" class="btn btn-warning mt-2" onclick="performLogin()"><i class="bi bi-box-arrow-in-right me-2"></i>Login</button>
</div>
<div class="d-grid">
<a href="/Login/ForgotPassword" class="btn btn-link mt-2">Forgot Password</a>
</div>
<div class="d-grid">
<a href="/Login/Registration" class="btn btn-link mt-2">Register</a>
</div>

View File

@@ -22,7 +22,7 @@
</div>
<div class="form-group">
<label for="inputUserPassword">Password</label>
<input type="password" id="inputUserPassword" onkeyup="handlePasswordKeyPress(event)" class="form-control">
<input type="password" id="inputUserPassword" class="form-control">
</div>
<div class="d-grid">
<button type="button" class="btn btn-warning mt-2" onclick="performRegistration()"><i class="bi bi-box-arrow-in-right me-2"></i>Register</button>

View File

@@ -0,0 +1,31 @@
@{
ViewData["Title"] = "LubeLogger - Register";
}
@section Scripts {
<script src="~/js/login.js" asp-append-version="true"></script>
}
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
<div class="row">
<div class="col-12">
<img src="/defaults/lubelogger_logo.png" />
<div class="form-group">
<label for="inputToken">Token</label>
<input type="text" id="inputToken" class="form-control">
</div>
<div class="form-group">
<label for="inputUserName">Email Address</label>
<input type="text" id="inputEmail" class="form-control">
</div>
<div class="form-group">
<label for="inputUserPassword">New Password</label>
<input type="password" id="inputUserPassword" class="form-control">
</div>
<div class="d-grid">
<button type="button" class="btn btn-warning mt-2" onclick="performPasswordReset()"><i class="bi bi-box-arrow-in-right me-2"></i>Reset Password</button>
</div>
<div class="d-grid">
<a href="/Login/Index" class="btn btn-link mt-2">Back to Login</a>
</div>
</div>
</div>
</div>

View File

@@ -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();