From 6f8b7af78e4973c80f69eaabf56ab8f57977e244 Mon Sep 17 00:00:00 2001 From: Garrritt McCune Date: Wed, 13 Jan 2021 16:41:20 -0600 Subject: [PATCH] Added and tested the password reset feature. So far so good, needs some more validation but its working and that's a good first step. --- .vs/SecureCore/v16/.suo | Bin 58880 -> 58880 bytes SecureCore/Authentication/PasswordManager.cs | 34 +++++++++++----- SecureCore/Controllers/AuthController.cs | 40 +++++++++++++------ 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/.vs/SecureCore/v16/.suo b/.vs/SecureCore/v16/.suo index 21a296fbbf84dfa512830f25040ea3abd6d517b7..2ea93876ec243ef5b43ce6e74a999af90783e8fd 100644 GIT binary patch delta 2305 zcmZoT!`yI&c|#5p-vON;&;Pu<#%RgFz;M8Oaurh*qu%7NO!bUrlS`TF8P8At%B;_5 zK3SDTpH+{6fk9?spfF?Jy$|85kIp85kIJ7#J8#85kINCm&>y zW;B?5m!)1Zf`Ne{lYxQZ@Bjb*qZt?&Vi*`0Vi_11GA0IUv;O=4|G&b-Kw;i|1_p)# z1_p*g1_p+xiGkLWm~uE99xclI{qh>4(dJUN^^C0M3=9m4lQlV{8RaIsawyxfF)%P> zF)%R1GcYjx|Ns9#9|HqJ0s{ksGy?;J6axc;CR7|G2g066e9no1@-n;(3=GB$3=Ad= z3=C#aNec!B215o0hRF{&99Y#D7#QLv2HHHfk6Rk3dp)_1_p*41_p*)1_p+tiGkjexEd@K85tOq7#SFp85tN< z7#SE;85tPV7#SGU85tNf7#SEe85tO~7$N5AFfuUcLfQW|Cb~0j;&EozoO3MFDmCb8NC@87z{un5bL-%|4{X2 zntWK;kQj$(HZU`qPW~!Z&uB8aR6Ljk*)fd9o4@M%GjV{TO%-7Q`{ojZ^Nf?7!x}j> z7#J9YkYpx*G4kgGM?ENRCPN&=zPZFWgpoxHN!{izCOnLb;+Pg$uuU>^o_tu)c5;K6 z^JHgc!^sEASvLPL<6xY8>8{vjF^5;olcGFWbdmLz*!3`Oay!BT4~ETORMs#mf?Nr* zLmQgvL3%*h0~9@=;4z&1QA=C_lw?6E3Y5337#J9=85kIB7#J9A85kJsK(_o)li>j+ z9uW2b2`H#5uz)z5U3?3eS$2Yic4+BD>|$VG*v-Jeu!n(xVJ`y%!#)NEhW!i-3b-*4}z-*qqIF)Hrp9B*lkUlrRJ^Br+5;6f=}FT2YGnXGp=SqX1!(AlNEUVgN-H z$fui@&12>QrEO3N<8pBFhxrj8X$D3H28JI=f%eDPFAGixW;o!#m}wzMJxClxgD}hF zKv`{24x2oooI@TY!vqqB;}V8UhGd3B1`wS;*|A)d(;4j9RE8Oo8S9i;9=!Yie{w^E z&Ex}BB9lexbtY9C@uo5)Gn6tEF@Wg&$@bL;H`SRi^BOZaGPp6gGSo8YF)&PSY_{Lr z&{)PK>S&>B;A-NiYvSh2&A@c~3ak68J`(*udf|FxfEhhK$6l^}Q`~c%*#a5BY z0=-pYDsaz99dKe`V5neVU~t(#`NRB}%?+y#Fi!s1nlkypl$c3t9VWk<;xM^k?SaWP zQ(a(|pawM9ZIiQBiA|oc#%FTR8iPq2947Bs<1#tBS6(3;ZjsUfP~rk5G9Cs724<-E zVw8=2pdCy<=i1VQOgJH7cMnm>Qh608HhT_Tg8#E^; zHAqZu*x<04am#;3#{Y{O?l4WBWFazn%Kew+Eq&Ewft?=~ zU0|8~sb6KXfK|lgKRY=lGu>62{BJuSdnh>Z7eS1GXa_|!h7l7k7fhbyC^0!oZSG$w28iI`ll!(wv9#S@bS?zv5VVJWfs-c2Q@#SQluCm&E` Jnapyh1_1T`yJY|X delta 1440 zcmZoT!`yI&c|#5pXTp2c&*xuWW3=2{%GS*|iA9A`cd{yrKC2D`1B1-OKw-wr$+;}@ ztOg7W4EmENvd9bSF)%QgGcYi4GcYhHGcYiiPCm#Y&B#0XE=#>+ECT~W6axdp-~a#r zM>8-m#4s>0{QLj^f5yZ>ZB_*a28R5Jf#RG63=9l~3=9kr69cU$G39VB@QZy9a)R;Z zQnvMstcnZ_404k-Iiwk-CcAPd+ww6mFk~|@FeETAF#P}j|Gy>!14AMM1A{aJ1A`F* z1A`${93%(AaY%f&iGlJmoD2*MybKHsCJYP=W>85BC_VWBhXbo80|SHF#6bJWDV!pX z@eB+M{0s~X@(c_NNem1O=?n}EDGUq@sSFGZX;A-UL3M+y19>Zlfq@~Hfq@~9fq@}; zVxapX9;Qt^&TNyNnGGkG2>FBQFG2wvstgPaVhjunljRi^CmS%aZ!Qr&&kU9=5f5R} zMV9#@-oQAyguiE#R0+rC5`iAZNh(KJ#6c?h6?Ha$@mj;Ud4bwuMu=vikjeduI+MQ$ zg-kvnth2d9vw?Z?7qJT*AoqdwK<#7H+Wb}5p9w6k1u_dNzPZHUJR>I~7Xt%>B$Dvt z5~G00N5VF2E-?;aob17@v-yh&591^|=gH174V;Yb3=9my$jT2Cvuyrh$HBPBf^CwE z^W?*VwwoJV8W<-BZ5P`t=J1MnQo#upP;9}xKlw|+6&4O86IkyuFfeS~{L}R`3kxW$ zK)!@dTGd9z9V8MZ}v zOq<#^Ffy`D-q`Lxc|uFjr8@aQw*$n; zJ}1V_5@jzKCo}EUnq0SsZ}Nv5GMhts)-X<5c3|^?Ne+xKduljHv0(FoZU-ia5%-ul zSwQwOFfh1G7My>0a^8Be%?>`Gj93M$oPNL2^N;g238i# z3=C|O?ayqJw#h5a$+2TF&L}C7+xbbq*!OI5b(?q-^R@ISRL2q*2ZnnuHdvZ3NV`SRQIAty)F^-$UHCdtAX0pSX ztK{k+|pjd!`fuU)#{Hnys6BbD)l<8WYpx ZWPP#C5*J=DF1p4sIcP%E=BOLXSOIK7$&&y8 diff --git a/SecureCore/Authentication/PasswordManager.cs b/SecureCore/Authentication/PasswordManager.cs index 97c0096..5376ac9 100644 --- a/SecureCore/Authentication/PasswordManager.cs +++ b/SecureCore/Authentication/PasswordManager.cs @@ -29,20 +29,22 @@ namespace SecureCore.Authentication return (GetHash(password, salt), Convert.ToBase64String(salt)); } - public static (bool IsValid, string Message) IsPasswordValid(string password, string salt, string passwordHash) + public static (bool IsValid, string Message) IsPasswordValid(string password) { if (password.Length < MinPasswordLength) return (false, $"Your password is too short, it must be at least {MinPasswordLength} characters long and not exceed {MaxPasswordLength} characters."); if (password.Length > MaxPasswordLength) return (false, $"Your password is too long, it must not exceed {MaxPasswordLength} characters and must contain at least {MinPasswordLength} characters."); - var saltBytes = Convert.FromBase64String(salt); - - if (passwordHash == GetHash(password, saltBytes)) - return (true, string.Empty); - else - return (false, "User name or password is not correct."); + return (true, string.Empty); } - public static void InsertPasswordResetRequest(string userName, string sessionToken, DateTime expirationDate, string userAgent, string ipAddress, string connectionString) + public static bool IsPasswordAMatch(string password, string salt, string passwordHash) + { + var saltBytes = Convert.FromBase64String(salt); + + return passwordHash == GetHash(password, saltBytes); + } + + public static void InsertPasswordResetRequest(string email, string sessionToken, DateTime expirationDate, string userAgent, string ipAddress, string connectionString) { using var connection = new SqlConnection(connectionString); @@ -50,7 +52,7 @@ namespace SecureCore.Authentication command.Parameters.AddWithValue("SessionToken", sessionToken); command.Parameters.AddWithValue("ExpirationDate", expirationDate); - command.Parameters.AddWithValue("UserName", userName); + command.Parameters.AddWithValue("Email", email); command.Parameters.AddWithValue("UserAgent", userAgent); command.Parameters.AddWithValue("IpAddress", ipAddress); @@ -59,7 +61,19 @@ namespace SecureCore.Authentication command.ExecuteNonQuery(); } - //TODO: Reset Password Function Here + public static void ResetPassword(string passwordHash, string salt, string sessionToken, string connectionString) + { + using var connection = new SqlConnection(connectionString); + using var command = new SqlCommand("ResetPassword", connection) { CommandType = CommandType.StoredProcedure }; + + command.Parameters.AddWithValue("PasswordHash", passwordHash); + command.Parameters.AddWithValue("Salt", salt); + command.Parameters.AddWithValue("SessionToken", sessionToken); + + connection.Open(); + + command.ExecuteNonQuery(); + } public static (string PasswordHash, string Salt) GetPasswordHashAndSalt(string username, string connectionString) { diff --git a/SecureCore/Controllers/AuthController.cs b/SecureCore/Controllers/AuthController.cs index ac48bf4..c9676d8 100644 --- a/SecureCore/Controllers/AuthController.cs +++ b/SecureCore/Controllers/AuthController.cs @@ -23,25 +23,26 @@ namespace SecureCore.Controllers //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")) + if (SessionManager.IsSessionTokenValid(HttpContext.Request.Cookies["Session"], connectionString)) + return Ok("Logged in\n"); + //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); - //if (HttpContext.Request.Cookies.ContainsKey("Session")) - //{ - // if(UserDataService.IsSessionTokenValid(HttpContext.Request.Cookies["Session"]))//, info.UserName)) - // return Ok($"Session is live{Environment.NewLine}"); - //} //Next try to get the user's login data, if the fuction returns empty strings, then the user isn't a registered name. var (password, salt) = PasswordManager.GetPasswordHashAndSalt(info.UserName, connectionString); //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, salt, password); + 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(); var agent = HttpContext.Request.Headers[Microsoft.Net.Http.Headers.HeaderNames.UserAgent]; var ip = PasswordManager.HashStringData(Request.HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString(), salt); @@ -64,6 +65,15 @@ namespace SecureCore.Controllers [AcceptVerbs("POST")] public IActionResult Register([FromBody] LoginInfo info) { + //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); + //Validate the password, make sure its not to long or short, etc. + result = PasswordManager.IsPasswordValid(info.Password); + + if (!result.IsValid) return Unauthorized(result.Message); + AppSettingsManager.TryGetConnectionStringByName("MainDataConnectionString", out string connectionString); var (hash, salt) = PasswordManager.HashPassword(info.Password); var sessionToken = SessionManager.CreateSessionToken(); @@ -99,9 +109,9 @@ namespace SecureCore.Controllers return Ok(); } - [HttpGet("ResetPassword")] - //[AcceptVerbs("GET")] - public IActionResult ResetPassword([FromQuery] string token) + [HttpPost("ResetPassword")] + [AcceptVerbs("POST")] + public IActionResult ResetPassword([FromQuery] string token, [FromBody] string password) { AppSettingsManager.TryGetConnectionStringByName("MainDataConnectionString", out string connectionString); @@ -109,17 +119,21 @@ namespace SecureCore.Controllers { if (!SessionManager.IsSessionTokenValid(token, connectionString, true)) return Unauthorized("Token invalid"); + var (Hash, Salt) = PasswordManager.HashPassword(password); + + PasswordManager.ResetPassword(Hash, Salt, token, connectionString); + + return Ok("Reset successful\n"); } catch (Exception e) { - + return Unauthorized(e.Message); } - return Ok("Done"); } [HttpPost("CreatePasswordResetLink")] [AcceptVerbs("POST")] - public IActionResult CreatePasswordResetLink([FromBody] string userName) //TODO: this sig should only accept an email, so the link can be sent there. + public IActionResult CreatePasswordResetLink([FromBody] string email) //TODO: this sig should only accept an email, so the link can be sent there. { AppSettingsManager.TryGetConnectionStringByName("MainDataConnectionString", out string connectionString); var token = SessionManager.CreateSessionToken(); @@ -128,7 +142,7 @@ namespace SecureCore.Controllers try { - PasswordManager.InsertPasswordResetRequest(userName, token, DateTime.Now.AddHours(1), agent, ip, connectionString); + PasswordManager.InsertPasswordResetRequest(email, token, DateTime.Now.AddHours(1), agent, ip, connectionString); token = HttpUtility.UrlEncode(token);