Skip to content

Implement forgot password and reset password workflow with email integration#117

Open
ARYAN-MISHRA-2006 wants to merge 3 commits into
rdodiya:gssoc_developfrom
ARYAN-MISHRA-2006:forgot-password-clean
Open

Implement forgot password and reset password workflow with email integration#117
ARYAN-MISHRA-2006 wants to merge 3 commits into
rdodiya:gssoc_developfrom
ARYAN-MISHRA-2006:forgot-password-clean

Conversation

@ARYAN-MISHRA-2006
Copy link
Copy Markdown

Description

Implemented complete forgot-password and reset-password workflow with secure email-based recovery.

Features Added

  • Forgot password API endpoint
  • Reset password API endpoint
  • UUID-based reset token generation
  • Reset token expiry validation
  • BCrypt password encryption
  • PostgreSQL persistence for reset tokens
  • Spring Mail SMTP integration
  • Email-based password reset flow
  • Reset token cleanup after successful password update
  • Secure password update handling

Verification & Testing

  • Tested APIs using Swagger UI
  • Verified database updates in PostgreSQL
  • Verified reset token generation and validation
  • Successfully tested real email delivery using Gmail SMTP
  • Verified successful password reset flow end-to-end

Closes #40

@ARYAN-MISHRA-2006 ARYAN-MISHRA-2006 marked this pull request as draft May 15, 2026 17:51
@rdodiya
Copy link
Copy Markdown
Owner

rdodiya commented May 16, 2026

Hi @ARYAN-MISHRA-2006 ,
Please perform above changes and resolve conflicts

@rdodiya rdodiya marked this pull request as ready for review May 18, 2026 08:19
Copilot AI review requested due to automatic review settings May 18, 2026 08:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 User and repository lookup by token.
  • Introduced email sending service and wired forgot/reset password methods into AuthServiceImpl.
  • Exposed new /forgot-password and /reset-password endpoints 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.println in 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 RuntimeException here will be handled by the generic exception handler and likely return a 500, even though this is an expected client error. Prefer throwing BusinessException/ResourceNotFoundException with 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 a NullPointerException if resetTokenExpiry is 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}}
Comment on lines +16 to +19
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(System.getenv("MAIL_USERNAME"));
message.setTo(toEmail);
message.setSubject("Password Reset Request");
Comment on lines +133 to +144

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);
Comment on lines +153 to +180
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");
}
Comment on lines +166 to +176
@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");
}
@rdodiya
Copy link
Copy Markdown
Owner

rdodiya commented May 18, 2026

Hi @ARYAN-MISHRA-2006 ,
Please do below changes,
• Add resetPassExpiryDate column instead of resetTokenExpiry in User entity/table.
• Add configurable property reset.expiry.threshold.days (default: 90).
• During registration, resetPassExpiryDate is set to registrationDate + thresholdDays.
• On successful login, password expiry is validated using resetPassExpiryDate.
• Add isResetRequire flag in AuthResponse.
• UI can use isResetRequire=true to show reset password popup/component.
• Updated /reset-password API:

• accepts new password + token (optional as we will re-use this api in forgot pass which validate token and 2nd time based pass expiry which does not require token )
• prevents reuse of current password
• validate token if present in app
• updates password in DB
• refreshes resetPassExpiryDate to now + thresholdDays.

• Token Validation Flow:

• While sending forgot-password mail:

• generate 5–6 digit token
• store in thread-safe cache/map using email as key
• store: 
	• token
	• expiryTime

• During /reset-password:

• fetch token data using email from cache
• if not found → invalid/expired token
• if current time > expiryTime: 
	• remove from cache
	• return Token expired, please regenerate token
• if token mismatch: 
	• return Invalid token
• if valid: 
	• reset password
	• remove token from cache

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Forgot Password Functionality Not Sending Password Reset Link to Registered Email

3 participants