Implement forgot password and reset password workflow with email integration#117
Implement forgot password and reset password workflow with email integration#117ARYAN-MISHRA-2006 wants to merge 3 commits into
Conversation
41e7683 to
8b2528c
Compare
|
Hi @ARYAN-MISHRA-2006 , |
There was a problem hiding this comment.
Pull request overview
Implements a forgot-password / reset-password flow in the Spring Boot backend by generating a reset token, persisting it on the user record, and sending a reset email via Spring Mail to allow password updates.
Changes:
- Added reset token + expiry fields to
Userand repository lookup by token. - Introduced email sending service and wired forgot/reset password methods into
AuthServiceImpl. - Exposed new
/forgot-passwordand/reset-passwordendpoints and added Spring Mail dependency.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| RestroHub/src/main/resources/application-dev.properties | Updates dev datasource password placeholder (currently incorrect). |
| RestroHub/src/main/java/com/restroly/qrmenu/user/repository/UserRepository.java | Adds findByResetToken query method. |
| RestroHub/src/main/java/com/restroly/qrmenu/user/entity/User.java | Adds resetToken and resetTokenExpiry fields persisted to DB. |
| RestroHub/src/main/java/com/restroly/qrmenu/config/SecurityConfig.java | Minor whitespace change only. |
| RestroHub/src/main/java/com/restroly/qrmenu/auth/service/EmailService.java | New service to send password reset emails. |
| RestroHub/src/main/java/com/restroly/qrmenu/auth/service/AuthServiceImpl.java | Implements forgot/reset password logic with token generation, persistence, and email sending. |
| RestroHub/src/main/java/com/restroly/qrmenu/auth/service/AuthService.java | Adds new forgot/reset password service contract methods. |
| RestroHub/src/main/java/com/restroly/qrmenu/auth/controller/AuthController.java | Adds forgot/reset password endpoints. |
| RestroHub/build.gradle | Adds spring-boot-starter-mail dependency. |
| RestroHub/.gitignore | Ignores logs/ under the backend module. |
Comments suppressed due to low confidence (5)
RestroHub/src/main/java/com/restroly/qrmenu/auth/service/EmailService.java:25
- The reset link is hard-coded to
https://example.com/reset-password?token=..., which will send users to a non-functional URL in real environments. Make the base URL configurable (e.g., via an application property) and build the link from that value so it points to the actual frontend/reset page.
message.setText(
"Click the link below to reset your password:\n\n"
+ "https://example.com/reset-password?token="
+ token
);
RestroHub/src/main/java/com/restroly/qrmenu/auth/service/EmailService.java:30
- Avoid
System.out.printlnin a Spring service; it won’t respect the application logging configuration and can leak operational details. Use the project logger (@Slf4j/log.info/log.warn) instead.
mailSender.send(message);
System.out.println("Reset email sent successfully");
}
RestroHub/src/main/java/com/restroly/qrmenu/auth/service/AuthServiceImpl.java:136
- Using
RuntimeExceptionhere will be handled by the generic exception handler and likely return a 500, even though this is an expected client error. Prefer throwingBusinessException/ResourceNotFoundExceptionwith an appropriate status so clients get a correct 4xx response.
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new RuntimeException("User not found"));
RestroHub/src/main/java/com/restroly/qrmenu/auth/service/AuthServiceImpl.java:145
- The reset token is persisted before attempting to send the email. If
mailSender.send(...)fails, the request will error but the token remains active in the DB, causing inconsistent state/retries. Consider wrapping the operation in a transaction that rolls back on email send failure, or explicitly clearing the token/expiry when sending fails.
user.setResetToken(token);
user.setResetTokenExpiry(LocalDateTime.now().plusMinutes(30));
userRepository.save(user);
emailService.sendResetEmail(user.getEmail(), token);
}
RestroHub/src/main/java/com/restroly/qrmenu/auth/service/AuthServiceImpl.java:154
user.getResetTokenExpiry().isBefore(...)can throw aNullPointerExceptionifresetTokenExpiryis unexpectedly null (e.g., existing data, partial writes, manual DB edits). Add a null check and treat missing expiry as invalid/expired to avoid 500s.
User user = userRepository.findByResetToken(token)
.orElseThrow(() -> new RuntimeException("Invalid reset token"));
if (user.getResetTokenExpiry().isBefore(LocalDateTime.now())) {
throw new RuntimeException("Reset token expired");
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/RestroHub_DB | ||
| spring.datasource.username=${DB_USERNAME:postgres} | ||
| spring.datasource.password=${DB_PASSWORD:postgres} | ||
| spring.datasource.password=${DB_PASSWORD:${DB_PASSWORD}} |
| SimpleMailMessage message = new SimpleMailMessage(); | ||
| message.setFrom(System.getenv("MAIL_USERNAME")); | ||
| message.setTo(toEmail); | ||
| message.setSubject("Password Reset Request"); |
|
|
||
| User user = userRepository.findByEmail(email) | ||
| .orElseThrow(() -> new RuntimeException("User not found")); | ||
|
|
||
| String token = UUID.randomUUID().toString(); | ||
|
|
||
| user.setResetToken(token); | ||
| user.setResetTokenExpiry(LocalDateTime.now().plusMinutes(30)); | ||
|
|
||
| userRepository.save(user); | ||
|
|
||
| emailService.sendResetEmail(user.getEmail(), token); |
| public ResponseEntity<String> forgotPassword(@RequestParam String email) { | ||
| if(email == null || email.trim().isEmpty()) { | ||
| return ResponseEntity.badRequest().body("Email cannot be empty"); | ||
| } | ||
|
|
||
| if(!email.matches("^[A-Za-z0-9+_.-]+@(.+)$")) { | ||
| return ResponseEntity.badRequest().body("Invalid email format"); | ||
| } | ||
| authService.forgotPassword(email); | ||
|
|
||
| return ResponseEntity.ok("Reset token generated successfully"); | ||
| } | ||
|
|
||
| @PostMapping("/reset-password") | ||
| public ResponseEntity<String> resetPassword( | ||
| @RequestParam String token, | ||
| @RequestParam String newPassword) { | ||
| if(newPassword == null || newPassword.trim().isEmpty()) { | ||
| return ResponseEntity.badRequest().body("Password cannot be empty"); | ||
| } | ||
|
|
||
| if(newPassword.length() < 6) { | ||
| return ResponseEntity.badRequest().body("Password must be at least 6 characters"); | ||
| } | ||
| authService.resetPassword(token, newPassword); | ||
|
|
||
| return ResponseEntity.ok("Password reset successful"); | ||
| } |
| @PostMapping("/reset-password") | ||
| public ResponseEntity<String> resetPassword( | ||
| @RequestParam String token, | ||
| @RequestParam String newPassword) { | ||
| if(newPassword == null || newPassword.trim().isEmpty()) { | ||
| return ResponseEntity.badRequest().body("Password cannot be empty"); | ||
| } | ||
|
|
||
| if(newPassword.length() < 6) { | ||
| return ResponseEntity.badRequest().body("Password must be at least 6 characters"); | ||
| } |
|
Hi @ARYAN-MISHRA-2006 ,
• Token Validation Flow:
|
Description
Implemented complete forgot-password and reset-password workflow with secure email-based recovery.
Features Added
Verification & Testing
Closes #40