diff --git a/SecureCore/Authentication/PasswordManager.cs b/SecureCore/Authentication/PasswordManager.cs index 5376ac9..51907db 100644 --- a/SecureCore/Authentication/PasswordManager.cs +++ b/SecureCore/Authentication/PasswordManager.cs @@ -47,7 +47,6 @@ namespace SecureCore.Authentication public static void InsertPasswordResetRequest(string email, string sessionToken, DateTime expirationDate, string userAgent, string ipAddress, string connectionString) { using var connection = new SqlConnection(connectionString); - using var command = new SqlCommand("LogPasswordResetRequest", connection) { CommandType = CommandType.StoredProcedure }; command.Parameters.AddWithValue("SessionToken", sessionToken); @@ -78,7 +77,6 @@ namespace SecureCore.Authentication public static (string PasswordHash, string Salt) GetPasswordHashAndSalt(string username, string connectionString) { using var connection = new SqlConnection(connectionString); - using var command = new SqlCommand("SELECT [Password Hash], [Salt] FROM dbo.GetUserPasswordHashAndSalt(@Username)", connection); command.Parameters.AddWithValue("Username", username); diff --git a/SecureCore/Authentication/SessionManager.cs b/SecureCore/Authentication/SessionManager.cs index 81e1f65..658b4d7 100644 --- a/SecureCore/Authentication/SessionManager.cs +++ b/SecureCore/Authentication/SessionManager.cs @@ -20,7 +20,6 @@ namespace SecureCore.Authentication public static bool IsSessionTokenValid(string sessionToken, string connectionString, bool isResetToken = false) { using var connection = new SqlConnection(connectionString); - using var command = new SqlCommand("ValidateSessionToken", connection) { CommandType = CommandType.StoredProcedure }; command.Parameters.AddWithValue("SessionToken", sessionToken); @@ -36,7 +35,6 @@ namespace SecureCore.Authentication public static void Logout(string sessionToken, string connectionString) { using var connection = new SqlConnection(connectionString); - using var command = new SqlCommand("LogoutUser", connection) { CommandType = CommandType.StoredProcedure }; command.Parameters.AddWithValue("SessionToken", sessionToken); @@ -49,7 +47,6 @@ namespace SecureCore.Authentication public static void Login(string userName, string sessionToken, DateTime expirationDate, string userAgent, string ipAddress, string connectionString) { using var connection = new SqlConnection(connectionString); - using var command = new SqlCommand("LoginUser", connection) { CommandType = CommandType.StoredProcedure }; command.Parameters.AddWithValue("SessionToken", sessionToken); diff --git a/SecureCore/Controllers/AuthController.cs b/SecureCore/Controllers/AuthController.cs index c8196de..13b4dcc 100644 --- a/SecureCore/Controllers/AuthController.cs +++ b/SecureCore/Controllers/AuthController.cs @@ -17,7 +17,7 @@ namespace SecureCore.Controllers public IActionResult Login([FromBody] UserInformation.LoginData info) { AppSettingsManager.TryGetConnectionStringByName("MainDataConnectionString", out string connectionString); - //TODO: see if the request has an active session key so we don't spam out new keys like mad + try { if (HttpContext.Request.Cookies.ContainsKey("Session")) @@ -27,6 +27,10 @@ namespace SecureCore.Controllers //Verify that the username provided is valid, i.e. no whitespace, special characters, etc. var result = UserDataService.IsUsernameValid(info.Username); //If the name isn't valid, return the error message to the user. + if (!result.IsValid) return Unauthorized(result.Message); + //Now make sure the password is valid. + result = PasswordManager.IsPasswordValid(info.Password); + if (!result.IsValid) return Unauthorized(result.Message); //Next try to get the user's login data, if the fuction returns empty strings, then the user isn't a registered name. @@ -34,10 +38,6 @@ namespace SecureCore.Controllers //If the user name isn't in the system, then simply return a generic error message about something not being right. if (password == string.Empty) return Unauthorized("User name or password is not correct."); - result = PasswordManager.IsPasswordValid(info.Password); - - if (!result.IsValid) return Unauthorized(result.Message); - if(!PasswordManager.IsPasswordAMatch(info.Password, salt, password)) return Unauthorized("User name or password is not correct."); var sessionToken = SessionManager.CreateSessionToken(); @@ -62,6 +62,7 @@ namespace SecureCore.Controllers [AcceptVerbs("POST")] public IActionResult Register([FromBody] UserInformation.RegistrationData info) { + AppSettingsManager.TryGetConnectionStringByName("MainDataConnectionString", out string connectionString); //Verify that the username provided is valid, i.e. no whitespace, special characters, etc. var result = UserDataService.IsUsernameValid(info.Username); //If the name isn't valid, return the error message to the user. @@ -71,7 +72,12 @@ namespace SecureCore.Controllers if (!result.IsValid) return Unauthorized(result.Message); - AppSettingsManager.TryGetConnectionStringByName("MainDataConnectionString", out string connectionString); + result = UserDataService.IsEmailValid(info.Email); + + if (!result.IsValid) return Unauthorized(result.Message); + + if (UserDataService.IsEmailInUse(info.Email, connectionString)) return Unauthorized("This email is already in use."); + var (hash, salt) = PasswordManager.HashPassword(info.Password); var sessionToken = SessionManager.CreateSessionToken(); var agent = HttpContext.Request.Headers[Microsoft.Net.Http.Headers.HeaderNames.UserAgent]; @@ -97,9 +103,18 @@ namespace SecureCore.Controllers [AcceptVerbs("POST")] public IActionResult Logout() { + if (!HttpContext.Request.Cookies.ContainsKey("Session")) return Ok(); + AppSettingsManager.TryGetConnectionStringByName("MainDataConnectionString", out string connectionString); - SessionManager.Logout(HttpContext.Request.Cookies["Session"], connectionString); + try + { + SessionManager.Logout(HttpContext.Request.Cookies["Session"], connectionString); + } + catch(Exception e) + { + //TODO: decide what to do here. If the SQL fails to clear the token from the database, do we want to clear the token cookie? + } HttpContext.Response.Cookies.Delete("Session"); @@ -111,10 +126,14 @@ namespace SecureCore.Controllers public IActionResult ResetPassword([FromQuery] string token, [FromBody] string password) { AppSettingsManager.TryGetConnectionStringByName("MainDataConnectionString", out string connectionString); - + try { - if (!SessionManager.IsSessionTokenValid(token, connectionString, true)) return Unauthorized("Token invalid"); + if (!SessionManager.IsSessionTokenValid(token, connectionString, true)) return Unauthorized("This link has expired, please request a new email reset link."); + + var (IsValid, Message) = PasswordManager.IsPasswordValid(password); + + if (!IsValid) return Unauthorized(Message); var (Hash, Salt) = PasswordManager.HashPassword(password); @@ -133,6 +152,14 @@ namespace SecureCore.Controllers public IActionResult CreatePasswordResetLink([FromBody] string email) { AppSettingsManager.TryGetConnectionStringByName("MainDataConnectionString", out string connectionString); + //Validate the email input. + var (IsValid, Message) = UserDataService.IsEmailValid(email); + + if (!IsValid) return Unauthorized(Message); + + //If its a properly formatted email, then check to see if its in use by anyone. + if (!UserDataService.IsEmailInUse(email, connectionString)) return Unauthorized("Email is not valid."); + var token = SessionManager.CreateSessionToken(); var agent = HttpContext.Request.Headers[Microsoft.Net.Http.Headers.HeaderNames.UserAgent]; var ip = PasswordManager.HashStringData(Request.HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString()); diff --git a/SecureCore/Services/UserDataService.cs b/SecureCore/Services/UserDataService.cs index cb307ff..1e24676 100644 --- a/SecureCore/Services/UserDataService.cs +++ b/SecureCore/Services/UserDataService.cs @@ -11,6 +11,7 @@ namespace SecureCore.Services { // public static int UserNameMaxLength { get; } = 64; + public static int EmailMaxLength { get; } = 512; public static (bool IsValid, string Message) IsUsernameValid(string userName) { @@ -29,7 +30,6 @@ namespace SecureCore.Services public static int RegisterNewUser(string userName, string email, string passwordHash, string saltHash, string sessionToken, DateTime expirationDate, string userAgent, string ipAddress, string connectionString) { using var connection = new SqlConnection(connectionString); - using var command = new SqlCommand("RegisterNewUser", connection) { CommandType = CommandType.StoredProcedure }; command.Parameters.AddWithValue("UserName", userName); @@ -47,5 +47,36 @@ namespace SecureCore.Services return Convert.ToInt32(result); } + + public static bool IsEmailInUse(string email, string connectionString) + { + using var connection = new SqlConnection(connectionString); + using var command = new SqlCommand("SELECT dbo.EmailInUse(@Email) AS InUse", connection); + + command.Parameters.AddWithValue("Email", email); + + connection.Open(); + + return Convert.ToBoolean(command.ExecuteScalar()); + } + + public static (bool IsValid, string Message) IsEmailValid(string email) + { + if (email.Length > EmailMaxLength) return (false, $"Email exceeds the maximum length of {EmailMaxLength} characters."); + + //Code taken from user "Cogwheel" on StackOverflow: https://stackoverflow.com/questions/1365407/c-sharp-code-to-validate-email-address + try + { + var addr = new System.Net.Mail.MailAddress(email); + + if (addr.Address != email) return (false, $"The provided email is not in the correct format."); + + return (true, string.Empty); + } + catch + { + return (false, $"The provided email is not in the correct format."); + } + } } }