From 7bdc88e8392391d427dffac83dfb9b0350b42d53 Mon Sep 17 00:00:00 2001 From: Gradyn Wursten Date: Wed, 1 Nov 2023 15:06:07 -0600 Subject: [PATCH] finish password reset API --- JournalyApiV2/Controllers/AuthController.cs | 20 +++++++++++++ .../Models/Requests/ResetPasswordRequest.cs | 7 +++++ JournalyApiV2/Services/BLL/AuthService.cs | 28 +++++++++++++------ JournalyApiV2/Services/BLL/IAuthService.cs | 1 + JournalyApiV2/Services/DAL/AuthDbService.cs | 16 +++++++++++ JournalyApiV2/Services/DAL/IAuthDbService.cs | 10 ++++--- 6 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 JournalyApiV2/Models/Requests/ResetPasswordRequest.cs diff --git a/JournalyApiV2/Controllers/AuthController.cs b/JournalyApiV2/Controllers/AuthController.cs index c53f1c5..66a608d 100644 --- a/JournalyApiV2/Controllers/AuthController.cs +++ b/JournalyApiV2/Controllers/AuthController.cs @@ -207,6 +207,26 @@ public async Task ResetPassword([FromBody] string email) { // Ignore argumentException - this means the email was not found but we don't want the user to know that } + catch (TooEarlyException ex) + { + throw new HttpBadRequestException(ex.Message); + } + return StatusCode(204); + } + + [Route("submit-password-reset")] + [HttpPost] + [AllowAnonymous] + public async Task SubmitPasswordReset([FromBody] ResetPasswordRequest request) + { + try + { + await _authService.SubmitPasswordResetAsync(request.Code, request.NewPassword); + } + catch (ArgumentException ex) + { + throw new HttpBadRequestException(ex.Message); + } return StatusCode(204); } } \ No newline at end of file diff --git a/JournalyApiV2/Models/Requests/ResetPasswordRequest.cs b/JournalyApiV2/Models/Requests/ResetPasswordRequest.cs new file mode 100644 index 0000000..327e51a --- /dev/null +++ b/JournalyApiV2/Models/Requests/ResetPasswordRequest.cs @@ -0,0 +1,7 @@ +namespace JournalyApiV2.Models.Requests; + +public class ResetPasswordRequest +{ + public string Code { get; set; } + public string NewPassword { get; set; } +} \ No newline at end of file diff --git a/JournalyApiV2/Services/BLL/AuthService.cs b/JournalyApiV2/Services/BLL/AuthService.cs index d0aba36..edfd80c 100644 --- a/JournalyApiV2/Services/BLL/AuthService.cs +++ b/JournalyApiV2/Services/BLL/AuthService.cs @@ -245,15 +245,27 @@ public async Task ResetPasswordAsync(string email) } else { - try - { - await _authDbService.ResetPasswordResetTimerAsync(Guid.Parse(user.Id)); - } - catch (TooEarlyException ex) - { - throw new HttpBadRequestException(ex.Message); - } + await _authDbService.ResetPasswordResetTimerAsync(Guid.Parse(user.Id)); } await _emailService.SendPasswordResetEmailAsync(user.Email, user.FirstName, user.LastName, code); } + + public async Task SubmitPasswordResetAsync(string code, string password) + { + var userGuid = await _authDbService.LookupPasswordResetAsync(code); + if (userGuid == null) throw new ArgumentException("Invalid password reset code"); + var user = await _userManager.FindByIdAsync(userGuid.Value.ToString()); + if (user == null) throw new Exception("User not found"); + await _userManager.RemovePasswordAsync(user); + var result = await _userManager.AddPasswordAsync(user, password); + if (result.Succeeded) + { + await _authDbService.ResetPassword(userGuid.Value); + } + else + { + throw new Exception(string.Join(", ", result.Errors.Select(x => x.Description).ToArray())); + } + } + } \ No newline at end of file diff --git a/JournalyApiV2/Services/BLL/IAuthService.cs b/JournalyApiV2/Services/BLL/IAuthService.cs index dce0386..859dc08 100644 --- a/JournalyApiV2/Services/BLL/IAuthService.cs +++ b/JournalyApiV2/Services/BLL/IAuthService.cs @@ -16,4 +16,5 @@ public interface IAuthService Task VerifyEmailWithShortCode(Guid userId, string shortCode); Task ResendVerificationEmailAsync(Guid userId); Task ResetPasswordAsync(string email); + Task SubmitPasswordResetAsync(string code, string password); } \ No newline at end of file diff --git a/JournalyApiV2/Services/DAL/AuthDbService.cs b/JournalyApiV2/Services/DAL/AuthDbService.cs index fafe868..657acbe 100644 --- a/JournalyApiV2/Services/DAL/AuthDbService.cs +++ b/JournalyApiV2/Services/DAL/AuthDbService.cs @@ -202,4 +202,20 @@ public async Task ResetPasswordResetTimerAsync(Guid userId) code.LastSent = DateTime.UtcNow; await db.SaveChangesAsync(); } + + public async Task LookupPasswordResetAsync(string code) + { + await using var db = _db.Journaly(); + var codeObj = await db.PasswordResetCodes.SingleOrDefaultAsync(x => x.Code == code); + + return codeObj?.User; + } + + public async Task ResetPassword(Guid userId) + { + await using var db = _db.Journaly(); + var toRemove = db.PasswordResetCodes.Where(x => x.User == userId); + db.RemoveRange(toRemove); + await db.SaveChangesAsync(); + } } \ No newline at end of file diff --git a/JournalyApiV2/Services/DAL/IAuthDbService.cs b/JournalyApiV2/Services/DAL/IAuthDbService.cs index fa8d488..016a6d5 100644 --- a/JournalyApiV2/Services/DAL/IAuthDbService.cs +++ b/JournalyApiV2/Services/DAL/IAuthDbService.cs @@ -4,17 +4,19 @@ namespace JournalyApiV2.Services.DAL; public interface IAuthDbService { - Task ExchangeRefreshTokenAsync(string token); + Task ExchangeRefreshTokenAsync(string token); + Task NewRefreshTokenAsync(Guid user); Task LookupRefreshTokenAsync(string token); - Task NewRefreshTokenAsync(Guid user); Task VoidRefreshTokensAsync(params int[] tokenIds); - Task GetRefreshTokensAsync(Guid userId); + Task GetRefreshTokensAsync(Guid userId); Task GetOrCreateEmailVerificationCode(Guid userId); + Task ResetEmailVerificationTimerAsync(Guid userId); Task GetUserByLongCode(string longCode); Task VerifyUser(Guid user); Task CheckShortCode(Guid userId, string shortCode); - Task ResetEmailVerificationTimerAsync(Guid userId); Task GetPasswordResetCode(Guid userId); Task GeneratePasswordResetCode(Guid userId); Task ResetPasswordResetTimerAsync(Guid userId); + Task LookupPasswordResetAsync(string code); + Task ResetPassword(Guid userId); } \ No newline at end of file