Forgot Password (#1017)

* Implemented forgot password flow. Fixed a bug in manage user where admins were showing the Sharing With section.

* Cleaned up the reset password flow.

* Reverted some debug code

* Fixed an issue with invites due to ImmutableArray not being set.
This commit is contained in:
Joseph Milazzo 2022-02-01 06:04:23 -08:00 committed by GitHub
parent 8564378b77
commit 8ff123e06c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 271 additions and 38 deletions

View file

@ -394,6 +394,7 @@ namespace API.Controllers
}
[Authorize(Policy = "RequireAdminRole")]
[HttpPost("invite")]
public async Task<ActionResult<string>> InviteUser(InviteUserDto dto)
@ -439,7 +440,7 @@ namespace API.Controllers
var roleResult = await _userManager.AddToRoleAsync(user, role);
if (!roleResult.Succeeded)
return
BadRequest(roleResult.Errors); // TODO: Combine all these return BadRequest into one big thing
BadRequest(roleResult.Errors);
}
// Grant access to libraries
@ -482,7 +483,7 @@ namespace API.Controllers
}
return Ok(emailLink);
}
catch (Exception ex)
catch (Exception)
{
_unitOfWork.UserRepository.Delete(user);
await _unitOfWork.CommitAsync();
@ -533,6 +534,56 @@ namespace API.Controllers
};
}
[AllowAnonymous]
[HttpPost("confirm-password-reset")]
public async Task<ActionResult<string>> ConfirmForgotPassword(ConfirmPasswordResetDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email);
if (user == null)
{
return BadRequest("Invalid Details");
}
var result = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, "ResetPassword", dto.Token);
if (!result) return BadRequest("Unable to reset password");
var errors = await _accountService.ChangeUserPassword(user, dto.Password);
return errors.Any() ? BadRequest(errors) : BadRequest("Unable to reset password");
}
/// <summary>
/// Will send user a link to update their password to their email or prompt them if not accessible
/// </summary>
/// <param name="email"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost("forgot-password")]
public async Task<ActionResult<string>> ForgotPassword([FromQuery] string email)
{
var user = await _unitOfWork.UserRepository.GetUserByEmailAsync(email);
if (user == null)
{
_logger.LogError("There are no users with email: {Email} but user is requesting password reset", email);
return Ok("An email will be sent to the email if it exists in our database");
}
var emailLink = GenerateEmailLink(await _userManager.GeneratePasswordResetTokenAsync(user), "confirm-reset-password", user.Email);
_logger.LogInformation("[Forgot Password]: Email Link: {Link}", emailLink);
var host = _environment.IsDevelopment() ? "localhost:4200" : Request.Host.ToString();
if (await _emailService.CheckIfAccessible(host))
{
await _emailService.SendPasswordResetEmail(new PasswordResetEmailDto()
{
EmailAddress = user.Email,
ServerConfirmationLink = emailLink
});
return Ok("Email sent");
}
return Ok("Your server is not accessible. The Link to reset your password is in the logs.");
}
[AllowAnonymous]
[HttpPost("confirm-migration-email")]
public async Task<ActionResult<UserDto>> ConfirmMigrationEmail(ConfirmMigrationEmailDto dto)
@ -570,10 +621,7 @@ namespace API.Controllers
"This user needs to migrate. Have them log out and login to trigger a migration flow");
if (user.EmailConfirmed) return BadRequest("User already confirmed");
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var host = _environment.IsDevelopment() ? "localhost:4200" : Request.Host.ToString();
var emailLink =
$"{Request.Scheme}://{host}{Request.PathBase}/registration/confirm-migration-email?token={HttpUtility.UrlEncode(token)}&email={HttpUtility.UrlEncode(user.Email)}";
var emailLink = GenerateEmailLink(await _userManager.GenerateEmailConfirmationTokenAsync(user), "confirm-migration-email", user.Email);
_logger.LogInformation("[Email Migration]: Email Link: {Link}", emailLink);
await _emailService.SendMigrationEmail(new EmailMigrationDto()
{
@ -586,6 +634,14 @@ namespace API.Controllers
return Ok(emailLink);
}
private string GenerateEmailLink(string token, string routePart, string email)
{
var host = _environment.IsDevelopment() ? "localhost:4200" : Request.Host.ToString();
var emailLink =
$"{Request.Scheme}://{host}{Request.PathBase}/registration/{routePart}?token={HttpUtility.UrlEncode(token)}&email={HttpUtility.UrlEncode(email)}";
return emailLink;
}
/// <summary>
/// This is similar to invite. Essentially we authenticate the user's password then go through invite email flow
/// </summary>
@ -622,9 +678,7 @@ namespace API.Controllers
_unitOfWork.UserRepository.Update(user);
await _unitOfWork.CommitAsync();
var host = _environment.IsDevelopment() ? "localhost:4200" : Request.Host.ToString();
var emailLink =
$"{Request.Scheme}://{host}{Request.PathBase}/registration/confirm-migration-email?token={HttpUtility.UrlEncode(token)}&email={HttpUtility.UrlEncode(dto.Email)}";
var emailLink = GenerateEmailLink(await _userManager.GenerateEmailConfirmationTokenAsync(user), "confirm-migration-email", user.Email);
_logger.LogInformation("[Email Migration]: Email Link: {Link}", emailLink);
if (dto.SendEmail)
{