i'm trying to implement a password reset/forgot password feature in my spring boot/react app but i'm having some problems trying to fully understand it, I'm unsure whether the approach i'm taking is correct or not.
I already have both register/login working
- I created a REST endpoint: /sendForgotPasswordEmail [POST]
- This finds a user via their email address they enter in the react form and sees if the account with the email exists.
- If a user account is found, sends them an email with a unique token with a link to a reset password form (stuck here)
- I also created a Token entity that generates a random UUID so when the user clicks forgot password and fills in the details, the email generates this link: http://localhost:3000/reset-password/e90c4fa5-2e74-4b44-b3c4-5d6f1c616461
@PostMapping("/sendForgotPasswordEmail")
public ResponseEntity<?> sendForgotPasswordEmail(@Valid @RequestBody ForgotPasswordEmailRequest request) {
User user = userService.findUserByEmailAddress(request.getForgotPasswordEmail());
ResetPasswordToken resetPasswordToken = userService.createResetPasswordToken(user);
if(userService.existsByEmailAddress(request.getForgotPasswordEmail())) {
emailService.sendResetPasswordEmail(request.getForgotPasswordEmail(), resetPasswordToken.getToken());
return ResponseEntity.ok(new ApiResponse("Please check your email for a password reset link" , true));
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
}
- I created a REST endpoint: /resetPassword [POST]
- Reads the token generated using @RequestParam to check whether it expired etc.
- I have a service method called resetPassword(String email, String password) which searches for a user by their email and sets the user password to the provided password from the method.
@PostMapping("/resetPassword")
public ResponseEntity<?> resetPassword(@RequestParam("token") String token, @Valid @RequestBody ForgotPasswordEmailRequest request) {
ResetPasswordToken resetPasswordToken = userService.findByResetPasswordToken(token);
if(resetPasswordToken == null) throw new BadRequestException("Invalid token");
Calendar calendar = Calendar.getInstance();
if((resetPasswordToken.getExpiredAt().getTime() - calendar.getTime().getTime()) <= 0) {
throw new BadRequestException("Link expired. Generate new link from http://localhost:3000/signup");
}
userService.resetPassword(request.getForgotPasswordEmail(), request.getNewPassword());
return ResponseEntity.ok(token);
}
UserServiceImpl.java (Methods related to the reset token)
@Override
public ResetPasswordToken createResetPasswordToken(User user) {
ResetPasswordToken resetPasswordToken = new ResetPasswordToken(user);
return resetPasswordTokenRepository.save(resetPasswordToken);
}
@Override
public ResetPasswordToken findByResetPasswordToken(String token) {
return resetPasswordTokenRepository.findByToken(token);
}
@Override
public void resetPassword(String emailAddress, String password) {
User user = findUserByEmailAddress(emailAddress);
user.setPassword(passwordEncoder.encode(password));
}
ForgotPasswordEmailRequest.java
public class ForgotPasswordEmailRequest {
private String forgotPasswordEmail;
private String newPassword;
// constructors / getters and setters
}
The thing i'd like clarity on is how exactly to go about doing this and making sure i can successfully redirect a user to the reset password page, and they can change it from there. I'm unsure where my controller methods are missing something or where i'm going wrong.
When the user reaches the reset password form after being sent an email, what's the process that needs to happen on the front + backend from there.
I'd appreciate any help.