public async Task Alias_IndexPatchMatchingOld_NoContentWithChangedPassword() { using (IdentityWsDbContext ef = CreateEf()) { Being being = new Being { SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword("password1") }; ef.Aliases.Add(new Alias { EmailAddress = "*****@*****.**", Being = being }); await ef.SaveChangesAsync(); AliasesController patient = new AliasesController(ef, dummyLog, now, dummyRunner); AliasesController.IndexPatchRequestBody body = new AliasesController.IndexPatchRequestBody { oldPassword = "******", password = "******" }; IActionResult result = await patient.IndexPatch("*****@*****.**", body); result.Should().BeOfType <NoContentResult>($"'{nameof(body.oldPassword)}' matched"); Sha512Util.TestPassword("p@ssword1", being.SaltedHashedPassword).Should().BeTrue("the password should have been changed"); } }
public async Task <IActionResult> Login([EmailAddress, MaxLength(100)] string email_address, [Required, MaxLength(20)] string client, [FromBody] LoginRequestBody body) { if (!ModelState.IsValid) { return(BadRequest(ModelState)); } // Get the entities. Alias alias = await ef.Aliases .Include(a => a.Being).ThenInclude(b => b.Clients) .FirstOrDefaultAsync(a => a.EmailAddress == email_address); Being being = alias?.Being; if (being == null || !being.Clients.Any(c => c.ClientName == client)) { return(NotFound()); } // Check the number of consecutive failures. DateTime period_start = now.UtcNow.AddMinutes(-1 * config.GetValue <double>("LockoutPeriodMins")); int consecutive_failures = await ef.LoginAttempts .Where(a => a.Alias.BeingID == being.BeingID && a.DateCreated >= period_start && !a.Success && !ef.LoginAttempts.Any(a2 => a2.Alias.BeingID == being.BeingID && a2.LoginAttemptID > a.LoginAttemptID && a2.Success)) .CountAsync(); if (consecutive_failures >= config.GetValue <int>("MaxFailedLoginsBeforeLockout")) { return(StatusCode(StatusCodes.Status503ServiceUnavailable)); } // Check the password. bool password_ok = Sha512Util.TestPassword(body.password, being.SaltedHashedPassword); // Log the attempt. ef.LoginAttempts.Add(new LoginAttempt { AliasID = alias.AliasID, Success = password_ok, ClientName = client }); await ef.SaveChangesAsync(); return(password_ok ? (IActionResult)NoContent() : Unauthorized()); }
public async Task <IActionResult> IndexPatch([EmailAddress, MaxLength(100)] string email_address, [FromBody] IndexPatchRequestBody body) { if (!ModelState.IsValid) { return(BadRequest(ModelState)); } // Get the being. Being being = (await ef.Aliases .Include(a => a.Being) .FirstOrDefaultAsync(a => a.EmailAddress == email_address)) ?.Being; if (being == null) { return(NotFound()); } if (body.resetToken != null) { // Authenticate via reset token. if (body.oldPassword != null) { // Only one of resetToken or oldPassword may be supplied. return(BadRequest()); } if (!being.PasswordResetTokenValidUntil.HasValue || being.PasswordResetTokenValidUntil <= now.UtcNow || being.PasswordResetToken != body.resetToken) { return(Unauthorized()); } if (Sha512Util.TestPassword(body.password, being.SaltedHashedPassword)) { // Cannot change password to itself. return(StatusCode(StatusCodes.Status409Conflict)); } // The token is used up. being.PasswordResetToken = null; being.PasswordResetTokenValidUntil = null; } else { // Authenticate via old password. if (body.oldPassword == null) { // One of resetToken or oldPassword must be supplied. return(BadRequest()); } if (!Sha512Util.TestPassword(body.oldPassword, being.SaltedHashedPassword)) { return(Unauthorized()); } if (body.oldPassword == body.password) { // Cannot change password to itself. return(StatusCode(StatusCodes.Status409Conflict)); } } // Change the password. being.SaltedHashedPassword = Sha512Util.SaltAndHashNewPassword(body.password); await ef.SaveChangesAsync(); return(NoContent()); }
public void MismatchingPasswordAndHash_TestPassword_ReturnsFalse() { Sha512Util.TestPassword("Hello worldx!", "$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1") .Should().BeFalse("the hash does not match the password"); }