From 8815009b04eea472b7abcfbb2ab1cb8d660c27bd Mon Sep 17 00:00:00 2001 From: "DESKTOP-T0O5CDB\\DESK-555BD" Date: Fri, 12 Jan 2024 18:37:51 -0700 Subject: [PATCH 01/25] added token based registration. --- Controllers/AdminController.cs | 31 +++ Controllers/LoginController.cs | 74 +++---- .../Implementations/TokenRecordDataAccess.cs | 48 +++++ .../Implementations/UserRecordDataAccess.cs | 57 +++++ External/Interfaces/ITokenRecordDataAccess.cs | 12 ++ External/Interfaces/IUserRecordDataAccess.cs | 13 ++ Helper/LoginHelper.cs | 47 ---- Logic/LoginLogic.cs | 201 ++++++++++++++++++ Middleware/Authen.cs | 69 +++--- Models/Admin/AdminViewModel.cs | 8 + Models/Login/AuthCookie.cs | 3 +- Models/Login/LoginModel.cs | 1 + Models/Login/Token.cs | 8 + Models/Login/UserData.cs | 10 + Models/OperationResponse.cs | 8 + Program.cs | 7 +- Views/Admin/Index.cshtml | 62 ++++++ Views/Login/Index.cshtml | 3 + Views/Login/Registration.cshtml | 31 +++ wwwroot/js/login.js | 13 ++ 20 files changed, 581 insertions(+), 125 deletions(-) create mode 100644 Controllers/AdminController.cs create mode 100644 External/Implementations/TokenRecordDataAccess.cs create mode 100644 External/Implementations/UserRecordDataAccess.cs create mode 100644 External/Interfaces/ITokenRecordDataAccess.cs create mode 100644 External/Interfaces/IUserRecordDataAccess.cs delete mode 100644 Helper/LoginHelper.cs create mode 100644 Logic/LoginLogic.cs create mode 100644 Models/Admin/AdminViewModel.cs create mode 100644 Models/Login/Token.cs create mode 100644 Models/Login/UserData.cs create mode 100644 Models/OperationResponse.cs create mode 100644 Views/Admin/Index.cshtml create mode 100644 Views/Login/Registration.cshtml diff --git a/Controllers/AdminController.cs b/Controllers/AdminController.cs new file mode 100644 index 0000000..be5dbf4 --- /dev/null +++ b/Controllers/AdminController.cs @@ -0,0 +1,31 @@ +using CarCareTracker.Logic; +using CarCareTracker.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace CarCareTracker.Controllers +{ + [Authorize(Roles = nameof(UserData.IsAdmin))] + public class AdminController : Controller + { + private ILoginLogic _loginLogic; + public AdminController(ILoginLogic loginLogic) + { + _loginLogic = loginLogic; + } + public IActionResult Index() + { + var viewModel = new AdminViewModel + { + Users = _loginLogic.GetAllUsers(), + Tokens = _loginLogic.GetAllTokens() + }; + return View(viewModel); + } + public IActionResult GenerateNewToken() + { + var result = _loginLogic.GenerateUserToken(); + return Json(result); + } + } +} diff --git a/Controllers/LoginController.cs b/Controllers/LoginController.cs index fc9733e..97a0840 100644 --- a/Controllers/LoginController.cs +++ b/Controllers/LoginController.cs @@ -1,4 +1,5 @@ using CarCareTracker.Helper; +using CarCareTracker.Logic; using CarCareTracker.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.DataProtection; @@ -13,22 +14,26 @@ namespace CarCareTracker.Controllers public class LoginController : Controller { private IDataProtector _dataProtector; - private ILoginHelper _loginHelper; + private ILoginLogic _loginLogic; private readonly ILogger _logger; public LoginController( ILogger logger, IDataProtectionProvider securityProvider, - ILoginHelper loginHelper + ILoginLogic loginLogic ) { _dataProtector = securityProvider.CreateProtector("login"); _logger = logger; - _loginHelper = loginHelper; + _loginLogic = loginLogic; } public IActionResult Index() { return View(); } + public IActionResult Registration() + { + return View(); + } [HttpPost] public IActionResult Login(LoginModel credentials) { @@ -40,13 +45,12 @@ namespace CarCareTracker.Controllers //compare it against hashed credentials try { - var loginIsValid = _loginHelper.ValidateUserCredentials(credentials); - if (loginIsValid) + var userData = _loginLogic.ValidateUserCredentials(credentials); + if (userData.Id != default) { AuthCookie authCookie = new AuthCookie { - Id = 1, //this is hardcoded for now - UserName = credentials.UserName, + UserData = userData, ExpiresOn = DateTime.Now.AddDays(credentials.IsPersistent ? 30 : 1) }; var serializedCookie = JsonSerializer.Serialize(authCookie); @@ -61,26 +65,21 @@ namespace CarCareTracker.Controllers } return Json(false); } + + [HttpPost] + public IActionResult Register(LoginModel credentials) + { + var result = _loginLogic.RegisterNewUser(credentials); + return Json(result); + } [Authorize] //User must already be logged in to do this. [HttpPost] public IActionResult CreateLoginCreds(LoginModel credentials) { try { - var configFileContents = System.IO.File.ReadAllText(StaticHelper.UserConfigPath); - var existingUserConfig = JsonSerializer.Deserialize(configFileContents); - if (existingUserConfig is not null) - { - //create hashes of the login credentials. - var hashedUserName = Sha256_hash(credentials.UserName); - var hashedPassword = Sha256_hash(credentials.Password); - //copy over settings that are off limits on the settings page. - existingUserConfig.EnableAuth = true; - existingUserConfig.UserNameHash = hashedUserName; - existingUserConfig.UserPasswordHash = hashedPassword; - } - System.IO.File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(existingUserConfig)); - return Json(true); + var result = _loginLogic.CreateRootUserCredentials(credentials); + return Json(result); } catch (Exception ex) { @@ -94,19 +93,13 @@ namespace CarCareTracker.Controllers { try { - var configFileContents = System.IO.File.ReadAllText(StaticHelper.UserConfigPath); - var existingUserConfig = JsonSerializer.Deserialize(configFileContents); - if (existingUserConfig is not null) - { - //copy over settings that are off limits on the settings page. - existingUserConfig.EnableAuth = false; - existingUserConfig.UserNameHash = string.Empty; - existingUserConfig.UserPasswordHash = string.Empty; - } - System.IO.File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(existingUserConfig)); + var result = _loginLogic.DeleteRootUserCredentials(); //destroy any login cookies. - Response.Cookies.Delete("ACCESS_TOKEN"); - return Json(true); + if (result) + { + Response.Cookies.Delete("ACCESS_TOKEN"); + } + return Json(result); } catch (Exception ex) { @@ -121,20 +114,5 @@ namespace CarCareTracker.Controllers Response.Cookies.Delete("ACCESS_TOKEN"); return Json(true); } - private static string Sha256_hash(string value) - { - StringBuilder Sb = new StringBuilder(); - - using (var hash = SHA256.Create()) - { - Encoding enc = Encoding.UTF8; - byte[] result = hash.ComputeHash(enc.GetBytes(value)); - - foreach (byte b in result) - Sb.Append(b.ToString("x2")); - } - - return Sb.ToString(); - } } } diff --git a/External/Implementations/TokenRecordDataAccess.cs b/External/Implementations/TokenRecordDataAccess.cs new file mode 100644 index 0000000..289a1c0 --- /dev/null +++ b/External/Implementations/TokenRecordDataAccess.cs @@ -0,0 +1,48 @@ +using CarCareTracker.External.Interfaces; +using CarCareTracker.Helper; +using CarCareTracker.Models; +using LiteDB; + +namespace CarCareTracker.External.Implementations +{ + public class TokenRecordDataAccess : ITokenRecordDataAccess + { + private static string dbName = StaticHelper.DbName; + private static string tableName = "tokenrecords"; + public List GetTokens() + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + return table.FindAll().ToList(); + }; + } + public Token GetTokenRecordByBody(string tokenBody) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + var tokenRecord = table.FindOne(Query.EQ(nameof(Token.Body), tokenBody)); + return tokenRecord ?? new Token(); + }; + } + public bool CreateNewToken(Token token) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + table.Insert(token); + return true; + }; + } + public bool DeleteToken(int tokenId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + table.Delete(tokenId); + return true; + }; + } + } +} \ No newline at end of file diff --git a/External/Implementations/UserRecordDataAccess.cs b/External/Implementations/UserRecordDataAccess.cs new file mode 100644 index 0000000..1586389 --- /dev/null +++ b/External/Implementations/UserRecordDataAccess.cs @@ -0,0 +1,57 @@ +using CarCareTracker.External.Interfaces; +using CarCareTracker.Helper; +using CarCareTracker.Models; +using LiteDB; + +namespace CarCareTracker.External.Implementations +{ + public class UserRecordDataAccess : IUserRecordDataAccess + { + private static string dbName = StaticHelper.DbName; + private static string tableName = "userrecords"; + public List GetUsers() + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + return table.FindAll().ToList(); + }; + } + public UserData GetUserRecordByUserName(string userName) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + var userRecord = table.FindOne(Query.EQ(nameof(UserData.UserName), userName)); + return userRecord ?? new UserData(); + }; + } + public UserData GetUserRecordById(int userId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + var userRecord = table.FindById(userId); + return userRecord ?? new UserData(); + }; + } + public bool SaveUserRecord(UserData userRecord) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + table.Upsert(userRecord); + return true; + }; + } + public bool DeleteUserRecord(int userId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + table.Delete(userId); + return true; + }; + } + } +} \ No newline at end of file diff --git a/External/Interfaces/ITokenRecordDataAccess.cs b/External/Interfaces/ITokenRecordDataAccess.cs new file mode 100644 index 0000000..231fa5c --- /dev/null +++ b/External/Interfaces/ITokenRecordDataAccess.cs @@ -0,0 +1,12 @@ +using CarCareTracker.Models; + +namespace CarCareTracker.External.Interfaces +{ + public interface ITokenRecordDataAccess + { + public List GetTokens(); + public Token GetTokenRecordByBody(string tokenBody); + public bool CreateNewToken(Token token); + public bool DeleteToken(int tokenId); + } +} diff --git a/External/Interfaces/IUserRecordDataAccess.cs b/External/Interfaces/IUserRecordDataAccess.cs new file mode 100644 index 0000000..3ba8ae9 --- /dev/null +++ b/External/Interfaces/IUserRecordDataAccess.cs @@ -0,0 +1,13 @@ +using CarCareTracker.Models; + +namespace CarCareTracker.External.Interfaces +{ + public interface IUserRecordDataAccess + { + public List GetUsers(); + public UserData GetUserRecordByUserName(string userName); + public UserData GetUserRecordById(int userId); + public bool SaveUserRecord(UserData userRecord); + public bool DeleteUserRecord(int userId); + } +} \ No newline at end of file diff --git a/Helper/LoginHelper.cs b/Helper/LoginHelper.cs deleted file mode 100644 index a43b7c6..0000000 --- a/Helper/LoginHelper.cs +++ /dev/null @@ -1,47 +0,0 @@ -using CarCareTracker.Models; -using System.Security.Cryptography; -using System.Text; - -namespace CarCareTracker.Helper -{ - public interface ILoginHelper - { - bool ValidateUserCredentials(LoginModel credentials); - } - public class LoginHelper: ILoginHelper - { - public bool ValidateUserCredentials(LoginModel credentials) - { - var configFileContents = System.IO.File.ReadAllText(StaticHelper.UserConfigPath); - var existingUserConfig = System.Text.Json.JsonSerializer.Deserialize(configFileContents); - if (existingUserConfig is not null) - { - //create hashes of the login credentials. - var hashedUserName = Sha256_hash(credentials.UserName); - var hashedPassword = Sha256_hash(credentials.Password); - //compare against stored hash. - if (hashedUserName == existingUserConfig.UserNameHash && - hashedPassword == existingUserConfig.UserPasswordHash) - { - return true; - } - } - return false; - } - private static string Sha256_hash(string value) - { - StringBuilder Sb = new StringBuilder(); - - using (var hash = SHA256.Create()) - { - Encoding enc = Encoding.UTF8; - byte[] result = hash.ComputeHash(enc.GetBytes(value)); - - foreach (byte b in result) - Sb.Append(b.ToString("x2")); - } - - return Sb.ToString(); - } - } -} diff --git a/Logic/LoginLogic.cs b/Logic/LoginLogic.cs new file mode 100644 index 0000000..b2aa44f --- /dev/null +++ b/Logic/LoginLogic.cs @@ -0,0 +1,201 @@ +using CarCareTracker.External.Interfaces; +using CarCareTracker.Helper; +using CarCareTracker.Models; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; + +namespace CarCareTracker.Logic +{ + public interface ILoginLogic + { + bool GenerateUserToken(); + OperationResponse RegisterNewUser(LoginModel credentials); + OperationResponse ResetUserPassword(LoginModel credentials); + UserData ValidateUserCredentials(LoginModel credentials); + bool CreateRootUserCredentials(LoginModel credentials); + bool DeleteRootUserCredentials(); + List GetAllUsers(); + List GetAllTokens(); + + } + public class LoginLogic : ILoginLogic + { + private readonly IUserRecordDataAccess _userData; + private readonly ITokenRecordDataAccess _tokenData; + public LoginLogic(IUserRecordDataAccess userData, ITokenRecordDataAccess tokenData) + { + _userData = userData; + _tokenData = tokenData; + } + public OperationResponse RegisterNewUser(LoginModel credentials) + { + //validate their token. + var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token); + if (existingToken.Id == default) + { + return new OperationResponse { Success = false, Message = "Invalid Token" }; + } + //token is valid, check if username and password is acceptable and that username is unique. + if (string.IsNullOrWhiteSpace(credentials.UserName) || string.IsNullOrWhiteSpace(credentials.Password)) + { + return new OperationResponse { Success = false, Message = "Neither username nor password can be blank" }; + } + var existingUser = _userData.GetUserRecordByUserName(credentials.UserName); + if (existingUser.Id != default) + { + return new OperationResponse { Success = false, Message = "Username already taken" }; + } + //username is unique then we delete the token and create the user. + _tokenData.DeleteToken(existingToken.Id); + var newUser = new UserData() + { + UserName = credentials.UserName, + Password = GetHash(credentials.Password) + }; + var result = _userData.SaveUserRecord(newUser); + if (result) + { + return new OperationResponse { Success = true, Message = "You will be redirected to the login page briefly." }; + } else + { + return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." }; + } + } + /// + /// Returns an empty user if can't auth against neither root nor db user. + /// + /// credentials from login page + /// + public UserData ValidateUserCredentials(LoginModel credentials) + { + if (UserIsRoot(credentials)) + { + return new UserData() + { + Id = -1, + UserName = credentials.UserName, + IsAdmin = true + }; + } else + { + //authenticate via DB. + var result = _userData.GetUserRecordByUserName(credentials.UserName); + if (GetHash(credentials.Password) == result.Password) + { + result.Password = string.Empty; + return result; + } else + { + return new UserData(); + } + } + } + #region "Admin Functions" + public List GetAllUsers() + { + var result = _userData.GetUsers(); + return result; + } + public List GetAllTokens() + { + var result = _tokenData.GetTokens(); + return result; + } + public bool GenerateUserToken() + { + var token = new Token() + { + Body = Guid.NewGuid().ToString().Substring(0, 8) + }; + var result = _tokenData.CreateNewToken(token); + return result; + } + public OperationResponse ResetUserPassword(LoginModel credentials) + { + //user might have forgotten their password. + var existingUser = _userData.GetUserRecordByUserName(credentials.UserName); + if (existingUser.Id == default) + { + return new OperationResponse { Success = false, Message = "Unable to find user" }; + } + var newPassword = Guid.NewGuid().ToString().Substring(0, 8); + existingUser.Password = GetHash(newPassword); + var result = _userData.SaveUserRecord(existingUser); + if (result) + { + return new OperationResponse { Success = true, Message = newPassword }; + } else + { + return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." }; + } + } + #endregion + #region "Root User" + public bool CreateRootUserCredentials(LoginModel credentials) + { + var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath); + var existingUserConfig = JsonSerializer.Deserialize(configFileContents); + if (existingUserConfig is not null) + { + //create hashes of the login credentials. + var hashedUserName = GetHash(credentials.UserName); + var hashedPassword = GetHash(credentials.Password); + //copy over settings that are off limits on the settings page. + existingUserConfig.EnableAuth = true; + existingUserConfig.UserNameHash = hashedUserName; + existingUserConfig.UserPasswordHash = hashedPassword; + } + File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(existingUserConfig)); + return true; + } + public bool DeleteRootUserCredentials() { + var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath); + var existingUserConfig = JsonSerializer.Deserialize(configFileContents); + if (existingUserConfig is not null) + { + //copy over settings that are off limits on the settings page. + existingUserConfig.EnableAuth = false; + existingUserConfig.UserNameHash = string.Empty; + existingUserConfig.UserPasswordHash = string.Empty; + } + File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(existingUserConfig)); + return true; + } + private bool UserIsRoot(LoginModel credentials) + { + var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath); + var existingUserConfig = JsonSerializer.Deserialize(configFileContents); + if (existingUserConfig is not null) + { + //create hashes of the login credentials. + var hashedUserName = GetHash(credentials.UserName); + var hashedPassword = GetHash(credentials.Password); + //compare against stored hash. + if (hashedUserName == existingUserConfig.UserNameHash && + hashedPassword == existingUserConfig.UserPasswordHash) + { + return true; + } + } + return false; + } + #endregion + private static string GetHash(string value) + { + StringBuilder Sb = new StringBuilder(); + + using (var hash = SHA256.Create()) + { + Encoding enc = Encoding.UTF8; + byte[] result = hash.ComputeHash(enc.GetBytes(value)); + + foreach (byte b in result) + Sb.Append(b.ToString("x2")); + } + + return Sb.ToString(); + } + } +} diff --git a/Middleware/Authen.cs b/Middleware/Authen.cs index fb10b03..b486b59 100644 --- a/Middleware/Authen.cs +++ b/Middleware/Authen.cs @@ -1,4 +1,4 @@ -using CarCareTracker.Helper; +using CarCareTracker.Logic; using CarCareTracker.Models; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Components.Web; @@ -15,20 +15,20 @@ namespace CarCareTracker.Middleware { private IHttpContextAccessor _httpContext; private IDataProtector _dataProtector; - private ILoginHelper _loginHelper; + private ILoginLogic _loginLogic; private bool enableAuth; public Authen( IOptionsMonitor options, UrlEncoder encoder, ILoggerFactory logger, IConfiguration configuration, - ILoginHelper loginHelper, + ILoginLogic loginLogic, IDataProtectionProvider securityProvider, IHttpContextAccessor httpContext) : base(options, logger, encoder) { _httpContext = httpContext; _dataProtector = securityProvider.CreateProtector("login"); - _loginHelper = loginHelper; + _loginLogic = loginLogic; enableAuth = bool.Parse(configuration["EnableAuth"]); } protected override async Task HandleAuthenticateAsync() @@ -66,14 +66,18 @@ namespace CarCareTracker.Middleware return AuthenticateResult.Fail("Invalid credentials"); } else { - var validUser = _loginHelper.ValidateUserCredentials(new LoginModel { UserName = splitString[0], Password = splitString[1] }); - if (validUser) + var userData = _loginLogic.ValidateUserCredentials(new LoginModel { UserName = splitString[0], Password = splitString[1] }); + if (userData.Id != default) { var appIdentity = new ClaimsIdentity("Custom"); var userIdentity = new List { new(ClaimTypes.Name, splitString[0]) }; + if (userData.IsAdmin) + { + userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsAdmin))); + } appIdentity.AddClaims(userIdentity); AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name); return AuthenticateResult.Success(ticket); @@ -82,33 +86,44 @@ namespace CarCareTracker.Middleware } else if (!string.IsNullOrWhiteSpace(access_token)) { - //decrypt the access token. - var decryptedCookie = _dataProtector.Unprotect(access_token); - AuthCookie authCookie = JsonSerializer.Deserialize(decryptedCookie); - if (authCookie != null) + try { - //validate auth cookie - if (authCookie.ExpiresOn < DateTime.Now) + //decrypt the access token. + var decryptedCookie = _dataProtector.Unprotect(access_token); + AuthCookie authCookie = JsonSerializer.Deserialize(decryptedCookie); + if (authCookie != null) { - //if cookie is expired - return AuthenticateResult.Fail("Expired credentials"); - } - else if (authCookie.Id == default || string.IsNullOrWhiteSpace(authCookie.UserName)) - { - return AuthenticateResult.Fail("Corrupted credentials"); - } - else - { - var appIdentity = new ClaimsIdentity("Custom"); - var userIdentity = new List + //validate auth cookie + if (authCookie.ExpiresOn < DateTime.Now) { - new(ClaimTypes.Name, authCookie.UserName) + //if cookie is expired + return AuthenticateResult.Fail("Expired credentials"); + } + else if (authCookie.UserData.Id == default || string.IsNullOrWhiteSpace(authCookie.UserData.UserName)) + { + return AuthenticateResult.Fail("Corrupted credentials"); + } + else + { + var appIdentity = new ClaimsIdentity("Custom"); + var userIdentity = new List + { + new(ClaimTypes.Name, authCookie.UserData.UserName) }; - appIdentity.AddClaims(userIdentity); - AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name); - return AuthenticateResult.Success(ticket); + if (authCookie.UserData.IsAdmin) + { + userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsAdmin))); + } + appIdentity.AddClaims(userIdentity); + AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name); + return AuthenticateResult.Success(ticket); + } } } + catch (Exception ex) + { + return AuthenticateResult.Fail("Corrupted credentials"); + } } return AuthenticateResult.Fail("Invalid credentials"); } diff --git a/Models/Admin/AdminViewModel.cs b/Models/Admin/AdminViewModel.cs new file mode 100644 index 0000000..2aeba42 --- /dev/null +++ b/Models/Admin/AdminViewModel.cs @@ -0,0 +1,8 @@ +namespace CarCareTracker.Models +{ + public class AdminViewModel + { + public List Users { get; set; } + public List Tokens { get; set; } + } +} diff --git a/Models/Login/AuthCookie.cs b/Models/Login/AuthCookie.cs index 0a3356a..24b4d96 100644 --- a/Models/Login/AuthCookie.cs +++ b/Models/Login/AuthCookie.cs @@ -2,8 +2,7 @@ { public class AuthCookie { - public int Id { get; set; } - public string UserName { get; set; } + public UserData UserData { get; set; } public DateTime ExpiresOn { get; set; } } } diff --git a/Models/Login/LoginModel.cs b/Models/Login/LoginModel.cs index 8afa0fb..a5424ed 100644 --- a/Models/Login/LoginModel.cs +++ b/Models/Login/LoginModel.cs @@ -4,6 +4,7 @@ { public string UserName { get; set; } public string Password { get; set; } + public string Token { get; set; } public bool IsPersistent { get; set; } = false; } } diff --git a/Models/Login/Token.cs b/Models/Login/Token.cs new file mode 100644 index 0000000..1b438e1 --- /dev/null +++ b/Models/Login/Token.cs @@ -0,0 +1,8 @@ +namespace CarCareTracker.Models +{ + public class Token + { + public int Id { get; set; } + public string Body { get; set; } + } +} diff --git a/Models/Login/UserData.cs b/Models/Login/UserData.cs new file mode 100644 index 0000000..8160473 --- /dev/null +++ b/Models/Login/UserData.cs @@ -0,0 +1,10 @@ +namespace CarCareTracker.Models +{ + public class UserData + { + public int Id { get; set; } + public string UserName { get; set; } + public string Password { get; set; } + public bool IsAdmin { get; set; } + } +} \ No newline at end of file diff --git a/Models/OperationResponse.cs b/Models/OperationResponse.cs new file mode 100644 index 0000000..a5be6c7 --- /dev/null +++ b/Models/OperationResponse.cs @@ -0,0 +1,8 @@ +namespace CarCareTracker.Models +{ + public class OperationResponse + { + public bool Success { get; set; } + public string Message { get; set; } + } +} diff --git a/Program.cs b/Program.cs index e729b1c..c521fbe 100644 --- a/Program.cs +++ b/Program.cs @@ -1,6 +1,7 @@ using CarCareTracker.External.Implementations; using CarCareTracker.External.Interfaces; using CarCareTracker.Helper; +using CarCareTracker.Logic; using CarCareTracker.Middleware; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; @@ -17,14 +18,18 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); //configure helpers builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); -builder.Services.AddSingleton(); builder.Services.AddSingleton(); +//configur logic +builder.Services.AddSingleton(); + if (!Directory.Exists("data")) { Directory.CreateDirectory("data"); diff --git a/Views/Admin/Index.cshtml b/Views/Admin/Index.cshtml new file mode 100644 index 0000000..6ea13d0 --- /dev/null +++ b/Views/Admin/Index.cshtml @@ -0,0 +1,62 @@ +@model AdminViewModel +
+
+
+
+ +
+ + + + + + + + + @foreach (Token token in Model.Tokens) + { + + + + + } + +
TokenDelete
@token.Body@token.Id
+
+
+ + + + + + + + + + + @foreach (UserData userData in Model.Users) + { + + + + + + + } + +
UserNameIs AdminReset PasswordDelete
@userData.UserName@userData.Id@userData.Id@userData.Id
+
+
+
+ \ No newline at end of file diff --git a/Views/Login/Index.cshtml b/Views/Login/Index.cshtml index e7bc17e..ab462d3 100644 --- a/Views/Login/Index.cshtml +++ b/Views/Login/Index.cshtml @@ -23,6 +23,9 @@
+
+ Register +
\ No newline at end of file diff --git a/Views/Login/Registration.cshtml b/Views/Login/Registration.cshtml new file mode 100644 index 0000000..2e12031 --- /dev/null +++ b/Views/Login/Registration.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 cd06122..b2f002e 100644 --- a/wwwroot/js/login.js +++ b/wwwroot/js/login.js @@ -10,6 +10,19 @@ } }) } +function performRegistration() { + var token = $("#inputToken").val(); + var userName = $("#inputUserName").val(); + var userPassword = $("#inputUserPassword").val(); + $.post('/Login/Register', { userName: userName, password: userPassword, token: token }, 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(); From 03b89786ecdcc3bb6777b7a6ba9c281b0b672051 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Fri, 12 Jan 2024 23:54:12 -0700 Subject: [PATCH 02/25] make email address required for token generation and user registration. --- Controllers/AdminController.cs | 9 ++- .../Implementations/TokenRecordDataAccess.cs | 9 +++ .../Implementations/UserRecordDataAccess.cs | 9 +++ External/Interfaces/ITokenRecordDataAccess.cs | 1 + External/Interfaces/IUserRecordDataAccess.cs | 1 + Logic/LoginLogic.cs | 43 ++++++++++---- Middleware/Authen.cs | 2 +- Models/Login/LoginModel.cs | 1 + Models/Login/Token.cs | 1 + Models/Login/UserData.cs | 1 + Views/Admin/Index.cshtml | 59 +++++++++++++++---- Views/Login/Registration.cshtml | 4 ++ wwwroot/js/login.js | 3 +- 13 files changed, 119 insertions(+), 24 deletions(-) diff --git a/Controllers/AdminController.cs b/Controllers/AdminController.cs index be5dbf4..6197bba 100644 --- a/Controllers/AdminController.cs +++ b/Controllers/AdminController.cs @@ -22,9 +22,14 @@ namespace CarCareTracker.Controllers }; return View(viewModel); } - public IActionResult GenerateNewToken() + public IActionResult GenerateNewToken(string emailAddress) { - var result = _loginLogic.GenerateUserToken(); + var result = _loginLogic.GenerateUserToken(emailAddress); + return Json(result); + } + public IActionResult DeleteToken(int tokenId) + { + var result = _loginLogic.DeleteUserToken(tokenId); return Json(result); } } diff --git a/External/Implementations/TokenRecordDataAccess.cs b/External/Implementations/TokenRecordDataAccess.cs index 289a1c0..961f583 100644 --- a/External/Implementations/TokenRecordDataAccess.cs +++ b/External/Implementations/TokenRecordDataAccess.cs @@ -26,6 +26,15 @@ namespace CarCareTracker.External.Implementations return tokenRecord ?? new Token(); }; } + public Token GetTokenRecordByEmailAddress(string emailAddress) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + var tokenRecord = table.FindOne(Query.EQ(nameof(Token.EmailAddress), emailAddress)); + return tokenRecord ?? new Token(); + }; + } public bool CreateNewToken(Token token) { using (var db = new LiteDatabase(dbName)) diff --git a/External/Implementations/UserRecordDataAccess.cs b/External/Implementations/UserRecordDataAccess.cs index 1586389..7e13f46 100644 --- a/External/Implementations/UserRecordDataAccess.cs +++ b/External/Implementations/UserRecordDataAccess.cs @@ -26,6 +26,15 @@ namespace CarCareTracker.External.Implementations return userRecord ?? new UserData(); }; } + public UserData GetUserRecordByEmailAddress(string emailAddress) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + var userRecord = table.FindOne(Query.EQ(nameof(UserData.EmailAddress), emailAddress)); + return userRecord ?? new UserData(); + }; + } public UserData GetUserRecordById(int userId) { using (var db = new LiteDatabase(dbName)) diff --git a/External/Interfaces/ITokenRecordDataAccess.cs b/External/Interfaces/ITokenRecordDataAccess.cs index 231fa5c..d0dcca2 100644 --- a/External/Interfaces/ITokenRecordDataAccess.cs +++ b/External/Interfaces/ITokenRecordDataAccess.cs @@ -6,6 +6,7 @@ namespace CarCareTracker.External.Interfaces { public List GetTokens(); public Token GetTokenRecordByBody(string tokenBody); + public Token GetTokenRecordByEmailAddress(string emailAddress); public bool CreateNewToken(Token token); public bool DeleteToken(int tokenId); } diff --git a/External/Interfaces/IUserRecordDataAccess.cs b/External/Interfaces/IUserRecordDataAccess.cs index 3ba8ae9..971072d 100644 --- a/External/Interfaces/IUserRecordDataAccess.cs +++ b/External/Interfaces/IUserRecordDataAccess.cs @@ -6,6 +6,7 @@ namespace CarCareTracker.External.Interfaces { public List GetUsers(); public UserData GetUserRecordByUserName(string userName); + public UserData GetUserRecordByEmailAddress(string emailAddress); public UserData GetUserRecordById(int userId); public bool SaveUserRecord(UserData userRecord); public bool DeleteUserRecord(int userId); diff --git a/Logic/LoginLogic.cs b/Logic/LoginLogic.cs index b2aa44f..22a425c 100644 --- a/Logic/LoginLogic.cs +++ b/Logic/LoginLogic.cs @@ -10,7 +10,8 @@ namespace CarCareTracker.Logic { public interface ILoginLogic { - bool GenerateUserToken(); + bool GenerateUserToken(string emailAddress); + bool DeleteUserToken(int tokenId); OperationResponse RegisterNewUser(LoginModel credentials); OperationResponse ResetUserPassword(LoginModel credentials); UserData ValidateUserCredentials(LoginModel credentials); @@ -33,12 +34,12 @@ namespace CarCareTracker.Logic { //validate their token. var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token); - if (existingToken.Id == default) + if (existingToken.Id == default || existingToken.EmailAddress != credentials.EmailAddress) { return new OperationResponse { Success = false, Message = "Invalid Token" }; } //token is valid, check if username and password is acceptable and that username is unique. - if (string.IsNullOrWhiteSpace(credentials.UserName) || string.IsNullOrWhiteSpace(credentials.Password)) + if (string.IsNullOrWhiteSpace(credentials.EmailAddress) || string.IsNullOrWhiteSpace(credentials.UserName) || string.IsNullOrWhiteSpace(credentials.Password)) { return new OperationResponse { Success = false, Message = "Neither username nor password can be blank" }; } @@ -47,6 +48,11 @@ namespace CarCareTracker.Logic { return new OperationResponse { Success = false, Message = "Username already taken" }; } + var existingUserWithEmail = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress); + if (existingUserWithEmail.Id != default) + { + return new OperationResponse { Success = false, Message = "A user with that email already exists" }; + } //username is unique then we delete the token and create the user. _tokenData.DeleteToken(existingToken.Id); var newUser = new UserData() @@ -58,7 +64,8 @@ namespace CarCareTracker.Logic if (result) { return new OperationResponse { Success = true, Message = "You will be redirected to the login page briefly." }; - } else + } + else { return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." }; } @@ -78,7 +85,8 @@ namespace CarCareTracker.Logic UserName = credentials.UserName, IsAdmin = true }; - } else + } + else { //authenticate via DB. var result = _userData.GetUserRecordByUserName(credentials.UserName); @@ -86,7 +94,8 @@ namespace CarCareTracker.Logic { result.Password = string.Empty; return result; - } else + } + else { return new UserData(); } @@ -103,15 +112,27 @@ namespace CarCareTracker.Logic var result = _tokenData.GetTokens(); return result; } - public bool GenerateUserToken() + public bool GenerateUserToken(string emailAddress) { + //check if email address already has a token attached to it. + var existingToken = _tokenData.GetTokenRecordByEmailAddress(emailAddress); + if (existingToken.Id != default) + { + return false; + } var token = new Token() { - Body = Guid.NewGuid().ToString().Substring(0, 8) + Body = Guid.NewGuid().ToString().Substring(0, 8), + EmailAddress = emailAddress }; var result = _tokenData.CreateNewToken(token); return result; } + public bool DeleteUserToken(int tokenId) + { + var result = _tokenData.DeleteToken(tokenId); + return result; + } public OperationResponse ResetUserPassword(LoginModel credentials) { //user might have forgotten their password. @@ -126,7 +147,8 @@ namespace CarCareTracker.Logic if (result) { return new OperationResponse { Success = true, Message = newPassword }; - } else + } + else { return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." }; } @@ -150,7 +172,8 @@ namespace CarCareTracker.Logic File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(existingUserConfig)); return true; } - public bool DeleteRootUserCredentials() { + public bool DeleteRootUserCredentials() + { var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath); var existingUserConfig = JsonSerializer.Deserialize(configFileContents); if (existingUserConfig is not null) diff --git a/Middleware/Authen.cs b/Middleware/Authen.cs index b486b59..4639980 100644 --- a/Middleware/Authen.cs +++ b/Middleware/Authen.cs @@ -99,7 +99,7 @@ namespace CarCareTracker.Middleware //if cookie is expired return AuthenticateResult.Fail("Expired credentials"); } - else if (authCookie.UserData.Id == default || string.IsNullOrWhiteSpace(authCookie.UserData.UserName)) + else if (authCookie.UserData is null || authCookie.UserData.Id == default || string.IsNullOrWhiteSpace(authCookie.UserData.UserName)) { return AuthenticateResult.Fail("Corrupted credentials"); } diff --git a/Models/Login/LoginModel.cs b/Models/Login/LoginModel.cs index a5424ed..167787e 100644 --- a/Models/Login/LoginModel.cs +++ b/Models/Login/LoginModel.cs @@ -4,6 +4,7 @@ { public string UserName { get; set; } public string Password { get; set; } + public string EmailAddress { get; set; } public string Token { get; set; } public bool IsPersistent { get; set; } = false; } diff --git a/Models/Login/Token.cs b/Models/Login/Token.cs index 1b438e1..78a7e19 100644 --- a/Models/Login/Token.cs +++ b/Models/Login/Token.cs @@ -4,5 +4,6 @@ { public int Id { get; set; } public string Body { get; set; } + public string EmailAddress { get; set; } } } diff --git a/Models/Login/UserData.cs b/Models/Login/UserData.cs index 8160473..29e5505 100644 --- a/Models/Login/UserData.cs +++ b/Models/Login/UserData.cs @@ -4,6 +4,7 @@ { public int Id { get; set; } public string UserName { get; set; } + public string EmailAddress { get; set; } public string Password { get; set; } public bool IsAdmin { get; set; } } diff --git a/Views/Admin/Index.cshtml b/Views/Admin/Index.cshtml index 6ea13d0..a9be61b 100644 --- a/Views/Admin/Index.cshtml +++ b/Views/Admin/Index.cshtml @@ -1,29 +1,36 @@ -@model AdminViewModel +@{ + ViewData["Title"] = "Admin"; +} +@model AdminViewModel
-
+
- - + + + @foreach (Token token in Model.Tokens) { - - - + + + + }
TokenDeleteTokenIssued ToDelete
@token.Body@token.Id
@token.Body@token.EmailAddress + +
-
+
@@ -52,11 +59,43 @@ function reloadPage() { window.location.reload(); } - function generateNewToken(){ - $.get('/Admin/GenerateNewToken', function (data) { + function deleteToken(tokenId) { + $.post(`/Admin/DeleteToken?tokenId=${tokenId}`, function (data) { if (data) { reloadPage(); } }); } + function copyToClipboard(e) { + var textToCopy = e.textContent; + navigator.clipboard.writeText(textToCopy); + successToast("Copied to Clipboard"); + } + function generateNewToken(){ + Swal.fire({ + title: 'Generate Token', + html: ` + + `, + confirmButtonText: 'Generate', + focusConfirm: false, + preConfirm: () => { + const emailAddress = $("#inputEmail").val(); + if (!emailAddress) { + Swal.showValidationMessage(`Please enter an email address`) + } + return { emailAddress } + }, + }).then(function (result) { + if (result.isConfirmed) { + $.get('/Admin/GenerateNewToken', {emailAddress: result.value.emailAddress}, function (data) { + if (data) { + reloadPage(); + } else { + errorToast("An error occurred, make sure the email address doesn't already have a token generated for it.") + } + }); + } + }); + } \ No newline at end of file diff --git a/Views/Login/Registration.cshtml b/Views/Login/Registration.cshtml index 2e12031..860e342 100644 --- a/Views/Login/Registration.cshtml +++ b/Views/Login/Registration.cshtml @@ -12,6 +12,10 @@ +
+ + +
diff --git a/wwwroot/js/login.js b/wwwroot/js/login.js index b2f002e..d737d40 100644 --- a/wwwroot/js/login.js +++ b/wwwroot/js/login.js @@ -14,7 +14,8 @@ function performRegistration() { var token = $("#inputToken").val(); var userName = $("#inputUserName").val(); var userPassword = $("#inputUserPassword").val(); - $.post('/Login/Register', { userName: userName, password: userPassword, token: token }, function (data) { + var userEmail = $("#inputEmail").val(); + $.post('/Login/Register', { userName: userName, password: userPassword, token: token, emailAddress: userEmail }, function (data) { if (data.success) { successToast(data.message); setTimeout(function () { window.location.href = '/Login/Index' }, 500); From c9d60910e519f93d12d58584fa480679ae2c4e58 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 11:12:37 -0700 Subject: [PATCH 03/25] added scaffolding for email sending methods --- Controllers/AdminController.cs | 30 ++++++++++++++++++++++++++++++ Logic/LoginLogic.cs | 3 ++- Models/Configuration/MailConfig.cs | 12 ++++++++++++ Models/Login/UserData.cs | 1 + 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 Models/Configuration/MailConfig.cs diff --git a/Controllers/AdminController.cs b/Controllers/AdminController.cs index 6197bba..a704d34 100644 --- a/Controllers/AdminController.cs +++ b/Controllers/AdminController.cs @@ -2,6 +2,8 @@ using CarCareTracker.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using System.Net; +using System.Net.Mail; namespace CarCareTracker.Controllers { @@ -25,6 +27,8 @@ namespace CarCareTracker.Controllers public IActionResult GenerateNewToken(string emailAddress) { var result = _loginLogic.GenerateUserToken(emailAddress); + //send an email test block. + SendEmail(emailAddress); return Json(result); } public IActionResult DeleteToken(int tokenId) @@ -32,5 +36,31 @@ namespace CarCareTracker.Controllers var result = _loginLogic.DeleteUserToken(tokenId); return Json(result); } + private bool SendEmail(string emailAddress) + { + var mailConfig = new MailConfig(); + string to = emailAddress; + string from = mailConfig.EmailFrom; + var server = mailConfig.EmailServer; + MailMessage message = new MailMessage(from, to); + message.Subject = "Using the new SMTP client."; + message.Body = @"Using this new feature, you can send an email message from an application very easily."; + SmtpClient client = new SmtpClient(server); + client.EnableSsl = mailConfig.UseSSL; + client.Port = mailConfig.Port; + client.Credentials = new NetworkCredential(mailConfig.Username, mailConfig.Password); + + try + { + client.Send(message); + return true; + } + catch (Exception ex) + { + Console.WriteLine("Exception caught in CreateTestMessage2(): {0}", + ex.ToString()); + return false; + } + } } } diff --git a/Logic/LoginLogic.cs b/Logic/LoginLogic.cs index 22a425c..cefd889 100644 --- a/Logic/LoginLogic.cs +++ b/Logic/LoginLogic.cs @@ -83,7 +83,8 @@ namespace CarCareTracker.Logic { Id = -1, UserName = credentials.UserName, - IsAdmin = true + IsAdmin = true, + IsRootUser = true }; } else diff --git a/Models/Configuration/MailConfig.cs b/Models/Configuration/MailConfig.cs new file mode 100644 index 0000000..23243e8 --- /dev/null +++ b/Models/Configuration/MailConfig.cs @@ -0,0 +1,12 @@ +namespace CarCareTracker.Models +{ + public class MailConfig + { + public string EmailServer { get; set; } + public string EmailFrom { get; set; } + public bool UseSSL { get; set; } + public int Port { get; set; } + public string Username { get; set; } + public string Password { get; set; } + } +} diff --git a/Models/Login/UserData.cs b/Models/Login/UserData.cs index 29e5505..6fb77a1 100644 --- a/Models/Login/UserData.cs +++ b/Models/Login/UserData.cs @@ -7,5 +7,6 @@ public string EmailAddress { get; set; } public string Password { get; set; } public bool IsAdmin { get; set; } + public bool IsRootUser { get; set; } = false; } } \ No newline at end of file From 2247b1b1db47de244d0cc74520214730ea944b9f Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 11:48:20 -0700 Subject: [PATCH 04/25] added ability to notify user that they have a registration token. --- Controllers/AdminController.cs | 32 +------------- Helper/MailHelper.cs | 77 ++++++++++++++++++++++++++++++++++ Helper/StaticHelper.cs | 1 + Logic/LoginLogic.cs | 26 +++++++++--- Program.cs | 1 + Views/Admin/Index.cshtml | 11 +++-- 6 files changed, 110 insertions(+), 38 deletions(-) create mode 100644 Helper/MailHelper.cs diff --git a/Controllers/AdminController.cs b/Controllers/AdminController.cs index a704d34..fe62c74 100644 --- a/Controllers/AdminController.cs +++ b/Controllers/AdminController.cs @@ -24,11 +24,9 @@ namespace CarCareTracker.Controllers }; return View(viewModel); } - public IActionResult GenerateNewToken(string emailAddress) + public IActionResult GenerateNewToken(string emailAddress, bool autoNotify) { - var result = _loginLogic.GenerateUserToken(emailAddress); - //send an email test block. - SendEmail(emailAddress); + var result = _loginLogic.GenerateUserToken(emailAddress, autoNotify); return Json(result); } public IActionResult DeleteToken(int tokenId) @@ -36,31 +34,5 @@ namespace CarCareTracker.Controllers var result = _loginLogic.DeleteUserToken(tokenId); return Json(result); } - private bool SendEmail(string emailAddress) - { - var mailConfig = new MailConfig(); - string to = emailAddress; - string from = mailConfig.EmailFrom; - var server = mailConfig.EmailServer; - MailMessage message = new MailMessage(from, to); - message.Subject = "Using the new SMTP client."; - message.Body = @"Using this new feature, you can send an email message from an application very easily."; - SmtpClient client = new SmtpClient(server); - client.EnableSsl = mailConfig.UseSSL; - client.Port = mailConfig.Port; - client.Credentials = new NetworkCredential(mailConfig.Username, mailConfig.Password); - - try - { - client.Send(message); - return true; - } - catch (Exception ex) - { - Console.WriteLine("Exception caught in CreateTestMessage2(): {0}", - ex.ToString()); - return false; - } - } } } diff --git a/Helper/MailHelper.cs b/Helper/MailHelper.cs new file mode 100644 index 0000000..ed86618 --- /dev/null +++ b/Helper/MailHelper.cs @@ -0,0 +1,77 @@ +using CarCareTracker.Models; +using System.Net.Mail; +using System.Net; + +namespace CarCareTracker.Helper +{ + public interface IMailHelper + { + OperationResponse NotifyUserForRegistration(string emailAddress, string token); + OperationResponse NotifyUserForPasswordReset(string emailAddress, string token); + } + public class MailHelper : IMailHelper + { + private readonly MailConfig mailConfig; + public MailHelper( + IConfiguration config + ) { + //load mailConfig from Configuration + mailConfig = config.GetSection("MailConfig").Get(); + } + public OperationResponse NotifyUserForRegistration(string emailAddress, string token) + { + if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token)) { + return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" }; + } + string emailSubject = "Your Registration Token for LubeLogger"; + string emailBody = $"A token has been generated on your behalf, please complete your registration for LubeLogger using the token: {token}"; + var result = SendEmail(emailAddress, emailSubject, emailBody); + if (result) + { + return new OperationResponse { Success = true, Message = "Email Sent!" }; + } else + { + return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage }; + } + } + public OperationResponse NotifyUserForPasswordReset(string emailAddress, string token) + { + if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token)) + { + return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" }; + } + string emailSubject = "Your Password Reset Token for LubeLogger"; + string emailBody = $"A token has been generated on your behalf, please reset your password for LubeLogger using the token: {token}"; + var result = SendEmail(emailAddress, emailSubject, emailBody); + if (result) + { + return new OperationResponse { Success = true, Message = "Email Sent!" }; + } + else + { + return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage }; + } + } + private bool SendEmail(string emailTo, string emailSubject, string emailBody) { + string to = emailTo; + string from = mailConfig.EmailFrom; + var server = mailConfig.EmailServer; + MailMessage message = new MailMessage(from, to); + message.Subject = emailSubject; + message.Body = emailBody; + SmtpClient client = new SmtpClient(server); + client.EnableSsl = mailConfig.UseSSL; + client.Port = mailConfig.Port; + client.Credentials = new NetworkCredential(mailConfig.Username, mailConfig.Password); + try + { + client.Send(message); + return true; + } + catch (Exception ex) + { + return false; + } + } + } +} diff --git a/Helper/StaticHelper.cs b/Helper/StaticHelper.cs index 2780549..04633cc 100644 --- a/Helper/StaticHelper.cs +++ b/Helper/StaticHelper.cs @@ -7,6 +7,7 @@ { public static string DbName = "data/cartracker.db"; public static string UserConfigPath = "config/userConfig.json"; + public static string GenericErrorMessage = "An error occurred, please try again later"; public static string TruncateStrings(string input, int maxLength = 25) { diff --git a/Logic/LoginLogic.cs b/Logic/LoginLogic.cs index cefd889..30f8e21 100644 --- a/Logic/LoginLogic.cs +++ b/Logic/LoginLogic.cs @@ -10,7 +10,7 @@ namespace CarCareTracker.Logic { public interface ILoginLogic { - bool GenerateUserToken(string emailAddress); + OperationResponse GenerateUserToken(string emailAddress, bool autoNotify); bool DeleteUserToken(int tokenId); OperationResponse RegisterNewUser(LoginModel credentials); OperationResponse ResetUserPassword(LoginModel credentials); @@ -25,10 +25,12 @@ namespace CarCareTracker.Logic { private readonly IUserRecordDataAccess _userData; private readonly ITokenRecordDataAccess _tokenData; - public LoginLogic(IUserRecordDataAccess userData, ITokenRecordDataAccess tokenData) + private readonly IMailHelper _mailHelper; + public LoginLogic(IUserRecordDataAccess userData, ITokenRecordDataAccess tokenData, IMailHelper mailHelper) { _userData = userData; _tokenData = tokenData; + _mailHelper = mailHelper; } public OperationResponse RegisterNewUser(LoginModel credentials) { @@ -113,13 +115,13 @@ namespace CarCareTracker.Logic var result = _tokenData.GetTokens(); return result; } - public bool GenerateUserToken(string emailAddress) + public OperationResponse GenerateUserToken(string emailAddress, bool autoNotify) { //check if email address already has a token attached to it. var existingToken = _tokenData.GetTokenRecordByEmailAddress(emailAddress); if (existingToken.Id != default) { - return false; + return new OperationResponse { Success = false, Message = "There is an existing token tied to this email address" }; } var token = new Token() { @@ -127,7 +129,21 @@ namespace CarCareTracker.Logic EmailAddress = emailAddress }; var result = _tokenData.CreateNewToken(token); - return result; + if (result && autoNotify) + { + result = _mailHelper.NotifyUserForRegistration(emailAddress, token.Body).Success; + if (!result) + { + return new OperationResponse { Success = false, Message = "Token Generated, but Email failed to send, please check your SMTP settings." }; + } + } + if (result) + { + return new OperationResponse { Success = true, Message = "Token Generated!" }; + } else + { + return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage }; + } } public bool DeleteUserToken(int tokenId) { diff --git a/Program.cs b/Program.cs index c521fbe..f8597c5 100644 --- a/Program.cs +++ b/Program.cs @@ -26,6 +26,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); //configur logic builder.Services.AddSingleton(); diff --git a/Views/Admin/Index.cshtml b/Views/Admin/Index.cshtml index a9be61b..d92583b 100644 --- a/Views/Admin/Index.cshtml +++ b/Views/Admin/Index.cshtml @@ -6,6 +6,10 @@
+
+ + +
@@ -88,11 +92,12 @@ }, }).then(function (result) { if (result.isConfirmed) { - $.get('/Admin/GenerateNewToken', {emailAddress: result.value.emailAddress}, function (data) { - if (data) { + var autoNotify = $("#enableAutoNotify").is(":checked"); + $.get('/Admin/GenerateNewToken', {emailAddress: result.value.emailAddress, autoNotify: autoNotify}, function (data) { + if (data.success) { reloadPage(); } else { - errorToast("An error occurred, make sure the email address doesn't already have a token generated for it.") + errorToast(data.message) } }); } From 8d989ee81c98c24d4488af9380a4a8fbc70da924 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 12:50:55 -0700 Subject: [PATCH 05/25] 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 @@ - + } @@ -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 @@
+ 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(); From 08104eef2ad81096089bcf161c2c0a35b001ca75 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 12:52:26 -0700 Subject: [PATCH 06/25] moved field around. --- Views/Login/ForgotPassword.cshtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Views/Login/ForgotPassword.cshtml b/Views/Login/ForgotPassword.cshtml index b4a9498..f75bb3a 100644 --- a/Views/Login/ForgotPassword.cshtml +++ b/Views/Login/ForgotPassword.cshtml @@ -16,10 +16,10 @@
From 8f3f71772b74991c2b961af02a8dc07f18c1cfb2 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 15:44:29 -0700 Subject: [PATCH 07/25] added data access methods for user access. --- Enum/UserAccessType.cs | 8 ++ .../Implementations/UserAccessDataAcces.cs | 91 +++++++++++++++++++ External/Implementations/VehicleDataAccess.cs | 10 +- External/Interfaces/IUserAccessDataAccess.cs | 15 +++ External/Interfaces/IVehicleDataAccess.cs | 1 + Middleware/Authen.cs | 14 ++- Models/User/UserAccess.cs | 10 ++ Models/{Login => User}/UserData.cs | 0 Program.cs | 1 + 9 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 Enum/UserAccessType.cs create mode 100644 External/Implementations/UserAccessDataAcces.cs create mode 100644 External/Interfaces/IUserAccessDataAccess.cs create mode 100644 Models/User/UserAccess.cs rename Models/{Login => User}/UserData.cs (100%) diff --git a/Enum/UserAccessType.cs b/Enum/UserAccessType.cs new file mode 100644 index 0000000..4f61d58 --- /dev/null +++ b/Enum/UserAccessType.cs @@ -0,0 +1,8 @@ +namespace CarCareTracker.Models +{ + public enum UserAccessType + { + Viewer = 0, + Editor = 1 + } +} diff --git a/External/Implementations/UserAccessDataAcces.cs b/External/Implementations/UserAccessDataAcces.cs new file mode 100644 index 0000000..2f364cf --- /dev/null +++ b/External/Implementations/UserAccessDataAcces.cs @@ -0,0 +1,91 @@ +using CarCareTracker.External.Interfaces; +using CarCareTracker.Helper; +using CarCareTracker.Models; +using LiteDB; + +namespace CarCareTracker.External.Implementations +{ + public class UserAccessDataAccess : IUserAccessDataAccess + { + private static string dbName = StaticHelper.DbName; + private static string tableName = "useraccessrecords"; + public UserAccess GetUserAccessByVehicleAndUserId(int vehicleId, int userId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + return table.FindOne(Query.And( + Query.EQ(nameof(UserAccess.VehicleId), vehicleId), + Query.EQ(nameof(UserAccess.UserId), userId) + )); + }; + } + /// + /// Gets a list of vehicles user have access to. + /// + /// + /// + public List GetUserAccessByUserId(int userId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + return table.Find(Query.EQ(nameof(UserAccess.UserId), userId)).ToList(); + }; + } + public List GetUserAccessByVehicleId(int vehicleId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + return table.Find(Query.EQ(nameof(UserAccess.VehicleId), vehicleId)).ToList(); + }; + } + public bool SaveUserAccess(UserAccess userAccess) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + table.Upsert(userAccess); + return true; + }; + } + public bool DeleteUserAccess(int userAccessId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + table.Delete(userAccessId); + return true; + }; + } + /// + /// Delete all access records when a vehicle is deleted. + /// + /// + /// + public bool DeleteAllAccessRecordsByVehicleId(int vehicleId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + table.DeleteMany(Query.EQ(nameof(UserAccess.VehicleId), vehicleId)); + return true; + }; + } + /// + /// Delee all access records when a user is deleted. + /// + /// + /// + public bool DeleteAllAccessRecordsByUserId(int userId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + table.DeleteMany(Query.EQ(nameof(UserAccess.UserId), userId)); + return true; + }; + } + } +} \ No newline at end of file diff --git a/External/Implementations/VehicleDataAccess.cs b/External/Implementations/VehicleDataAccess.cs index e2cd45a..766ac47 100644 --- a/External/Implementations/VehicleDataAccess.cs +++ b/External/Implementations/VehicleDataAccess.cs @@ -14,10 +14,18 @@ namespace CarCareTracker.External.Implementations using (var db = new LiteDatabase(dbName)) { var table = db.GetCollection(tableName); - table.Upsert(vehicle); + var result = table.Upsert(vehicle); return true; }; } + public Vehicle GetLastInsertedVehicle() + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + return table.FindOne(Query.All(Query.Descending)); + }; + } public bool DeleteVehicle(int vehicleId) { using (var db = new LiteDatabase(dbName)) diff --git a/External/Interfaces/IUserAccessDataAccess.cs b/External/Interfaces/IUserAccessDataAccess.cs new file mode 100644 index 0000000..c242884 --- /dev/null +++ b/External/Interfaces/IUserAccessDataAccess.cs @@ -0,0 +1,15 @@ +using CarCareTracker.Models; + +namespace CarCareTracker.External.Interfaces +{ + public interface IUserAccessDataAccess + { + UserAccess GetUserAccessByVehicleAndUserId(int vehicleId, int userId); + List GetUserAccessByUserId(int userId); + List GetUserAccessByVehicleId(int vehicleId); + bool SaveUserAccess(UserAccess userAccess); + bool DeleteUserAccess(int userAccessId); + bool DeleteAllAccessRecordsByVehicleId(int vehicleId); + bool DeleteAllAccessRecordsByUserId(int userId); + } +} diff --git a/External/Interfaces/IVehicleDataAccess.cs b/External/Interfaces/IVehicleDataAccess.cs index f54118f..4a5ff42 100644 --- a/External/Interfaces/IVehicleDataAccess.cs +++ b/External/Interfaces/IVehicleDataAccess.cs @@ -5,6 +5,7 @@ namespace CarCareTracker.External.Interfaces public interface IVehicleDataAccess { public bool SaveVehicle(Vehicle vehicle); + public Vehicle GetLastInsertedVehicle(); public bool DeleteVehicle(int vehicleId); public List GetVehicles(); public Vehicle GetVehicleById(int vehicleId); diff --git a/Middleware/Authen.cs b/Middleware/Authen.cs index 4639980..3d1cc6f 100644 --- a/Middleware/Authen.cs +++ b/Middleware/Authen.cs @@ -39,7 +39,8 @@ namespace CarCareTracker.Middleware var appIdentity = new ClaimsIdentity("Custom"); var userIdentity = new List { - new(ClaimTypes.Name, "admin") + new(ClaimTypes.Name, "admin"), + new(ClaimTypes.Role, nameof(UserData.IsRootUser)) }; appIdentity.AddClaims(userIdentity); AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name); @@ -64,7 +65,8 @@ namespace CarCareTracker.Middleware if (splitString.Count() != 2) { return AuthenticateResult.Fail("Invalid credentials"); - } else + } + else { var userData = _loginLogic.ValidateUserCredentials(new LoginModel { UserName = splitString[0], Password = splitString[1] }); if (userData.Id != default) @@ -78,6 +80,10 @@ namespace CarCareTracker.Middleware { userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsAdmin))); } + if (userData.IsRootUser) + { + userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser))); + } appIdentity.AddClaims(userIdentity); AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name); return AuthenticateResult.Success(ticket); @@ -114,6 +120,10 @@ namespace CarCareTracker.Middleware { userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsAdmin))); } + if (authCookie.UserData.IsRootUser) + { + userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser))); + } appIdentity.AddClaims(userIdentity); AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name); return AuthenticateResult.Success(ticket); diff --git a/Models/User/UserAccess.cs b/Models/User/UserAccess.cs new file mode 100644 index 0000000..9940e0d --- /dev/null +++ b/Models/User/UserAccess.cs @@ -0,0 +1,10 @@ +namespace CarCareTracker.Models +{ + public class UserAccess + { + public int Id { get; set; } + public int UserId { get; set; } + public int VehicleId { get; set; } + public UserAccessType AccessType { get; set; } + } +} diff --git a/Models/Login/UserData.cs b/Models/User/UserData.cs similarity index 100% rename from Models/Login/UserData.cs rename to Models/User/UserData.cs diff --git a/Program.cs b/Program.cs index f8597c5..2edb61f 100644 --- a/Program.cs +++ b/Program.cs @@ -20,6 +20,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); //configure helpers builder.Services.AddSingleton(); From 00fd499805f81717bc4516327f29672e43136691 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 16:34:39 -0700 Subject: [PATCH 08/25] added user logic. --- Controllers/HomeController.cs | 21 ++++++++++++--- Logic/UserLogic.cs | 51 +++++++++++++++++++++++++++++++++++ Middleware/Authen.cs | 10 ++++--- Program.cs | 3 ++- 4 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 Logic/UserLogic.cs diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs index 01f750a..b2dfbe2 100644 --- a/Controllers/HomeController.cs +++ b/Controllers/HomeController.cs @@ -9,6 +9,9 @@ using System.Linq.Expressions; using Microsoft.Extensions.Logging; using CarCareTracker.Helper; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using System.Security.Claims; +using CarCareTracker.Logic; namespace CarCareTracker.Controllers { @@ -17,17 +20,23 @@ namespace CarCareTracker.Controllers { private readonly ILogger _logger; private readonly IVehicleDataAccess _dataAccess; - private readonly IFileHelper _fileHelper; + private readonly IUserLogic _userLogic; private readonly IConfiguration _config; - public HomeController(ILogger logger, IVehicleDataAccess dataAccess, IFileHelper fileHelper, IConfiguration configuration) + public HomeController(ILogger logger, + IVehicleDataAccess dataAccess, + IUserLogic userLogic, + IConfiguration configuration) { _logger = logger; _dataAccess = dataAccess; - _fileHelper = fileHelper; _config = configuration; + _userLogic = userLogic; + } + private int GetUserID() + { + return int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)); } - public IActionResult Index(string tab = "garage") { return View(model: tab); @@ -35,6 +44,10 @@ namespace CarCareTracker.Controllers public IActionResult Garage() { var vehiclesStored = _dataAccess.GetVehicles(); + if (!User.IsInRole(nameof(UserData.IsRootUser))) + { + vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID()); + } return PartialView("_GarageDisplay", vehiclesStored); } public IActionResult Settings() diff --git a/Logic/UserLogic.cs b/Logic/UserLogic.cs new file mode 100644 index 0000000..5e37561 --- /dev/null +++ b/Logic/UserLogic.cs @@ -0,0 +1,51 @@ +using CarCareTracker.External.Interfaces; +using CarCareTracker.Models; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace CarCareTracker.Logic +{ + public interface IUserLogic + { + List FilterUserVehicles(List results, int userId); + bool UserCanAccessVehicle(int userId, int vehicleId); + bool UserCanEditVehicle(int userId, int vehicleId); + } + public class UserLogic: IUserLogic + { + private readonly IUserAccessDataAccess _userAccess; + public UserLogic(IUserAccessDataAccess userAccess) { + _userAccess = userAccess; + } + public List FilterUserVehicles(List results, int userId) + { + var accessibleVehicles = _userAccess.GetUserAccessByUserId(userId); + if (accessibleVehicles.Any()) + { + var vehicleIds = accessibleVehicles.Select(x => x.VehicleId); + return results.Where(x => vehicleIds.Contains(x.Id)).ToList(); + } + else + { + return new List(); + } + } + public bool UserCanAccessVehicle(int userId, int vehicleId) + { + var userAccess = _userAccess.GetUserAccessByVehicleAndUserId(userId, vehicleId); + if (userAccess != null) + { + return true; + } + return false; + } + public bool UserCanEditVehicle(int userId, int vehicleId) + { + var userAccess = _userAccess.GetUserAccessByVehicleAndUserId(userId, vehicleId); + if (userAccess != null && userAccess.AccessType == UserAccessType.Editor) + { + return true; + } + return false; + } + } +} diff --git a/Middleware/Authen.cs b/Middleware/Authen.cs index 3d1cc6f..a4f82fa 100644 --- a/Middleware/Authen.cs +++ b/Middleware/Authen.cs @@ -74,7 +74,8 @@ namespace CarCareTracker.Middleware var appIdentity = new ClaimsIdentity("Custom"); var userIdentity = new List { - new(ClaimTypes.Name, splitString[0]) + new(ClaimTypes.Name, splitString[0]), + new(ClaimTypes.NameIdentifier, userData.Id.ToString()) }; if (userData.IsAdmin) { @@ -113,9 +114,10 @@ namespace CarCareTracker.Middleware { var appIdentity = new ClaimsIdentity("Custom"); var userIdentity = new List - { - new(ClaimTypes.Name, authCookie.UserData.UserName) - }; + { + new(ClaimTypes.Name, authCookie.UserData.UserName), + new(ClaimTypes.NameIdentifier, authCookie.UserData.Id.ToString()) + }; if (authCookie.UserData.IsAdmin) { userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsAdmin))); diff --git a/Program.cs b/Program.cs index 2edb61f..d03c217 100644 --- a/Program.cs +++ b/Program.cs @@ -29,8 +29,9 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); -//configur logic +//configure logic builder.Services.AddSingleton(); +builder.Services.AddSingleton(); if (!Directory.Exists("data")) { From a1b2b40abed2cebceaa0a9e0141ba6cf78fb90c8 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 17:49:48 -0700 Subject: [PATCH 09/25] reshaped user access object, added 401 page. --- Controllers/HomeController.cs | 6 --- Controllers/VehicleController.cs | 30 ++++++++++++++ .../Implementations/UserAccessDataAcces.cs | 27 ++++++------- External/Implementations/VehicleDataAccess.cs | 8 ---- External/Interfaces/IUserAccessDataAccess.cs | 2 +- External/Interfaces/IVehicleDataAccess.cs | 1 - Logic/UserLogic.cs | 39 ++++++++++++++++++- Middleware/Authen.cs | 1 + Models/User/UserAccess.cs | 7 +++- Views/Shared/401.cshtml | 1 + 10 files changed, 88 insertions(+), 34 deletions(-) create mode 100644 Views/Shared/401.cshtml diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs index b2dfbe2..1baa0d8 100644 --- a/Controllers/HomeController.cs +++ b/Controllers/HomeController.cs @@ -1,15 +1,9 @@ using CarCareTracker.External.Interfaces; using CarCareTracker.Models; -using LiteDB; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; -using static System.Net.Mime.MediaTypeNames; -using System.Drawing; -using System.Linq.Expressions; -using Microsoft.Extensions.Logging; using CarCareTracker.Helper; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; using System.Security.Claims; using CarCareTracker.Logic; diff --git a/Controllers/VehicleController.cs b/Controllers/VehicleController.cs index 62098e9..d5dc9cc 100644 --- a/Controllers/VehicleController.cs +++ b/Controllers/VehicleController.cs @@ -7,6 +7,8 @@ using CsvHelper; using System.Globalization; using Microsoft.AspNetCore.Authorization; using CarCareTracker.MapProfile; +using System.Security.Claims; +using CarCareTracker.Logic; namespace CarCareTracker.Controllers { @@ -29,6 +31,7 @@ namespace CarCareTracker.Controllers private readonly IGasHelper _gasHelper; private readonly IReminderHelper _reminderHelper; private readonly IReportHelper _reportHelper; + private readonly IUserLogic _userLogic; public VehicleController(ILogger logger, IFileHelper fileHelper, @@ -43,6 +46,7 @@ namespace CarCareTracker.Controllers ITaxRecordDataAccess taxRecordDataAccess, IReminderRecordDataAccess reminderRecordDataAccess, IUpgradeRecordDataAccess upgradeRecordDataAccess, + IUserLogic userLogic, IWebHostEnvironment webEnv, IConfiguration config) { @@ -59,13 +63,22 @@ namespace CarCareTracker.Controllers _taxRecordDataAccess = taxRecordDataAccess; _reminderRecordDataAccess = reminderRecordDataAccess; _upgradeRecordDataAccess = upgradeRecordDataAccess; + _userLogic = userLogic; _webEnv = webEnv; _config = config; _useDescending = bool.Parse(config[nameof(UserConfig.UseDescending)]); } + private int GetUserID() + { + return int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)); + } [HttpGet] public IActionResult Index(int vehicleId) { + if (!_userLogic.UserCanAccessVehicle(GetUserID(), vehicleId)) + { + return View("401"); + } var data = _dataAccess.GetVehicleById(vehicleId); return View(data); } @@ -77,6 +90,10 @@ namespace CarCareTracker.Controllers [HttpGet] public IActionResult GetEditVehiclePartialViewById(int vehicleId) { + if (!_userLogic.UserCanEditVehicle(GetUserID(), vehicleId)) + { + return View("401"); + } var data = _dataAccess.GetVehicleById(vehicleId); return PartialView("_VehicleModal", data); } @@ -85,10 +102,22 @@ namespace CarCareTracker.Controllers { try { + bool isNewAddition = vehicleInput.Id == default; + if (!isNewAddition) + { + if (!_userLogic.UserCanEditVehicle(GetUserID(), vehicleInput.Id)) + { + return View("401"); + } + } //move image from temp folder to images folder. vehicleInput.ImageLocation = _fileHelper.MoveFileFromTemp(vehicleInput.ImageLocation, "images/"); //save vehicle. var result = _dataAccess.SaveVehicle(vehicleInput); + if (isNewAddition) + { + _userLogic.AddUserAccessToVehicle(GetUserID(), vehicleInput.Id, UserAccessType.Editor); + } return Json(result); } catch (Exception ex) @@ -108,6 +137,7 @@ namespace CarCareTracker.Controllers _noteDataAccess.DeleteAllNotesByVehicleId(vehicleId) && _reminderRecordDataAccess.DeleteAllReminderRecordsByVehicleId(vehicleId) && _upgradeRecordDataAccess.DeleteAllUpgradeRecordsByVehicleId(vehicleId) && + _userLogic.DeleteAllAccessToVehicle(vehicleId) && _dataAccess.DeleteVehicle(vehicleId); return Json(result); } diff --git a/External/Implementations/UserAccessDataAcces.cs b/External/Implementations/UserAccessDataAcces.cs index 2f364cf..6b4012f 100644 --- a/External/Implementations/UserAccessDataAcces.cs +++ b/External/Implementations/UserAccessDataAcces.cs @@ -9,17 +9,6 @@ namespace CarCareTracker.External.Implementations { private static string dbName = StaticHelper.DbName; private static string tableName = "useraccessrecords"; - public UserAccess GetUserAccessByVehicleAndUserId(int vehicleId, int userId) - { - using (var db = new LiteDatabase(dbName)) - { - var table = db.GetCollection(tableName); - return table.FindOne(Query.And( - Query.EQ(nameof(UserAccess.VehicleId), vehicleId), - Query.EQ(nameof(UserAccess.UserId), userId) - )); - }; - } /// /// Gets a list of vehicles user have access to. /// @@ -30,7 +19,15 @@ namespace CarCareTracker.External.Implementations using (var db = new LiteDatabase(dbName)) { var table = db.GetCollection(tableName); - return table.Find(Query.EQ(nameof(UserAccess.UserId), userId)).ToList(); + return table.Find(x=>x.Id.UserId == userId).ToList(); + }; + } + public UserAccess GetUserAccessByVehicleAndUserId(int userId, int vehicleId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + return table.Find(x => x.Id.UserId == userId && x.Id.VehicleId == vehicleId).FirstOrDefault(); }; } public List GetUserAccessByVehicleId(int vehicleId) @@ -38,7 +35,7 @@ namespace CarCareTracker.External.Implementations using (var db = new LiteDatabase(dbName)) { var table = db.GetCollection(tableName); - return table.Find(Query.EQ(nameof(UserAccess.VehicleId), vehicleId)).ToList(); + return table.Find(x => x.Id.VehicleId == vehicleId).ToList(); }; } public bool SaveUserAccess(UserAccess userAccess) @@ -69,7 +66,7 @@ namespace CarCareTracker.External.Implementations using (var db = new LiteDatabase(dbName)) { var table = db.GetCollection(tableName); - table.DeleteMany(Query.EQ(nameof(UserAccess.VehicleId), vehicleId)); + table.DeleteMany(x=>x.Id.VehicleId == vehicleId); return true; }; } @@ -83,7 +80,7 @@ namespace CarCareTracker.External.Implementations using (var db = new LiteDatabase(dbName)) { var table = db.GetCollection(tableName); - table.DeleteMany(Query.EQ(nameof(UserAccess.UserId), userId)); + table.DeleteMany(x => x.Id.UserId == userId); return true; }; } diff --git a/External/Implementations/VehicleDataAccess.cs b/External/Implementations/VehicleDataAccess.cs index 766ac47..db8afab 100644 --- a/External/Implementations/VehicleDataAccess.cs +++ b/External/Implementations/VehicleDataAccess.cs @@ -18,14 +18,6 @@ namespace CarCareTracker.External.Implementations return true; }; } - public Vehicle GetLastInsertedVehicle() - { - using (var db = new LiteDatabase(dbName)) - { - var table = db.GetCollection(tableName); - return table.FindOne(Query.All(Query.Descending)); - }; - } public bool DeleteVehicle(int vehicleId) { using (var db = new LiteDatabase(dbName)) diff --git a/External/Interfaces/IUserAccessDataAccess.cs b/External/Interfaces/IUserAccessDataAccess.cs index c242884..c1ccbfc 100644 --- a/External/Interfaces/IUserAccessDataAccess.cs +++ b/External/Interfaces/IUserAccessDataAccess.cs @@ -4,8 +4,8 @@ namespace CarCareTracker.External.Interfaces { public interface IUserAccessDataAccess { - UserAccess GetUserAccessByVehicleAndUserId(int vehicleId, int userId); List GetUserAccessByUserId(int userId); + UserAccess GetUserAccessByVehicleAndUserId(int userId, int vehicleId); List GetUserAccessByVehicleId(int vehicleId); bool SaveUserAccess(UserAccess userAccess); bool DeleteUserAccess(int userAccessId); diff --git a/External/Interfaces/IVehicleDataAccess.cs b/External/Interfaces/IVehicleDataAccess.cs index 4a5ff42..f54118f 100644 --- a/External/Interfaces/IVehicleDataAccess.cs +++ b/External/Interfaces/IVehicleDataAccess.cs @@ -5,7 +5,6 @@ namespace CarCareTracker.External.Interfaces public interface IVehicleDataAccess { public bool SaveVehicle(Vehicle vehicle); - public Vehicle GetLastInsertedVehicle(); public bool DeleteVehicle(int vehicleId); public List GetVehicles(); public Vehicle GetVehicleById(int vehicleId); diff --git a/Logic/UserLogic.cs b/Logic/UserLogic.cs index 5e37561..b83075b 100644 --- a/Logic/UserLogic.cs +++ b/Logic/UserLogic.cs @@ -6,9 +6,12 @@ namespace CarCareTracker.Logic { public interface IUserLogic { + bool AddUserAccessToVehicle(int userId, int vehicleId, UserAccessType accessType); List FilterUserVehicles(List results, int userId); bool UserCanAccessVehicle(int userId, int vehicleId); bool UserCanEditVehicle(int userId, int vehicleId); + bool DeleteAllAccessToVehicle(int vehicleId); + bool DeleteAllAccessToUser(int userId); } public class UserLogic: IUserLogic { @@ -16,12 +19,28 @@ namespace CarCareTracker.Logic public UserLogic(IUserAccessDataAccess userAccess) { _userAccess = userAccess; } + public bool AddUserAccessToVehicle(int userId, int vehicleId, UserAccessType accessType) + { + if (userId == -1) + { + return true; + } + var userVehicle = new UserVehicle { UserId = userId, VehicleId = vehicleId }; + var userAccess = new UserAccess { Id = userVehicle, AccessType = accessType }; + var result = _userAccess.SaveUserAccess(userAccess); + return result; + } public List FilterUserVehicles(List results, int userId) { + //user is root user. + if (userId == -1) + { + return results; + } var accessibleVehicles = _userAccess.GetUserAccessByUserId(userId); if (accessibleVehicles.Any()) { - var vehicleIds = accessibleVehicles.Select(x => x.VehicleId); + var vehicleIds = accessibleVehicles.Select(x => x.Id.VehicleId); return results.Where(x => vehicleIds.Contains(x.Id)).ToList(); } else @@ -31,6 +50,10 @@ namespace CarCareTracker.Logic } public bool UserCanAccessVehicle(int userId, int vehicleId) { + if (userId == -1) + { + return true; + } var userAccess = _userAccess.GetUserAccessByVehicleAndUserId(userId, vehicleId); if (userAccess != null) { @@ -40,6 +63,10 @@ namespace CarCareTracker.Logic } public bool UserCanEditVehicle(int userId, int vehicleId) { + if (userId == -1) + { + return true; + } var userAccess = _userAccess.GetUserAccessByVehicleAndUserId(userId, vehicleId); if (userAccess != null && userAccess.AccessType == UserAccessType.Editor) { @@ -47,5 +74,15 @@ namespace CarCareTracker.Logic } return false; } + public bool DeleteAllAccessToVehicle(int vehicleId) + { + var result = _userAccess.DeleteAllAccessRecordsByVehicleId(vehicleId); + return result; + } + public bool DeleteAllAccessToUser(int userId) + { + var result = _userAccess.DeleteAllAccessRecordsByUserId(userId); + return result; + } } } diff --git a/Middleware/Authen.cs b/Middleware/Authen.cs index a4f82fa..975b395 100644 --- a/Middleware/Authen.cs +++ b/Middleware/Authen.cs @@ -40,6 +40,7 @@ namespace CarCareTracker.Middleware var userIdentity = new List { new(ClaimTypes.Name, "admin"), + new(ClaimTypes.NameIdentifier, "-1"), new(ClaimTypes.Role, nameof(UserData.IsRootUser)) }; appIdentity.AddClaims(userIdentity); diff --git a/Models/User/UserAccess.cs b/Models/User/UserAccess.cs index 9940e0d..960cde7 100644 --- a/Models/User/UserAccess.cs +++ b/Models/User/UserAccess.cs @@ -1,10 +1,13 @@ namespace CarCareTracker.Models { - public class UserAccess + public class UserVehicle { - public int Id { get; set; } public int UserId { get; set; } public int VehicleId { get; set; } + } + public class UserAccess + { + public UserVehicle Id { get; set; } public UserAccessType AccessType { get; set; } } } diff --git a/Views/Shared/401.cshtml b/Views/Shared/401.cshtml new file mode 100644 index 0000000..6a9e903 --- /dev/null +++ b/Views/Shared/401.cshtml @@ -0,0 +1 @@ +

Access Denied

\ No newline at end of file From 90fa6ad5fc2c2d2bb486fc5408d403cb70c2b773 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 17:55:02 -0700 Subject: [PATCH 10/25] fixed method to delete user access. --- External/Implementations/UserAccessDataAcces.cs | 4 ++-- External/Interfaces/IUserAccessDataAccess.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/External/Implementations/UserAccessDataAcces.cs b/External/Implementations/UserAccessDataAcces.cs index 6b4012f..d8b7fea 100644 --- a/External/Implementations/UserAccessDataAcces.cs +++ b/External/Implementations/UserAccessDataAcces.cs @@ -47,12 +47,12 @@ namespace CarCareTracker.External.Implementations return true; }; } - public bool DeleteUserAccess(int userAccessId) + public bool DeleteUserAccess(int userId, int vehicleId) { using (var db = new LiteDatabase(dbName)) { var table = db.GetCollection(tableName); - table.Delete(userAccessId); + table.DeleteMany(x => x.Id.UserId == userId && x.Id.VehicleId == vehicleId); return true; }; } diff --git a/External/Interfaces/IUserAccessDataAccess.cs b/External/Interfaces/IUserAccessDataAccess.cs index c1ccbfc..ee4d941 100644 --- a/External/Interfaces/IUserAccessDataAccess.cs +++ b/External/Interfaces/IUserAccessDataAccess.cs @@ -8,7 +8,7 @@ namespace CarCareTracker.External.Interfaces UserAccess GetUserAccessByVehicleAndUserId(int userId, int vehicleId); List GetUserAccessByVehicleId(int vehicleId); bool SaveUserAccess(UserAccess userAccess); - bool DeleteUserAccess(int userAccessId); + bool DeleteUserAccess(int userId, int vehicleId); bool DeleteAllAccessRecordsByVehicleId(int vehicleId); bool DeleteAllAccessRecordsByUserId(int userId); } From c972f9c8a26f6484fb7c2ffe20b230972bacfeaf Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 18:19:52 -0700 Subject: [PATCH 11/25] added collaborator view. --- Controllers/VehicleController.cs | 3 +++ Logic/UserLogic.cs | 23 ++++++++++++++++++++++- Models/Report/ReportViewModel.cs | 1 + Models/User/UserCollaborator.cs | 9 +++++++++ Views/Vehicle/_Collaborators.cshtml | 7 +++++++ Views/Vehicle/_Report.cshtml | 12 ++++++++++-- 6 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 Models/User/UserCollaborator.cs create mode 100644 Views/Vehicle/_Collaborators.cshtml diff --git a/Controllers/VehicleController.cs b/Controllers/VehicleController.cs index d5dc9cc..b6590e3 100644 --- a/Controllers/VehicleController.cs +++ b/Controllers/VehicleController.cs @@ -672,6 +672,9 @@ namespace CarCareTracker.Controllers { viewModel.Years.Add(DateTime.Now.AddYears(i * -1).Year); } + //get collaborators + var collaborators = _userLogic.GetCollaboratorsForVehicle(vehicleId); + viewModel.Collaborators = collaborators; return PartialView("_Report", viewModel); } [HttpGet] diff --git a/Logic/UserLogic.cs b/Logic/UserLogic.cs index b83075b..187904a 100644 --- a/Logic/UserLogic.cs +++ b/Logic/UserLogic.cs @@ -6,6 +6,7 @@ namespace CarCareTracker.Logic { public interface IUserLogic { + List GetCollaboratorsForVehicle(int vehicleId); bool AddUserAccessToVehicle(int userId, int vehicleId, UserAccessType accessType); List FilterUserVehicles(List results, int userId); bool UserCanAccessVehicle(int userId, int vehicleId); @@ -16,8 +17,28 @@ namespace CarCareTracker.Logic public class UserLogic: IUserLogic { private readonly IUserAccessDataAccess _userAccess; - public UserLogic(IUserAccessDataAccess userAccess) { + private readonly IUserRecordDataAccess _userData; + public UserLogic(IUserAccessDataAccess userAccess, + IUserRecordDataAccess userData) { _userAccess = userAccess; + _userData = userData; + } + public List GetCollaboratorsForVehicle(int vehicleId) + { + var result = _userAccess.GetUserAccessByVehicleId(vehicleId); + var convertedResult = new List(); + //convert useraccess to usercollaborator + foreach(UserAccess userAccess in result) + { + var userCollaborator = new UserCollaborator + { + UserName = _userData.GetUserRecordById(userAccess.Id.UserId).UserName, + AccessType = userAccess.AccessType, + UserVehicle = userAccess.Id + }; + convertedResult.Add(userCollaborator); + } + return convertedResult; } public bool AddUserAccessToVehicle(int userId, int vehicleId, UserAccessType accessType) { diff --git a/Models/Report/ReportViewModel.cs b/Models/Report/ReportViewModel.cs index 77d4be2..de2b2c8 100644 --- a/Models/Report/ReportViewModel.cs +++ b/Models/Report/ReportViewModel.cs @@ -6,5 +6,6 @@ public CostMakeUpForVehicle CostMakeUpForVehicle { get; set; } = new CostMakeUpForVehicle(); public ReminderMakeUpForVehicle ReminderMakeUpForVehicle { get; set; } = new ReminderMakeUpForVehicle(); public List Years { get; set; } = new List(); + public List Collaborators { get; set; } = new List(); } } diff --git a/Models/User/UserCollaborator.cs b/Models/User/UserCollaborator.cs new file mode 100644 index 0000000..d46c88e --- /dev/null +++ b/Models/User/UserCollaborator.cs @@ -0,0 +1,9 @@ +namespace CarCareTracker.Models +{ + public class UserCollaborator + { + public string UserName { get; set; } + public UserAccessType AccessType { get; set; } + public UserVehicle UserVehicle { get; set; } + } +} diff --git a/Views/Vehicle/_Collaborators.cshtml b/Views/Vehicle/_Collaborators.cshtml new file mode 100644 index 0000000..abe4f93 --- /dev/null +++ b/Views/Vehicle/_Collaborators.cshtml @@ -0,0 +1,7 @@ +@model List +
    + @foreach (UserCollaborator user in Model) + { +
  • @user.UserName
  • + } +
\ No newline at end of file diff --git a/Views/Vehicle/_Report.cshtml b/Views/Vehicle/_Report.cshtml index a97f42b..97a3a2f 100644 --- a/Views/Vehicle/_Report.cshtml +++ b/Views/Vehicle/_Report.cshtml @@ -69,8 +69,16 @@
-
- +
+ +
+
+
+ +
+
+
+ @await Html.PartialAsync("_Collaborators", Model.Collaborators)
From 4388df71f34966203fe664aee235e77f199f380a Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 20:13:12 -0700 Subject: [PATCH 12/25] added action filter attribute --- Controllers/Error.cs | 12 ++++++++++ Controllers/VehicleController.cs | 37 ++++++++++++++++++++++------- Enum/UserAccessType.cs | 8 ------- Filter/CollaboratorFilter.cs | 28 ++++++++++++++++++++++ Logic/UserLogic.cs | 27 +++++++++++++++++---- Models/User/UserAccess.cs | 1 - Models/User/UserCollaborator.cs | 1 - Views/Vehicle/_Collaborators.cshtml | 5 ++++ 8 files changed, 95 insertions(+), 24 deletions(-) create mode 100644 Controllers/Error.cs delete mode 100644 Enum/UserAccessType.cs create mode 100644 Filter/CollaboratorFilter.cs diff --git a/Controllers/Error.cs b/Controllers/Error.cs new file mode 100644 index 0000000..5f72cb0 --- /dev/null +++ b/Controllers/Error.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; + +namespace CarCareTracker.Controllers +{ + public class ErrorController : Controller + { + public IActionResult Unauthorized() + { + return View("401"); + } + } +} diff --git a/Controllers/VehicleController.cs b/Controllers/VehicleController.cs index b6590e3..cbb8392 100644 --- a/Controllers/VehicleController.cs +++ b/Controllers/VehicleController.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Authorization; using CarCareTracker.MapProfile; using System.Security.Claims; using CarCareTracker.Logic; +using CarCareTracker.Filter; namespace CarCareTracker.Controllers { @@ -72,13 +73,10 @@ namespace CarCareTracker.Controllers { return int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)); } + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] public IActionResult Index(int vehicleId) { - if (!_userLogic.UserCanAccessVehicle(GetUserID(), vehicleId)) - { - return View("401"); - } var data = _dataAccess.GetVehicleById(vehicleId); return View(data); } @@ -87,13 +85,10 @@ namespace CarCareTracker.Controllers { return PartialView("_VehicleModal", new Vehicle()); } + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] public IActionResult GetEditVehiclePartialViewById(int vehicleId) { - if (!_userLogic.UserCanEditVehicle(GetUserID(), vehicleId)) - { - return View("401"); - } var data = _dataAccess.GetVehicleById(vehicleId); return PartialView("_VehicleModal", data); } @@ -116,7 +111,7 @@ namespace CarCareTracker.Controllers var result = _dataAccess.SaveVehicle(vehicleInput); if (isNewAddition) { - _userLogic.AddUserAccessToVehicle(GetUserID(), vehicleInput.Id, UserAccessType.Editor); + _userLogic.AddUserAccessToVehicle(GetUserID(), vehicleInput.Id); } return Json(result); } @@ -126,6 +121,7 @@ namespace CarCareTracker.Controllers return Json(false); } } + [TypeFilter(typeof(CollaboratorFilter))] [HttpPost] public IActionResult DeleteVehicle(int vehicleId) { @@ -147,6 +143,7 @@ namespace CarCareTracker.Controllers { return PartialView("_BulkDataImporter", mode); } + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] public IActionResult ExportFromVehicleToCsv(int vehicleId, ImportMode mode) { @@ -250,6 +247,7 @@ namespace CarCareTracker.Controllers } return Json(false); } + [TypeFilter(typeof(CollaboratorFilter))] [HttpPost] public IActionResult ImportToVehicleIdFromCsv(int vehicleId, ImportMode mode, string fileName) { @@ -383,6 +381,7 @@ namespace CarCareTracker.Controllers } #endregion #region "Gas Records" + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] public IActionResult GetGasRecordsByVehicleId(int vehicleId) { @@ -449,6 +448,7 @@ namespace CarCareTracker.Controllers } #endregion #region "Service Records" + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] public IActionResult GetServiceRecordsByVehicleId(int vehicleId) { @@ -502,6 +502,7 @@ namespace CarCareTracker.Controllers } #endregion #region "Collision Records" + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] public IActionResult GetCollisionRecordsByVehicleId(int vehicleId) { @@ -555,6 +556,7 @@ namespace CarCareTracker.Controllers } #endregion #region "Tax Records" + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] public IActionResult GetTaxRecordsByVehicleId(int vehicleId) { @@ -607,6 +609,7 @@ namespace CarCareTracker.Controllers } #endregion #region "Reports" + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] public IActionResult GetReportPartialView(int vehicleId) { @@ -677,6 +680,14 @@ namespace CarCareTracker.Controllers viewModel.Collaborators = collaborators; return PartialView("_Report", viewModel); } + [TypeFilter(typeof(CollaboratorFilter))] + [HttpGet] + public IActionResult GetCollaboratorsForVehicle(int vehicleId) + { + var result = _userLogic.GetCollaboratorsForVehicle(vehicleId); + return PartialView("_Collaborators", result); + } + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] public IActionResult GetCostMakeUpForVehicle(int vehicleId, int year = 0) { @@ -703,6 +714,7 @@ namespace CarCareTracker.Controllers }; return PartialView("_CostMakeUpReport", viewModel); } + [TypeFilter(typeof(CollaboratorFilter))] public IActionResult GetReminderMakeUpByVehicle(int vehicleId, int daysToAdd) { var reminders = GetRemindersAndUrgency(vehicleId, DateTime.Now.AddDays(daysToAdd)); @@ -715,6 +727,7 @@ namespace CarCareTracker.Controllers }; return PartialView("_ReminderMakeUpReport", viewModel); } + [TypeFilter(typeof(CollaboratorFilter))] public IActionResult GetVehicleHistory(int vehicleId) { var vehicleHistory = new VehicleHistoryViewModel(); @@ -778,6 +791,7 @@ namespace CarCareTracker.Controllers vehicleHistory.VehicleHistory = reportData.OrderBy(x=>x.Date).ThenBy(x=>x.Odometer).ToList(); return PartialView("_VehicleHistory", vehicleHistory); } + [TypeFilter(typeof(CollaboratorFilter))] [HttpPost] public IActionResult GetCostByMonthByVehicle(int vehicleId, List selectedMetrics, int year = 0) { @@ -816,6 +830,7 @@ namespace CarCareTracker.Controllers } #endregion #region "Reminders" + [TypeFilter(typeof(CollaboratorFilter))] private int GetMaxMileage(int vehicleId) { var numbersArray = new List(); @@ -848,6 +863,7 @@ namespace CarCareTracker.Controllers List results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, dateCompare); return results; } + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] public IActionResult GetVehicleHaveUrgentOrPastDueReminders(int vehicleId) { @@ -858,6 +874,7 @@ namespace CarCareTracker.Controllers } return Json(false); } + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] public IActionResult GetReminderRecordsByVehicleId(int vehicleId) { @@ -908,6 +925,7 @@ namespace CarCareTracker.Controllers } #endregion #region "Upgrade Records" + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] public IActionResult GetUpgradeRecordsByVehicleId(int vehicleId) { @@ -961,6 +979,7 @@ namespace CarCareTracker.Controllers } #endregion #region "Notes" + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] public IActionResult GetNotesByVehicleId(int vehicleId) { diff --git a/Enum/UserAccessType.cs b/Enum/UserAccessType.cs deleted file mode 100644 index 4f61d58..0000000 --- a/Enum/UserAccessType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace CarCareTracker.Models -{ - public enum UserAccessType - { - Viewer = 0, - Editor = 1 - } -} diff --git a/Filter/CollaboratorFilter.cs b/Filter/CollaboratorFilter.cs new file mode 100644 index 0000000..550c458 --- /dev/null +++ b/Filter/CollaboratorFilter.cs @@ -0,0 +1,28 @@ +using CarCareTracker.Logic; +using CarCareTracker.Models; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using System.Security.Claims; + +namespace CarCareTracker.Filter +{ + public class CollaboratorFilter: ActionFilterAttribute + { + private readonly IUserLogic _userLogic; + public CollaboratorFilter(IUserLogic userLogic) { + _userLogic = userLogic; + } + public override void OnActionExecuting(ActionExecutingContext filterContext) + { + if (!filterContext.HttpContext.User.IsInRole(nameof(UserData.IsRootUser))) + { + var vehicleId = int.Parse(filterContext.ActionArguments["vehicleId"].ToString()); + var userId = int.Parse(filterContext.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier)); + if (!_userLogic.UserCanEditVehicle(userId, vehicleId)) + { + filterContext.Result = new RedirectResult("/Error/Unauthorized"); + } + } + } + } +} diff --git a/Logic/UserLogic.cs b/Logic/UserLogic.cs index 187904a..a5b3889 100644 --- a/Logic/UserLogic.cs +++ b/Logic/UserLogic.cs @@ -1,4 +1,5 @@ using CarCareTracker.External.Interfaces; +using CarCareTracker.Helper; using CarCareTracker.Models; using Microsoft.AspNetCore.Mvc.Formatters; @@ -7,7 +8,8 @@ namespace CarCareTracker.Logic public interface IUserLogic { List GetCollaboratorsForVehicle(int vehicleId); - bool AddUserAccessToVehicle(int userId, int vehicleId, UserAccessType accessType); + bool AddUserAccessToVehicle(int userId, int vehicleId); + OperationResponse AddCollaboratorToVehicle(int vehicleId, string username); List FilterUserVehicles(List results, int userId); bool UserCanAccessVehicle(int userId, int vehicleId); bool UserCanEditVehicle(int userId, int vehicleId); @@ -33,21 +35,36 @@ namespace CarCareTracker.Logic var userCollaborator = new UserCollaborator { UserName = _userData.GetUserRecordById(userAccess.Id.UserId).UserName, - AccessType = userAccess.AccessType, UserVehicle = userAccess.Id }; convertedResult.Add(userCollaborator); } return convertedResult; } - public bool AddUserAccessToVehicle(int userId, int vehicleId, UserAccessType accessType) + public OperationResponse AddCollaboratorToVehicle(int vehicleId, string username) + { + //try to find existing user. + var existingUser = _userData.GetUserRecordByUserName(username); + if (existingUser.Id != default) + { + //user exists. + var result = AddUserAccessToVehicle(existingUser.Id, vehicleId); + if (result) + { + return new OperationResponse { Success = true, Message = "Collaborator Added" }; + } + return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage }; + } + return new OperationResponse { Success = false, Message = $"Unable to find user {username} in the system" }; + } + public bool AddUserAccessToVehicle(int userId, int vehicleId) { if (userId == -1) { return true; } var userVehicle = new UserVehicle { UserId = userId, VehicleId = vehicleId }; - var userAccess = new UserAccess { Id = userVehicle, AccessType = accessType }; + var userAccess = new UserAccess { Id = userVehicle }; var result = _userAccess.SaveUserAccess(userAccess); return result; } @@ -89,7 +106,7 @@ namespace CarCareTracker.Logic return true; } var userAccess = _userAccess.GetUserAccessByVehicleAndUserId(userId, vehicleId); - if (userAccess != null && userAccess.AccessType == UserAccessType.Editor) + if (userAccess != null) { return true; } diff --git a/Models/User/UserAccess.cs b/Models/User/UserAccess.cs index 960cde7..039da7c 100644 --- a/Models/User/UserAccess.cs +++ b/Models/User/UserAccess.cs @@ -8,6 +8,5 @@ public class UserAccess { public UserVehicle Id { get; set; } - public UserAccessType AccessType { get; set; } } } diff --git a/Models/User/UserCollaborator.cs b/Models/User/UserCollaborator.cs index d46c88e..0d050d0 100644 --- a/Models/User/UserCollaborator.cs +++ b/Models/User/UserCollaborator.cs @@ -3,7 +3,6 @@ public class UserCollaborator { public string UserName { get; set; } - public UserAccessType AccessType { get; set; } public UserVehicle UserVehicle { get; set; } } } diff --git a/Views/Vehicle/_Collaborators.cshtml b/Views/Vehicle/_Collaborators.cshtml index abe4f93..ef793e9 100644 --- a/Views/Vehicle/_Collaborators.cshtml +++ b/Views/Vehicle/_Collaborators.cshtml @@ -1,4 +1,9 @@ @model List +
+
+ Collaborators +
+
    @foreach (UserCollaborator user in Model) { From 2ae334d06d1162ed9489c8cee63b53d4adca5520 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 21:18:58 -0700 Subject: [PATCH 13/25] added functions to add and remove collaborators. --- Controllers/APIController.cs | 7 +++ Controllers/AdminController.cs | 6 ++- Controllers/VehicleController.cs | 14 ++++++ Logic/UserLogic.cs | 6 +++ Views/Vehicle/_Collaborators.cshtml | 72 ++++++++++++++++++++++++++--- Views/Vehicle/_Report.cshtml | 8 +++- 6 files changed, 104 insertions(+), 9 deletions(-) diff --git a/Controllers/APIController.cs b/Controllers/APIController.cs index 0062f44..a353d9f 100644 --- a/Controllers/APIController.cs +++ b/Controllers/APIController.cs @@ -1,4 +1,5 @@ using CarCareTracker.External.Interfaces; +using CarCareTracker.Filter; using CarCareTracker.Helper; using CarCareTracker.Models; using Microsoft.AspNetCore.Authorization; @@ -53,6 +54,7 @@ namespace CarCareTracker.Controllers var result = _dataAccess.GetVehicles(); return Json(result); } + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] [Route("/api/vehicle/servicerecords")] public IActionResult ServiceRecords(int vehicleId) @@ -61,6 +63,7 @@ namespace CarCareTracker.Controllers var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() }); return Json(result); } + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] [Route("/api/vehicle/repairrecords")] public IActionResult RepairRecords(int vehicleId) @@ -69,6 +72,7 @@ namespace CarCareTracker.Controllers var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() }); return Json(result); } + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] [Route("/api/vehicle/upgraderecords")] public IActionResult UpgradeRecords(int vehicleId) @@ -77,6 +81,7 @@ namespace CarCareTracker.Controllers var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() }); return Json(result); } + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] [Route("/api/vehicle/taxrecords")] public IActionResult TaxRecords(int vehicleId) @@ -84,6 +89,7 @@ namespace CarCareTracker.Controllers var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId); return Json(result); } + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] [Route("/api/vehicle/gasrecords")] public IActionResult GasRecords(int vehicleId, bool useMPG, bool useUKMPG) @@ -92,6 +98,7 @@ namespace CarCareTracker.Controllers var result = _gasHelper.GetGasRecordViewModels(vehicleRecords, useMPG, useUKMPG).Select(x => new GasRecordExportModel { Date = x.Date, Odometer = x.Mileage.ToString(), Cost = x.Cost.ToString(), FuelConsumed = x.Gallons.ToString(), FuelEconomy = x.MilesPerGallon.ToString()}); return Json(result); } + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] [Route("/api/vehicle/reminders")] public IActionResult Reminders(int vehicleId) diff --git a/Controllers/AdminController.cs b/Controllers/AdminController.cs index 4a530a5..e41a530 100644 --- a/Controllers/AdminController.cs +++ b/Controllers/AdminController.cs @@ -11,9 +11,11 @@ namespace CarCareTracker.Controllers public class AdminController : Controller { private ILoginLogic _loginLogic; - public AdminController(ILoginLogic loginLogic) + private IUserLogic _userLogic; + public AdminController(ILoginLogic loginLogic, IUserLogic userLogic) { _loginLogic = loginLogic; + _userLogic = userLogic; } public IActionResult Index() { @@ -36,7 +38,7 @@ namespace CarCareTracker.Controllers } public IActionResult DeleteUser(int userId) { - var result =_loginLogic.DeleteUser(userId); + var result =_userLogic.DeleteAllAccessToUser(userId) && _loginLogic.DeleteUser(userId); return Json(result); } } diff --git a/Controllers/VehicleController.cs b/Controllers/VehicleController.cs index cbb8392..149fa0f 100644 --- a/Controllers/VehicleController.cs +++ b/Controllers/VehicleController.cs @@ -688,6 +688,20 @@ namespace CarCareTracker.Controllers return PartialView("_Collaborators", result); } [TypeFilter(typeof(CollaboratorFilter))] + [HttpPost] + public IActionResult AddCollaboratorsToVehicle(int vehicleId, string username) + { + var result = _userLogic.AddCollaboratorToVehicle(vehicleId, username); + return Json(result); + } + [TypeFilter(typeof(CollaboratorFilter))] + [HttpPost] + public IActionResult DeleteCollaboratorFromVehicle(int userId, int vehicleId) + { + var result = _userLogic.DeleteCollaboratorFromVehicle(userId, vehicleId); + return Json(result); + } + [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] public IActionResult GetCostMakeUpForVehicle(int vehicleId, int year = 0) { diff --git a/Logic/UserLogic.cs b/Logic/UserLogic.cs index a5b3889..dd910d9 100644 --- a/Logic/UserLogic.cs +++ b/Logic/UserLogic.cs @@ -9,6 +9,7 @@ namespace CarCareTracker.Logic { List GetCollaboratorsForVehicle(int vehicleId); bool AddUserAccessToVehicle(int userId, int vehicleId); + bool DeleteCollaboratorFromVehicle(int userId, int vehicleId); OperationResponse AddCollaboratorToVehicle(int vehicleId, string username); List FilterUserVehicles(List results, int userId); bool UserCanAccessVehicle(int userId, int vehicleId); @@ -57,6 +58,11 @@ namespace CarCareTracker.Logic } return new OperationResponse { Success = false, Message = $"Unable to find user {username} in the system" }; } + public bool DeleteCollaboratorFromVehicle(int userId, int vehicleId) + { + var result = _userAccess.DeleteUserAccess(userId, vehicleId); + return result; + } public bool AddUserAccessToVehicle(int userId, int vehicleId) { if (userId == -1) diff --git a/Views/Vehicle/_Collaborators.cshtml b/Views/Vehicle/_Collaborators.cshtml index ef793e9..20fc53e 100644 --- a/Views/Vehicle/_Collaborators.cshtml +++ b/Views/Vehicle/_Collaborators.cshtml @@ -1,12 +1,72 @@ @model List
    -
    +
    Collaborators
    +
    + +
    -
      - @foreach (UserCollaborator user in Model) - { -
    • @user.UserName
    • +
      +
@userData.UserName @userData.Id @userData.Id@userData.Id
+ + + + + + + + @foreach (UserCollaborator user in Model) + { + + + + + } + +
UsernameDelete
@user.UserName + @if(User.Identity.Name != user.UserName) + { + + } +
+
+ \ No newline at end of file diff --git a/Views/Vehicle/_Report.cshtml b/Views/Vehicle/_Report.cshtml index 97a3a2f..9cb0a6b 100644 --- a/Views/Vehicle/_Report.cshtml +++ b/Views/Vehicle/_Report.cshtml @@ -77,7 +77,7 @@
-
+
@await Html.PartialAsync("_Collaborators", Model.Collaborators)
@@ -153,4 +153,10 @@ refreshBarChart(); }) } + function refreshCollaborators(){ + var vehicleId = GetVehicleId().vehicleId; + $.get(`/Vehicle/GetCollaboratorsForVehicle?vehicleId=${vehicleId}`, function (data) { + $("#collaboratorContent").html(data); + }); + } \ No newline at end of file From d80f0dcb8fd82dce651d39bce5a390fad1be7dbd Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 21:33:13 -0700 Subject: [PATCH 14/25] cleaned up unused methods. --- Logic/UserLogic.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Logic/UserLogic.cs b/Logic/UserLogic.cs index dd910d9..bd80c08 100644 --- a/Logic/UserLogic.cs +++ b/Logic/UserLogic.cs @@ -12,7 +12,6 @@ namespace CarCareTracker.Logic bool DeleteCollaboratorFromVehicle(int userId, int vehicleId); OperationResponse AddCollaboratorToVehicle(int vehicleId, string username); List FilterUserVehicles(List results, int userId); - bool UserCanAccessVehicle(int userId, int vehicleId); bool UserCanEditVehicle(int userId, int vehicleId); bool DeleteAllAccessToVehicle(int vehicleId); bool DeleteAllAccessToUser(int userId); @@ -92,19 +91,6 @@ namespace CarCareTracker.Logic return new List(); } } - public bool UserCanAccessVehicle(int userId, int vehicleId) - { - if (userId == -1) - { - return true; - } - var userAccess = _userAccess.GetUserAccessByVehicleAndUserId(userId, vehicleId); - if (userAccess != null) - { - return true; - } - return false; - } public bool UserCanEditVehicle(int userId, int vehicleId) { if (userId == -1) From 4f706d3e93d8e93dfc6cdac4a97425565a00ce76 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 21:35:23 -0700 Subject: [PATCH 15/25] filtered out vehicles not owned by the user when accessing via API. --- Controllers/APIController.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Controllers/APIController.cs b/Controllers/APIController.cs index a353d9f..73e2f34 100644 --- a/Controllers/APIController.cs +++ b/Controllers/APIController.cs @@ -1,9 +1,11 @@ using CarCareTracker.External.Interfaces; using CarCareTracker.Filter; using CarCareTracker.Helper; +using CarCareTracker.Logic; using CarCareTracker.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; namespace CarCareTracker.Controllers { @@ -20,6 +22,7 @@ namespace CarCareTracker.Controllers private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess; private readonly IReminderHelper _reminderHelper; private readonly IGasHelper _gasHelper; + private readonly IUserLogic _userLogic; public APIController(IVehicleDataAccess dataAccess, IGasHelper gasHelper, IReminderHelper reminderHelper, @@ -29,7 +32,8 @@ namespace CarCareTracker.Controllers ICollisionRecordDataAccess collisionRecordDataAccess, ITaxRecordDataAccess taxRecordDataAccess, IReminderRecordDataAccess reminderRecordDataAccess, - IUpgradeRecordDataAccess upgradeRecordDataAccess) + IUpgradeRecordDataAccess upgradeRecordDataAccess, + IUserLogic userLogic) { _dataAccess = dataAccess; _noteDataAccess = noteDataAccess; @@ -41,17 +45,25 @@ namespace CarCareTracker.Controllers _upgradeRecordDataAccess = upgradeRecordDataAccess; _gasHelper = gasHelper; _reminderHelper = reminderHelper; + _userLogic = userLogic; } public IActionResult Index() { return View(); } - + private int GetUserID() + { + return int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)); + } [HttpGet] [Route("/api/vehicles")] public IActionResult Vehicles() { var result = _dataAccess.GetVehicles(); + if (!User.IsInRole(nameof(UserData.IsRootUser))) + { + result = _userLogic.FilterUserVehicles(result, GetUserID()); + } return Json(result); } [TypeFilter(typeof(CollaboratorFilter))] From 915eb1722dfa9c9590109c556c4e45fc4f25b403 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 22:29:14 -0700 Subject: [PATCH 16/25] added confighelper --- Helper/ConfigHelper.cs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Helper/ConfigHelper.cs diff --git a/Helper/ConfigHelper.cs b/Helper/ConfigHelper.cs new file mode 100644 index 0000000..f59e62b --- /dev/null +++ b/Helper/ConfigHelper.cs @@ -0,0 +1,37 @@ +using CarCareTracker.Models; + +namespace CarCareTracker.Helper +{ + public interface IConfigHelper + { + UserConfig GetUserConfig(bool userIsAdmin, int userId); + } + public class ConfigHelper : IConfigHelper + { + private readonly IConfiguration _config; + public ConfigHelper(IConfiguration serverConfiguration) + { + _config = serverConfiguration; + } + public UserConfig GetUserConfig(bool isRootUser, int userId) + { + if (isRootUser) + { + var serverConfig = new UserConfig + { + EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]), + UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]), + UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]), + UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]), + EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]), + HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]), + UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]) + }; + return serverConfig; + } else + { + return new UserConfig(); + } + } + } +} From 8c6920afab516eb2ed11e7df365587a9746f8aa9 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 23:13:11 -0700 Subject: [PATCH 17/25] enable users to have their own config file. --- Controllers/HomeController.cs | 45 +-------- .../Implementations/UserConfigDataAccess.cs | 38 ++++++++ External/Interfaces/IUserConfigDataAccess.cs | 11 +++ Helper/ConfigHelper.cs | 96 ++++++++++++++++--- Models/User/UserConfigData.cs | 11 +++ Program.cs | 2 + Views/Home/_Settings.cshtml | 11 ++- Views/Shared/_Layout.cshtml | 10 +- 8 files changed, 162 insertions(+), 62 deletions(-) create mode 100644 External/Implementations/UserConfigDataAccess.cs create mode 100644 External/Interfaces/IUserConfigDataAccess.cs create mode 100644 Models/User/UserConfigData.cs diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs index 1baa0d8..e6b8f26 100644 --- a/Controllers/HomeController.cs +++ b/Controllers/HomeController.cs @@ -15,12 +15,12 @@ namespace CarCareTracker.Controllers private readonly ILogger _logger; private readonly IVehicleDataAccess _dataAccess; private readonly IUserLogic _userLogic; - private readonly IConfiguration _config; + private readonly IConfigHelper _config; public HomeController(ILogger logger, IVehicleDataAccess dataAccess, IUserLogic userLogic, - IConfiguration configuration) + IConfigHelper configuration) { _logger = logger; _dataAccess = dataAccess; @@ -46,49 +46,14 @@ namespace CarCareTracker.Controllers } public IActionResult Settings() { - var userConfig = new UserConfig - { - EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]), - UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]), - UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]), - UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]), - EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]), - HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]), - UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]) - }; + var userConfig = _config.GetUserConfig(User); return PartialView("_Settings", userConfig); } [HttpPost] public IActionResult WriteToSettings(UserConfig userConfig) { - try - { - if (!System.IO.File.Exists(StaticHelper.UserConfigPath)) - { - //if file doesn't exist it might be because it's running on a mounted volume in docker. - System.IO.File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(new UserConfig())); - } - var configFileContents = System.IO.File.ReadAllText(StaticHelper.UserConfigPath); - var existingUserConfig = System.Text.Json.JsonSerializer.Deserialize(configFileContents); - if (existingUserConfig is not null) - { - //copy over settings that are off limits on the settings page. - userConfig.EnableAuth = existingUserConfig.EnableAuth; - userConfig.UserNameHash = existingUserConfig.UserNameHash; - userConfig.UserPasswordHash = existingUserConfig.UserPasswordHash; - } else - { - userConfig.EnableAuth = false; - userConfig.UserNameHash = string.Empty; - userConfig.UserPasswordHash = string.Empty; - } - System.IO.File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(userConfig)); - return Json(true); - } catch (Exception ex) - { - _logger.LogError(ex, "Error on saving config file."); - } - return Json(false); + var result = _config.SaveUserConfig(User.IsInRole(nameof(UserData.IsRootUser)), GetUserID(), userConfig); + return Json(result); } public IActionResult Privacy() { diff --git a/External/Implementations/UserConfigDataAccess.cs b/External/Implementations/UserConfigDataAccess.cs new file mode 100644 index 0000000..f2dc4b7 --- /dev/null +++ b/External/Implementations/UserConfigDataAccess.cs @@ -0,0 +1,38 @@ +using CarCareTracker.External.Interfaces; +using CarCareTracker.Helper; +using CarCareTracker.Models; +using LiteDB; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace CarCareTracker.External.Implementations +{ + public class UserConfigDataAccess: IUserConfigDataAccess + { + private static string dbName = StaticHelper.DbName; + private static string tableName = "userconfigrecords"; + public UserConfigData GetUserConfig(int userId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + return table.FindById(userId); + }; + } + public bool SaveUserConfig(UserConfigData userConfigData) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + return table.Upsert(userConfigData); + }; + } + public bool DeleteUserConfig(int userId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + return table.Delete(userId); + }; + } + } +} diff --git a/External/Interfaces/IUserConfigDataAccess.cs b/External/Interfaces/IUserConfigDataAccess.cs new file mode 100644 index 0000000..d4bf1eb --- /dev/null +++ b/External/Interfaces/IUserConfigDataAccess.cs @@ -0,0 +1,11 @@ +using CarCareTracker.Models; + +namespace CarCareTracker.External.Interfaces +{ + public interface IUserConfigDataAccess + { + public UserConfigData GetUserConfig(int userId); + public bool SaveUserConfig(UserConfigData userConfigData); + public bool DeleteUserConfig(int userId); + } +} diff --git a/Helper/ConfigHelper.cs b/Helper/ConfigHelper.cs index f59e62b..ca26486 100644 --- a/Helper/ConfigHelper.cs +++ b/Helper/ConfigHelper.cs @@ -1,36 +1,104 @@ -using CarCareTracker.Models; +using CarCareTracker.External.Interfaces; +using CarCareTracker.Models; +using System.Security.Claims; namespace CarCareTracker.Helper { public interface IConfigHelper { - UserConfig GetUserConfig(bool userIsAdmin, int userId); + UserConfig GetUserConfig(ClaimsPrincipal user); + bool SaveUserConfig(bool isRootUser, int userId, UserConfig configData); + public bool DeleteUserConfig(int userId); } public class ConfigHelper : IConfigHelper { private readonly IConfiguration _config; - public ConfigHelper(IConfiguration serverConfiguration) + private readonly IUserConfigDataAccess _userConfig; + public ConfigHelper(IConfiguration serverConfig, IUserConfigDataAccess userConfig) { - _config = serverConfiguration; + _config = serverConfig; + _userConfig = userConfig; } - public UserConfig GetUserConfig(bool isRootUser, int userId) + public bool SaveUserConfig(bool isRootUser, int userId, UserConfig configData) { if (isRootUser) { - var serverConfig = new UserConfig + try { - EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]), - UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]), - UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]), - UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]), - EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]), - HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]), - UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]) + if (!File.Exists(StaticHelper.UserConfigPath)) + { + //if file doesn't exist it might be because it's running on a mounted volume in docker. + File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(new UserConfig())); + } + var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath); + var existingUserConfig = System.Text.Json.JsonSerializer.Deserialize(configFileContents); + if (existingUserConfig is not null) + { + //copy over settings that are off limits on the settings page. + configData.EnableAuth = existingUserConfig.EnableAuth; + configData.UserNameHash = existingUserConfig.UserNameHash; + configData.UserPasswordHash = existingUserConfig.UserPasswordHash; + } + else + { + configData.EnableAuth = false; + configData.UserNameHash = string.Empty; + configData.UserPasswordHash = string.Empty; + } + File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(configData)); + return true; + } + catch (Exception ex) + { + return false; + } + } else + { + var userConfig = new UserConfigData() + { + Id = userId, + UserConfig = configData }; + var result = _userConfig.SaveUserConfig(userConfig); + return result; + } + } + public bool DeleteUserConfig(int userId) + { + var result = _userConfig.DeleteUserConfig(userId); + return result; + } + public UserConfig GetUserConfig(ClaimsPrincipal user) + { + var serverConfig = new UserConfig + { + EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]), + UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]), + UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]), + UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]), + EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]), + HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]), + UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]) + }; + if (!user.Identity.IsAuthenticated) + { + return serverConfig; + } + bool isRootUser = user.IsInRole(nameof(UserData.IsRootUser)); + int userId = int.Parse(user.FindFirstValue(ClaimTypes.NameIdentifier)); + if (isRootUser) + { return serverConfig; } else { - return new UserConfig(); + var result = _userConfig.GetUserConfig(userId); + if (result == null) + { + return serverConfig; + } else + { + return result.UserConfig; + } } } } diff --git a/Models/User/UserConfigData.cs b/Models/User/UserConfigData.cs new file mode 100644 index 0000000..6aa68c6 --- /dev/null +++ b/Models/User/UserConfigData.cs @@ -0,0 +1,11 @@ +namespace CarCareTracker.Models +{ + public class UserConfigData + { + /// + /// User ID + /// + public int Id { get; set; } + public UserConfig UserConfig { get; set; } + } +} diff --git a/Program.cs b/Program.cs index d03c217..157f3c2 100644 --- a/Program.cs +++ b/Program.cs @@ -21,6 +21,7 @@ builder.Services.AddSingleton builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); //configure helpers builder.Services.AddSingleton(); @@ -28,6 +29,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); //configure logic builder.Services.AddSingleton(); diff --git a/Views/Home/_Settings.cshtml b/Views/Home/_Settings.cshtml index 7266531..8ab00a2 100644 --- a/Views/Home/_Settings.cshtml +++ b/Views/Home/_Settings.cshtml @@ -32,10 +32,13 @@
-
- - -
+ @if (User.IsInRole(nameof(UserData.IsRootUser))) + { +
+ + +
+ }
diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index 17fd6c5..5694d37 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -1,8 +1,10 @@ - -@inject IConfiguration Configuration +@using CarCareTracker.Helper + +@inject IConfigHelper config @{ - var useDarkMode = bool.Parse(Configuration["UseDarkMode"]); - var enableCsvImports = bool.Parse(Configuration["EnableCsvImports"]); + var userConfig = config.GetUserConfig(User); + var useDarkMode = userConfig.UseDarkMode; + var enableCsvImports = userConfig.EnableCsvImports; var shortDatePattern = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; shortDatePattern = shortDatePattern.ToLower(); if (!shortDatePattern.Contains("dd")) From c58b4552b2bd0511b5e155e4f61cb8ac5277a227 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sat, 13 Jan 2024 23:43:38 -0700 Subject: [PATCH 18/25] hide settings page from user. --- Helper/ConfigHelper.cs | 37 ------------------------------------- Views/Home/Index.cshtml | 20 +++++++++++++------- 2 files changed, 13 insertions(+), 44 deletions(-) delete mode 100644 Helper/ConfigHelper.cs diff --git a/Helper/ConfigHelper.cs b/Helper/ConfigHelper.cs deleted file mode 100644 index f59e62b..0000000 --- a/Helper/ConfigHelper.cs +++ /dev/null @@ -1,37 +0,0 @@ -using CarCareTracker.Models; - -namespace CarCareTracker.Helper -{ - public interface IConfigHelper - { - UserConfig GetUserConfig(bool userIsAdmin, int userId); - } - public class ConfigHelper : IConfigHelper - { - private readonly IConfiguration _config; - public ConfigHelper(IConfiguration serverConfiguration) - { - _config = serverConfiguration; - } - public UserConfig GetUserConfig(bool isRootUser, int userId) - { - if (isRootUser) - { - var serverConfig = new UserConfig - { - EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]), - UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]), - UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]), - UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]), - EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]), - HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]), - UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]) - }; - return serverConfig; - } else - { - return new UserConfig(); - } - } - } -} diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml index 2dd47ce..892e543 100644 --- a/Views/Home/Index.cshtml +++ b/Views/Home/Index.cshtml @@ -14,9 +14,12 @@ - + @if (User.IsInRole(nameof(UserData.IsRootUser))) + { + + } @if (enableAuth) { - + @if (User.IsInRole(nameof(UserData.IsRootUser))) + { + + } @if (enableAuth) { - } From 8d747990990d44bc2e6b5391a02a922a4c26342d Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sun, 14 Jan 2024 09:54:13 -0700 Subject: [PATCH 19/25] replaced IConfiguration injection with IConfigHelper --- Controllers/AdminController.cs | 9 +- Controllers/HomeController.cs | 2 +- Controllers/VehicleController.cs | 18 ++-- .../Implementations/UserConfigDataAccess.cs | 3 +- Helper/ConfigHelper.cs | 86 +++++++++++++------ Logic/LoginLogic.cs | 16 ++++ Middleware/Authen.cs | 8 +- Views/Home/Index.cshtml | 10 +-- Views/Vehicle/_CollisionRecords.cshtml | 7 +- Views/Vehicle/_Gas.cshtml | 11 +-- Views/Vehicle/_GasModal.cshtml | 7 +- Views/Vehicle/_ServiceRecords.cshtml | 7 +- Views/Vehicle/_TaxRecords.cshtml | 7 +- Views/Vehicle/_UpgradeRecords.cshtml | 7 +- Views/Vehicle/_VehicleHistory.cshtml | 9 +- wwwroot/js/garage.js | 1 + 16 files changed, 137 insertions(+), 71 deletions(-) diff --git a/Controllers/AdminController.cs b/Controllers/AdminController.cs index e41a530..2e7c29e 100644 --- a/Controllers/AdminController.cs +++ b/Controllers/AdminController.cs @@ -1,4 +1,5 @@ -using CarCareTracker.Logic; +using CarCareTracker.Helper; +using CarCareTracker.Logic; using CarCareTracker.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -12,10 +13,12 @@ namespace CarCareTracker.Controllers { private ILoginLogic _loginLogic; private IUserLogic _userLogic; - public AdminController(ILoginLogic loginLogic, IUserLogic userLogic) + private IConfigHelper _configHelper; + public AdminController(ILoginLogic loginLogic, IUserLogic userLogic, IConfigHelper configHelper) { _loginLogic = loginLogic; _userLogic = userLogic; + _configHelper = configHelper; } public IActionResult Index() { @@ -38,7 +41,7 @@ namespace CarCareTracker.Controllers } public IActionResult DeleteUser(int userId) { - var result =_userLogic.DeleteAllAccessToUser(userId) && _loginLogic.DeleteUser(userId); + var result =_userLogic.DeleteAllAccessToUser(userId) && _configHelper.DeleteUserConfig(userId) && _loginLogic.DeleteUser(userId); return Json(result); } } diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs index e6b8f26..981177d 100644 --- a/Controllers/HomeController.cs +++ b/Controllers/HomeController.cs @@ -52,7 +52,7 @@ namespace CarCareTracker.Controllers [HttpPost] public IActionResult WriteToSettings(UserConfig userConfig) { - var result = _config.SaveUserConfig(User.IsInRole(nameof(UserData.IsRootUser)), GetUserID(), userConfig); + var result = _config.SaveUserConfig(User, userConfig); return Json(result); } public IActionResult Privacy() diff --git a/Controllers/VehicleController.cs b/Controllers/VehicleController.cs index 149fa0f..600c825 100644 --- a/Controllers/VehicleController.cs +++ b/Controllers/VehicleController.cs @@ -27,7 +27,7 @@ namespace CarCareTracker.Controllers private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess; private readonly IWebHostEnvironment _webEnv; private readonly bool _useDescending; - private readonly IConfiguration _config; + private readonly IConfigHelper _config; private readonly IFileHelper _fileHelper; private readonly IGasHelper _gasHelper; private readonly IReminderHelper _reminderHelper; @@ -49,7 +49,7 @@ namespace CarCareTracker.Controllers IUpgradeRecordDataAccess upgradeRecordDataAccess, IUserLogic userLogic, IWebHostEnvironment webEnv, - IConfiguration config) + IConfigHelper config) { _logger = logger; _dataAccess = dataAccess; @@ -67,7 +67,7 @@ namespace CarCareTracker.Controllers _userLogic = userLogic; _webEnv = webEnv; _config = config; - _useDescending = bool.Parse(config[nameof(UserConfig.UseDescending)]); + _useDescending = config.GetUserConfig(User).UseDescending; } private int GetUserID() { @@ -231,8 +231,8 @@ namespace CarCareTracker.Controllers var fileNameToExport = $"temp/{Guid.NewGuid()}.csv"; var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false); var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId); - bool useMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]); - bool useUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]); + bool useMPG = _config.GetUserConfig(User).UseMPG; + bool useUKMPG = _config.GetUserConfig(User).UseUKMPG; vehicleRecords = vehicleRecords.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList(); var convertedRecords = _gasHelper.GetGasRecordViewModels(vehicleRecords, useMPG, useUKMPG); var exportData = convertedRecords.Select(x => new GasRecordExportModel { Date = x.Date.ToString(), Cost = x.Cost.ToString(), FuelConsumed = x.Gallons.ToString(), FuelEconomy = x.MilesPerGallon.ToString(), Odometer = x.Mileage.ToString() }); @@ -389,8 +389,8 @@ namespace CarCareTracker.Controllers //need it in ascending order to perform computation. result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList(); //check if the user uses MPG or Liters per 100km. - bool useMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]); - bool useUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]); + bool useMPG = _config.GetUserConfig(User).UseMPG; + bool useUKMPG = _config.GetUserConfig(User).UseUKMPG; var computedResults = _gasHelper.GetGasRecordViewModels(result, useMPG, useUKMPG); if (_useDescending) { @@ -753,8 +753,8 @@ namespace CarCareTracker.Controllers var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId); var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId); var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId); - bool useMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]); - bool useUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]); + bool useMPG = _config.GetUserConfig(User).UseMPG; + bool useUKMPG = _config.GetUserConfig(User).UseUKMPG; vehicleHistory.TotalGasCost = gasRecords.Sum(x => x.Cost); vehicleHistory.TotalCost = serviceRecords.Sum(x => x.Cost) + repairRecords.Sum(x => x.Cost) + upgradeRecords.Sum(x => x.Cost) + taxRecords.Sum(x => x.Cost); var averageMPG = 0.00M; diff --git a/External/Implementations/UserConfigDataAccess.cs b/External/Implementations/UserConfigDataAccess.cs index f2dc4b7..83003b7 100644 --- a/External/Implementations/UserConfigDataAccess.cs +++ b/External/Implementations/UserConfigDataAccess.cs @@ -23,7 +23,8 @@ namespace CarCareTracker.External.Implementations using (var db = new LiteDatabase(dbName)) { var table = db.GetCollection(tableName); - return table.Upsert(userConfigData); + table.Upsert(userConfigData); + return true; }; } public bool DeleteUserConfig(int userId) diff --git a/Helper/ConfigHelper.cs b/Helper/ConfigHelper.cs index ca26486..097430d 100644 --- a/Helper/ConfigHelper.cs +++ b/Helper/ConfigHelper.cs @@ -1,5 +1,6 @@ using CarCareTracker.External.Interfaces; using CarCareTracker.Models; +using Microsoft.Extensions.Caching.Memory; using System.Security.Claims; namespace CarCareTracker.Helper @@ -7,20 +8,31 @@ namespace CarCareTracker.Helper public interface IConfigHelper { UserConfig GetUserConfig(ClaimsPrincipal user); - bool SaveUserConfig(bool isRootUser, int userId, UserConfig configData); + bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData); public bool DeleteUserConfig(int userId); } public class ConfigHelper : IConfigHelper { private readonly IConfiguration _config; private readonly IUserConfigDataAccess _userConfig; - public ConfigHelper(IConfiguration serverConfig, IUserConfigDataAccess userConfig) + private IMemoryCache _cache; + public ConfigHelper(IConfiguration serverConfig, + IUserConfigDataAccess userConfig, + IMemoryCache memoryCache) { _config = serverConfig; _userConfig = userConfig; + _cache = memoryCache; } - public bool SaveUserConfig(bool isRootUser, int userId, UserConfig configData) + public bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData) { + var storedUserId = user.FindFirstValue(ClaimTypes.NameIdentifier); + int userId = 0; + if (storedUserId != null) + { + userId = int.Parse(storedUserId); + } + bool isRootUser = user.IsInRole(nameof(UserData.IsRootUser)); if (isRootUser) { try @@ -46,6 +58,7 @@ namespace CarCareTracker.Helper configData.UserPasswordHash = string.Empty; } File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(configData)); + _cache.Set($"userConfig_{userId}", configData); return true; } catch (Exception ex) @@ -60,46 +73,65 @@ namespace CarCareTracker.Helper UserConfig = configData }; var result = _userConfig.SaveUserConfig(userConfig); + _cache.Set($"userConfig_{userId}", configData); return result; } } public bool DeleteUserConfig(int userId) { + _cache.Remove($"userConfig_{userId}"); var result = _userConfig.DeleteUserConfig(userId); return result; } public UserConfig GetUserConfig(ClaimsPrincipal user) { - var serverConfig = new UserConfig + int userId = 0; + if (user != null) { - EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]), - UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]), - UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]), - UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]), - EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]), - HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]), - UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]) - }; - if (!user.Identity.IsAuthenticated) - { - return serverConfig; - } - bool isRootUser = user.IsInRole(nameof(UserData.IsRootUser)); - int userId = int.Parse(user.FindFirstValue(ClaimTypes.NameIdentifier)); - if (isRootUser) - { - return serverConfig; + var storedUserId = user.FindFirstValue(ClaimTypes.NameIdentifier); + if (storedUserId != null) + { + userId = int.Parse(storedUserId); + } } else { - var result = _userConfig.GetUserConfig(userId); - if (result == null) + return new UserConfig(); + } + return _cache.GetOrCreate($"userConfig_{userId}", entry => + { + entry.SlidingExpiration = TimeSpan.FromHours(1); + var serverConfig = new UserConfig + { + EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]), + UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]), + UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]), + UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]), + EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]), + HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]), + UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]) + }; + if (!user.Identity.IsAuthenticated) { return serverConfig; - } else - { - return result.UserConfig; } - } + bool isRootUser = user.IsInRole(nameof(UserData.IsRootUser)); + if (isRootUser) + { + return serverConfig; + } + else + { + var result = _userConfig.GetUserConfig(userId); + if (result == null) + { + return serverConfig; + } + else + { + return result.UserConfig; + } + } + }); } } } diff --git a/Logic/LoginLogic.cs b/Logic/LoginLogic.cs index 7d6363c..b709614 100644 --- a/Logic/LoginLogic.cs +++ b/Logic/LoginLogic.cs @@ -19,6 +19,7 @@ namespace CarCareTracker.Logic OperationResponse ResetPasswordByUser(LoginModel credentials); OperationResponse ResetUserPassword(LoginModel credentials); UserData ValidateUserCredentials(LoginModel credentials); + bool CheckIfUserIsValid(int userId); bool CreateRootUserCredentials(LoginModel credentials); bool DeleteRootUserCredentials(); List GetAllUsers(); @@ -36,6 +37,21 @@ namespace CarCareTracker.Logic _tokenData = tokenData; _mailHelper = mailHelper; } + public bool CheckIfUserIsValid(int userId) + { + if (userId == -1) + { + return true; + } + var result = _userData.GetUserRecordById(userId); + if (result == null) + { + return false; + } else + { + return result.Id != 0; + } + } //handles user registration public OperationResponse RegisterNewUser(LoginModel credentials) { diff --git a/Middleware/Authen.cs b/Middleware/Authen.cs index 975b395..5cfdd89 100644 --- a/Middleware/Authen.cs +++ b/Middleware/Authen.cs @@ -113,11 +113,17 @@ namespace CarCareTracker.Middleware } else { + if (!_loginLogic.CheckIfUserIsValid(authCookie.UserData.Id)) + { + return AuthenticateResult.Fail("Cookie points to non-existant user."); + } + //validate if user is still valid var appIdentity = new ClaimsIdentity("Custom"); var userIdentity = new List { new(ClaimTypes.Name, authCookie.UserData.UserName), - new(ClaimTypes.NameIdentifier, authCookie.UserData.Id.ToString()) + new(ClaimTypes.NameIdentifier, authCookie.UserData.Id.ToString()), + new(ClaimTypes.Role, "CookieAuth") }; if (authCookie.UserData.IsAdmin) { diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml index 2dd47ce..5eddcea 100644 --- a/Views/Home/Index.cshtml +++ b/Views/Home/Index.cshtml @@ -1,6 +1,7 @@ -@inject IConfiguration Configuration +@using CarCareTracker.Helper +@inject IConfigHelper config @{ - var enableAuth = bool.Parse(Configuration[nameof(UserConfig.EnableAuth)]); + var enableAuth = config.GetUserConfig(User).EnableAuth; } @model string @{ @@ -17,7 +18,7 @@ - @if (enableAuth) + @if (User.IsInRole("CookieAuth")) { - @if (enableAuth) + @if (User.IsInRole("CookieAuth")) {
\ No newline at end of file diff --git a/Views/Vehicle/_CollisionRecords.cshtml b/Views/Vehicle/_CollisionRecords.cshtml index 9237b23..b84ef61 100644 --- a/Views/Vehicle/_CollisionRecords.cshtml +++ b/Views/Vehicle/_CollisionRecords.cshtml @@ -1,7 +1,8 @@ -@inject IConfiguration Configuration +@using CarCareTracker.Helper +@inject IConfigHelper config @{ - var enableCsvImports = bool.Parse(Configuration[nameof(UserConfig.EnableCsvImports)]); - var hideZero = bool.Parse(Configuration[nameof(UserConfig.HideZero)]); + var enableCsvImports = config.GetUserConfig(User).EnableCsvImports; + var hideZero = config.GetUserConfig(User).HideZero; } @model List
diff --git a/Views/Vehicle/_Gas.cshtml b/Views/Vehicle/_Gas.cshtml index ed7591b..233d29a 100644 --- a/Views/Vehicle/_Gas.cshtml +++ b/Views/Vehicle/_Gas.cshtml @@ -1,10 +1,11 @@ -@inject IConfiguration Configuration +@using CarCareTracker.Helper +@inject IConfigHelper config @model GasRecordViewModelContainer @{ - var enableCsvImports = bool.Parse(Configuration[nameof(UserConfig.EnableCsvImports)]); - var useMPG = bool.Parse(Configuration[nameof(UserConfig.UseMPG)]); - var useUKMPG = bool.Parse(Configuration[nameof(UserConfig.UseUKMPG)]); - var hideZero = bool.Parse(Configuration[nameof(UserConfig.HideZero)]); + var enableCsvImports = config.GetUserConfig(User).EnableCsvImports; + var useMPG = config.GetUserConfig(User).UseMPG; + var useUKMPG = config.GetUserConfig(User).UseUKMPG; + var hideZero = config.GetUserConfig(User).HideZero; var useKwh = Model.UseKwh; string consumptionUnit; string fuelEconomyUnit; diff --git a/Views/Vehicle/_GasModal.cshtml b/Views/Vehicle/_GasModal.cshtml index bfbac9a..fc86707 100644 --- a/Views/Vehicle/_GasModal.cshtml +++ b/Views/Vehicle/_GasModal.cshtml @@ -1,8 +1,9 @@ -@inject IConfiguration Configuration +@using CarCareTracker.Helper +@inject IConfigHelper config @model GasRecordInputContainer @{ - var useMPG = bool.Parse(Configuration[nameof(UserConfig.UseMPG)]); - var useUKMPG = bool.Parse(Configuration[nameof(UserConfig.UseUKMPG)]); + var useMPG = config.GetUserConfig(User).UseMPG; + var useUKMPG = config.GetUserConfig(User).UseUKMPG; var useKwh = Model.UseKwh; var isNew = Model.GasRecord.Id == 0; string consumptionUnit; diff --git a/Views/Vehicle/_ServiceRecords.cshtml b/Views/Vehicle/_ServiceRecords.cshtml index 72a9d05..c657262 100644 --- a/Views/Vehicle/_ServiceRecords.cshtml +++ b/Views/Vehicle/_ServiceRecords.cshtml @@ -1,7 +1,8 @@ -@inject IConfiguration Configuration +@using CarCareTracker.Helper +@inject IConfigHelper config @{ - var enableCsvImports = bool.Parse(Configuration[nameof(UserConfig.EnableCsvImports)]); - var hideZero = bool.Parse(Configuration[nameof(UserConfig.HideZero)]); + var enableCsvImports = config.GetUserConfig(User).EnableCsvImports; + var hideZero = config.GetUserConfig(User).HideZero; } @model List
diff --git a/Views/Vehicle/_TaxRecords.cshtml b/Views/Vehicle/_TaxRecords.cshtml index b7facd8..296a678 100644 --- a/Views/Vehicle/_TaxRecords.cshtml +++ b/Views/Vehicle/_TaxRecords.cshtml @@ -1,7 +1,8 @@ -@inject IConfiguration Configuration +@using CarCareTracker.Helper +@inject IConfigHelper config @{ - var enableCsvImports = bool.Parse(Configuration[nameof(UserConfig.EnableCsvImports)]); - var hideZero = bool.Parse(Configuration[nameof(UserConfig.HideZero)]); + var enableCsvImports = config.GetUserConfig(User).EnableCsvImports; + var hideZero = config.GetUserConfig(User).HideZero; } @model List
diff --git a/Views/Vehicle/_UpgradeRecords.cshtml b/Views/Vehicle/_UpgradeRecords.cshtml index 94de026..2bc9b2c 100644 --- a/Views/Vehicle/_UpgradeRecords.cshtml +++ b/Views/Vehicle/_UpgradeRecords.cshtml @@ -1,7 +1,8 @@ -@inject IConfiguration Configuration +@using CarCareTracker.Helper +@inject IConfigHelper config @{ - var enableCsvImports = bool.Parse(Configuration[nameof(UserConfig.EnableCsvImports)]); - var hideZero = bool.Parse(Configuration[nameof(UserConfig.HideZero)]); + var enableCsvImports = config.GetUserConfig(User).EnableCsvImports; + var hideZero = config.GetUserConfig(User).HideZero; } @model List
diff --git a/Views/Vehicle/_VehicleHistory.cshtml b/Views/Vehicle/_VehicleHistory.cshtml index 0b2c036..4c4b0ac 100644 --- a/Views/Vehicle/_VehicleHistory.cshtml +++ b/Views/Vehicle/_VehicleHistory.cshtml @@ -1,8 +1,9 @@ -@inject IConfiguration Configuration +@using CarCareTracker.Helper +@inject IConfigHelper config @{ - var hideZero = bool.Parse(Configuration[nameof(UserConfig.HideZero)]); - var useMPG = bool.Parse(Configuration[nameof(UserConfig.UseMPG)]); - var useUKMPG = bool.Parse(Configuration[nameof(UserConfig.UseUKMPG)]); + var hideZero = config.GetUserConfig(User).HideZero; + var useMPG = config.GetUserConfig(User).UseMPG; + var useUKMPG = config.GetUserConfig(User).UseUKMPG; var useKwh = Model.VehicleData.IsElectric; string fuelEconomyUnit; if (useKwh) diff --git a/wwwroot/js/garage.js b/wwwroot/js/garage.js index f436ae7..a63f96b 100644 --- a/wwwroot/js/garage.js +++ b/wwwroot/js/garage.js @@ -14,6 +14,7 @@ function hideAddVehicleModal() { function loadGarage() { $.get('/Home/Garage', function (data) { $("#garageContainer").html(data); + loadSettings(); }); } function loadSettings() { From 4975861710304fd1f75e6d2a5f2e8596a435ed04 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sun, 14 Jan 2024 10:52:42 -0700 Subject: [PATCH 20/25] moved things around added delay admin page styling. --- Helper/ConfigHelper.cs | 26 +++++++++++++------------- Logic/LoginLogic.cs | 11 ++++++++++- Views/Admin/Index.cshtml | 6 +++++- Views/Home/_Settings.cshtml | 2 +- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/Helper/ConfigHelper.cs b/Helper/ConfigHelper.cs index 097430d..830e6c6 100644 --- a/Helper/ConfigHelper.cs +++ b/Helper/ConfigHelper.cs @@ -32,7 +32,7 @@ namespace CarCareTracker.Helper { userId = int.Parse(storedUserId); } - bool isRootUser = user.IsInRole(nameof(UserData.IsRootUser)); + bool isRootUser = user.IsInRole(nameof(UserData.IsRootUser)) || userId == -1; if (isRootUser) { try @@ -85,6 +85,16 @@ namespace CarCareTracker.Helper } public UserConfig GetUserConfig(ClaimsPrincipal user) { + var serverConfig = new UserConfig + { + EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]), + UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]), + UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]), + UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]), + EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]), + HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]), + UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]) + }; int userId = 0; if (user != null) { @@ -95,26 +105,16 @@ namespace CarCareTracker.Helper } } else { - return new UserConfig(); + return serverConfig; } return _cache.GetOrCreate($"userConfig_{userId}", entry => { entry.SlidingExpiration = TimeSpan.FromHours(1); - var serverConfig = new UserConfig - { - EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]), - UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]), - UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]), - UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]), - EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]), - HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]), - UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]) - }; if (!user.Identity.IsAuthenticated) { return serverConfig; } - bool isRootUser = user.IsInRole(nameof(UserData.IsRootUser)); + bool isRootUser = user.IsInRole(nameof(UserData.IsRootUser)) || userId == -1; if (isRootUser) { return serverConfig; diff --git a/Logic/LoginLogic.cs b/Logic/LoginLogic.cs index b709614..ac444c8 100644 --- a/Logic/LoginLogic.cs +++ b/Logic/LoginLogic.cs @@ -1,6 +1,7 @@ using CarCareTracker.External.Interfaces; using CarCareTracker.Helper; using CarCareTracker.Models; +using Microsoft.Extensions.Caching.Memory; using System.Net; using System.Net.Mail; using System.Security.Cryptography; @@ -31,11 +32,16 @@ namespace CarCareTracker.Logic private readonly IUserRecordDataAccess _userData; private readonly ITokenRecordDataAccess _tokenData; private readonly IMailHelper _mailHelper; - public LoginLogic(IUserRecordDataAccess userData, ITokenRecordDataAccess tokenData, IMailHelper mailHelper) + private IMemoryCache _cache; + public LoginLogic(IUserRecordDataAccess userData, + ITokenRecordDataAccess tokenData, + IMailHelper mailHelper, + IMemoryCache memoryCache) { _userData = userData; _tokenData = tokenData; _mailHelper = mailHelper; + _cache = memoryCache; } public bool CheckIfUserIsValid(int userId) { @@ -275,6 +281,7 @@ namespace CarCareTracker.Logic existingUserConfig.UserPasswordHash = hashedPassword; } File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(existingUserConfig)); + _cache.Remove("userConfig_-1"); return true; } public bool DeleteRootUserCredentials() @@ -288,6 +295,8 @@ namespace CarCareTracker.Logic existingUserConfig.UserNameHash = string.Empty; existingUserConfig.UserPasswordHash = string.Empty; } + //clear out the cached config for the root user. + _cache.Remove("userConfig_-1"); File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(existingUserConfig)); return true; } diff --git a/Views/Admin/Index.cshtml b/Views/Admin/Index.cshtml index 9f24c23..b1eccc8 100644 --- a/Views/Admin/Index.cshtml +++ b/Views/Admin/Index.cshtml @@ -3,6 +3,10 @@ } @model AdminViewModel
+
+ Admin Panel +
+
@@ -38,7 +42,7 @@ - + diff --git a/Views/Home/_Settings.cshtml b/Views/Home/_Settings.cshtml index 8ab00a2..76b3070 100644 --- a/Views/Home/_Settings.cshtml +++ b/Views/Home/_Settings.cshtml @@ -123,7 +123,7 @@ if (result.isConfirmed) { $.post('/Login/CreateLoginCreds', { userName: result.value.username, password: result.value.password }, function (data) { if (data) { - window.location.href = '/Login'; + setTimeout(function () { window.location.href = '/Login' }, 500); } else { errorToast("An error occurred, please try again later."); } From 62e196b97d8540f68b74acc65c5bcc760383335b Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sun, 14 Jan 2024 11:12:09 -0700 Subject: [PATCH 21/25] fix admin view. --- Views/Admin/Index.cshtml | 33 +++++++++++++++++++++------------ Views/Home/Index.cshtml | 15 +++++++++++++-- Views/Vehicle/Index.cshtml | 2 +- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/Views/Admin/Index.cshtml b/Views/Admin/Index.cshtml index b1eccc8..5491974 100644 --- a/Views/Admin/Index.cshtml +++ b/Views/Admin/Index.cshtml @@ -4,17 +4,26 @@ @model AdminViewModel
- Admin Panel +
+ +
+
+ Admin Panel +

-
- - +
+ +
+
+
+ + +
-
UserNameUsername Is Admin Reset Password Delete
@@ -74,9 +83,9 @@ } }); } - function deleteUser(userId){ - $.post(`/Admin/DeleteUser?userId=${userId}`, function(data){ - if (data){ + function deleteUser(userId) { + $.post(`/Admin/DeleteUser?userId=${userId}`, function (data) { + if (data) { reloadPage(); } }) @@ -86,12 +95,12 @@ navigator.clipboard.writeText(textToCopy); successToast("Copied to Clipboard"); } - function generateNewToken(){ + function generateNewToken() { Swal.fire({ title: 'Generate Token', html: ` - - `, + + `, confirmButtonText: 'Generate', focusConfirm: false, preConfirm: () => { @@ -104,7 +113,7 @@ }).then(function (result) { if (result.isConfirmed) { var autoNotify = $("#enableAutoNotify").is(":checked"); - $.get('/Admin/GenerateNewToken', {emailAddress: result.value.emailAddress, autoNotify: autoNotify}, function (data) { + $.get('/Admin/GenerateNewToken', { emailAddress: result.value.emailAddress, autoNotify: autoNotify }, function (data) { if (data.success) { reloadPage(); } else { diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml index 5eddcea..730d5cf 100644 --- a/Views/Home/Index.cshtml +++ b/Views/Home/Index.cshtml @@ -45,8 +45,19 @@ @if (User.IsInRole("CookieAuth")) { - } diff --git a/Views/Vehicle/Index.cshtml b/Views/Vehicle/Index.cshtml index 998847e..25ed3c7 100644 --- a/Views/Vehicle/Index.cshtml +++ b/Views/Vehicle/Index.cshtml @@ -42,7 +42,7 @@ - From 6540d96d4dd7ab9e5ed8a95c278f130ac4a4954b Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sun, 14 Jan 2024 11:17:51 -0700 Subject: [PATCH 22/25] fixed admin view even more. --- Views/Admin/Index.cshtml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Views/Admin/Index.cshtml b/Views/Admin/Index.cshtml index 5491974..5a93ffe 100644 --- a/Views/Admin/Index.cshtml +++ b/Views/Admin/Index.cshtml @@ -13,9 +13,11 @@
-
+
+ Tokens +
-
+
@@ -47,7 +49,9 @@
-
+
+ Users +
From 7122f4ac0d4920d375a2c510e7f976dede7403a7 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sun, 14 Jan 2024 11:51:57 -0700 Subject: [PATCH 23/25] renamed report and make it default tab. --- Views/Admin/Index.cshtml | 5 +- Views/Home/Index.cshtml | 8 ++- Views/Vehicle/Index.cshtml | 21 ++++---- Views/Vehicle/_Report.cshtml | 96 ++++-------------------------------- wwwroot/js/reports.js | 76 ++++++++++++++++++++++++++++ wwwroot/js/vehicle.js | 2 +- 6 files changed, 107 insertions(+), 101 deletions(-) create mode 100644 wwwroot/js/reports.js diff --git a/Views/Admin/Index.cshtml b/Views/Admin/Index.cshtml index 5a93ffe..171553a 100644 --- a/Views/Admin/Index.cshtml +++ b/Views/Admin/Index.cshtml @@ -56,8 +56,8 @@ + - @@ -66,8 +66,8 @@ { + - } @@ -77,6 +77,7 @@ + }
@@ -19,7 +20,10 @@ + - @@ -61,7 +62,10 @@
diff --git a/Views/Vehicle/_Report.cshtml b/Views/Vehicle/_Report.cshtml index 9cb0a6b..7d0d651 100644 --- a/Views/Vehicle/_Report.cshtml +++ b/Views/Vehicle/_Report.cshtml @@ -69,94 +69,16 @@
-
- -
-
-
- -
-
@await Html.PartialAsync("_Collaborators", Model.Collaborators)
+
+ +
+
+
+ +
+
-
- \ No newline at end of file +
\ No newline at end of file diff --git a/wwwroot/js/reports.js b/wwwroot/js/reports.js new file mode 100644 index 0000000..3137ea0 --- /dev/null +++ b/wwwroot/js/reports.js @@ -0,0 +1,76 @@ +function getYear() { + return $("#yearOption").val(); +} +function generateVehicleHistoryReport() { + var vehicleId = GetVehicleId().vehicleId; + $.get(`/Vehicle/GetVehicleHistory?vehicleId=${vehicleId}`, function (data) { + if (data) { + $("#vehicleHistoryReport").html(data); + setTimeout(function () { + window.print(); + }, 500); + } + }) +} +var debounce = null; +function updateCheck(sender) { + clearTimeout(debounce); + debounce = setTimeout(function () { + refreshBarChart(); + }, 1000); +} +function refreshBarChart(callBack) { + var selectedMetrics = []; + var vehicleId = GetVehicleId().vehicleId; + var year = getYear(); + + if ($("#serviceExpenseCheck").is(":checked")) { + selectedMetrics.push('ServiceRecord'); + } + if ($("#repairExpenseCheck").is(":checked")) { + selectedMetrics.push('RepairRecord'); + } + if ($("#upgradeExpenseCheck").is(":checked")) { + selectedMetrics.push('UpgradeRecord'); + } + if ($("#gasExpenseCheck").is(":checked")) { + selectedMetrics.push('GasRecord'); + } + if ($("#taxExpenseCheck").is(":checked")) { + selectedMetrics.push('TaxRecord'); + } + + $.post('/Vehicle/GetCostByMonthByVehicle', + { + vehicleId: vehicleId, + selectedMetrics: selectedMetrics, + year: year + }, function (data) { + $("#gasCostByMonthReportContent").html(data); + if (callBack != undefined) { + callBack(); + } + }); +} +function updateReminderPie() { + var vehicleId = GetVehicleId().vehicleId; + var daysToAdd = $("#reminderOption").val(); + $.get(`/Vehicle/GetReminderMakeUpByVehicle?vehicleId=${vehicleId}`, { daysToAdd: daysToAdd }, function (data) { + $("#reminderMakeUpReportContent").html(data); + }); +} +//called when year selected is changed. +function yearUpdated() { + var vehicleId = GetVehicleId().vehicleId; + var year = getYear(); + $.get(`/Vehicle/GetCostMakeUpForVehicle?vehicleId=${vehicleId}`, { year: year }, function (data) { + $("#costMakeUpReportContent").html(data); + refreshBarChart(); + }) +} +function refreshCollaborators() { + var vehicleId = GetVehicleId().vehicleId; + $.get(`/Vehicle/GetCollaboratorsForVehicle?vehicleId=${vehicleId}`, function (data) { + $("#collaboratorContent").html(data); + }); +} \ No newline at end of file diff --git a/wwwroot/js/vehicle.js b/wwwroot/js/vehicle.js index 788b23b..a104a78 100644 --- a/wwwroot/js/vehicle.js +++ b/wwwroot/js/vehicle.js @@ -58,7 +58,7 @@ $(document).ready(function () { break; } }); - getVehicleServiceRecords(vehicleId); + getVehicleReport(vehicleId); }); function getVehicleNotes(vehicleId) { From 6e5cfde2cfdd6b1f9cda68b24aff23e588e88e81 Mon Sep 17 00:00:00 2001 From: "DESKTOP-GENO133\\IvanPlex" Date: Sun, 14 Jan 2024 12:13:37 -0700 Subject: [PATCH 24/25] only allow notification if smtp server is setup. --- External/Implementations/UserConfigDataAccess.cs | 3 ++- Helper/MailHelper.cs | 8 ++++++++ Views/Admin/Index.cshtml | 11 ++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/External/Implementations/UserConfigDataAccess.cs b/External/Implementations/UserConfigDataAccess.cs index 83003b7..7d63103 100644 --- a/External/Implementations/UserConfigDataAccess.cs +++ b/External/Implementations/UserConfigDataAccess.cs @@ -32,7 +32,8 @@ namespace CarCareTracker.External.Implementations using (var db = new LiteDatabase(dbName)) { var table = db.GetCollection(tableName); - return table.Delete(userId); + table.Delete(userId); + return true; }; } } diff --git a/Helper/MailHelper.cs b/Helper/MailHelper.cs index ed86618..6915f1d 100644 --- a/Helper/MailHelper.cs +++ b/Helper/MailHelper.cs @@ -20,6 +20,10 @@ namespace CarCareTracker.Helper } public OperationResponse NotifyUserForRegistration(string emailAddress, string token) { + if (string.IsNullOrWhiteSpace(mailConfig.EmailServer)) + { + return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" }; + } if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token)) { return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" }; } @@ -36,6 +40,10 @@ namespace CarCareTracker.Helper } public OperationResponse NotifyUserForPasswordReset(string emailAddress, string token) { + if (string.IsNullOrWhiteSpace(mailConfig.EmailServer)) + { + return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" }; + } if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token)) { return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" }; diff --git a/Views/Admin/Index.cshtml b/Views/Admin/Index.cshtml index 171553a..81bba62 100644 --- a/Views/Admin/Index.cshtml +++ b/Views/Admin/Index.cshtml @@ -1,6 +1,15 @@ @{ ViewData["Title"] = "Admin"; } +@inject IConfiguration config; +@{ + bool emailServerIsSetup = true; + var mailConfig = config.GetSection("MailConfig").Get(); + if (mailConfig is null || string.IsNullOrWhiteSpace(mailConfig.EmailServer)) + { + emailServerIsSetup = false; + } +} @model AdminViewModel
@@ -22,7 +31,7 @@
- +
From d67aeaa333f0cf969bfea6544f262fdf6ddd32d5 Mon Sep 17 00:00:00 2001 From: "DESKTOP-T0O5CDB\\DESK-555BD" Date: Sun, 14 Jan 2024 13:11:00 -0700 Subject: [PATCH 25/25] removed fueleconomy from fuelly mapper. --- MapProfile/FuellyMappers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MapProfile/FuellyMappers.cs b/MapProfile/FuellyMappers.cs index b90d948..8f3ed5e 100644 --- a/MapProfile/FuellyMappers.cs +++ b/MapProfile/FuellyMappers.cs @@ -9,7 +9,7 @@ namespace CarCareTracker.MapProfile { Map(m => m.Date).Name(["date", "fuelup_date"]); Map(m => m.Odometer).Name(["odometer"]); - Map(m => m.FuelConsumed).Name(["gallons", "liters", "litres", "consumption", "quantity", "fueleconomy", "fuelconsumed"]); + Map(m => m.FuelConsumed).Name(["gallons", "liters", "litres", "consumption", "quantity", "fuelconsumed"]); Map(m => m.Cost).Name(["cost", "total cost", "totalcost", "total price"]); Map(m => m.Notes).Name("notes", "note"); Map(m => m.Price).Name(["price"]);
UsernameEmail Is AdminReset Password Delete
@userData.UserName@userData.EmailAddress @userData.Id@userData.Id