diff --git a/src/main/java/com/empress/usermanagementapi/controller/ForgotPasswordController.java b/src/main/java/com/empress/usermanagementapi/controller/ForgotPasswordController.java index 5e078ca..9a52a56 100644 --- a/src/main/java/com/empress/usermanagementapi/controller/ForgotPasswordController.java +++ b/src/main/java/com/empress/usermanagementapi/controller/ForgotPasswordController.java @@ -1,13 +1,10 @@ package com.empress.usermanagementapi.controller; -import com.empress.usermanagementapi.entity.PasswordResetToken; import com.empress.usermanagementapi.entity.User; import com.empress.usermanagementapi.service.UserService; -import com.empress.usermanagementapi.service.EmailService; import com.empress.usermanagementapi.service.PasswordResetService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; @@ -20,17 +17,11 @@ public class ForgotPasswordController { private static final Logger log = LoggerFactory.getLogger(ForgotPasswordController.class); private final UserService userService; - private final EmailService emailService; private final PasswordResetService passwordResetService; - @Value("${app.base-url}") - private String baseUrl; - public ForgotPasswordController(UserService userService, - EmailService emailService, PasswordResetService passwordResetService) { this.userService = userService; - this.emailService = emailService; this.passwordResetService = passwordResetService; } @@ -64,15 +55,8 @@ public String handleForm(@RequestParam String username, return "forgot-password"; } - // User exists, create token + send email - PasswordResetToken tokenEntity = - passwordResetService.createPasswordResetTokenForEmail(trimmedEmail); - String token = tokenEntity.getToken(); - - String resetLink = baseUrl + "/reset-password?token=" + token; - try { - emailService.sendPasswordResetEmail(trimmedEmail, resetLink); + passwordResetService.createTokenAndSendResetEmail(trimmedEmail); } catch (Exception e) { log.error("Failed to send password reset email to: {}", trimmedEmail, e); model.addAttribute("error", "Failed to send password reset email. Please try again later."); diff --git a/src/main/java/com/empress/usermanagementapi/controller/RegistrationController.java b/src/main/java/com/empress/usermanagementapi/controller/RegistrationController.java index 6b757e0..7c6ccb8 100644 --- a/src/main/java/com/empress/usermanagementapi/controller/RegistrationController.java +++ b/src/main/java/com/empress/usermanagementapi/controller/RegistrationController.java @@ -3,14 +3,12 @@ import com.empress.usermanagementapi.entity.Role; import com.empress.usermanagementapi.entity.User; import com.empress.usermanagementapi.model.RegistrationRequest; -import com.empress.usermanagementapi.service.EmailService; import com.empress.usermanagementapi.service.EmailVerificationService; import com.empress.usermanagementapi.service.UserService; import com.empress.usermanagementapi.util.LoggingUtil; import jakarta.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; @@ -37,17 +35,11 @@ public class RegistrationController { private final UserService userService; private final EmailVerificationService emailVerificationService; - private final EmailService emailService; - - @Value("${app.base-url}") - private String baseUrl; public RegistrationController(UserService userService, - EmailVerificationService emailVerificationService, - EmailService emailService) { + EmailVerificationService emailVerificationService) { this.userService = userService; this.emailVerificationService = emailVerificationService; - this.emailService = emailService; } /** @@ -123,22 +115,7 @@ public String registerSubmit( created.getId(), created.getUsername()); - // create verification token - String token = emailVerificationService.createTokenForUser(created); - log.debug("Email verification token created - userId: {}", created.getId()); - - String verifyLink = baseUrl + "/verify-email?token=" + token; - - try { - emailService.sendVerificationEmail(created.getEmail(), verifyLink); - log.info("Verification email sent - userId: {}", created.getId()); - } catch (Exception e) { - // for now just log; account is created even if email fails - log.error("Failed to send verification email - userId: {}, error: {}", - created.getId(), - e.getMessage()); - System.err.println("Failed to send verification email: " + e.getMessage()); - } + emailVerificationService.createTokenAndSendVerificationEmail(created); } else { log.warn("User creation returned null or invalid user"); } diff --git a/src/main/java/com/empress/usermanagementapi/service/EmailVerificationService.java b/src/main/java/com/empress/usermanagementapi/service/EmailVerificationService.java index ef8cb33..662e4dc 100644 --- a/src/main/java/com/empress/usermanagementapi/service/EmailVerificationService.java +++ b/src/main/java/com/empress/usermanagementapi/service/EmailVerificationService.java @@ -7,6 +7,7 @@ import com.empress.usermanagementapi.util.LoggingUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @@ -20,11 +21,17 @@ public class EmailVerificationService { private final EmailVerificationTokenRepository tokenRepo; private final UserRepository userRepo; + private final EmailService emailService; + + @Value("${app.base-url}") + private String baseUrl; public EmailVerificationService(EmailVerificationTokenRepository tokenRepo, - UserRepository userRepo) { + UserRepository userRepo, + EmailService emailService) { this.tokenRepo = tokenRepo; this.userRepo = userRepo; + this.emailService = emailService; } // create or refresh a token for this user and return the token string @@ -65,6 +72,29 @@ public String createTokenForUser(User user) { return newTokenValue; } + /** + * Creates a verification token and sends the verification email. + * + * The account is already created at this point, so email delivery failures are logged + * without rolling back registration. This preserves the existing registration behavior + * while keeping email orchestration inside the service layer. + */ + public void createTokenAndSendVerificationEmail(User user) { + String token = createTokenForUser(user); + log.debug("Email verification token created - userId: {}", user.getId()); + + String verifyLink = baseUrl + "/verify-email?token=" + token; + + try { + emailService.sendVerificationEmail(user.getEmail(), verifyLink); + log.info("Verification email sent - userId: {}", user.getId()); + } catch (Exception e) { + log.error("Failed to send verification email - userId: {}, error: {}", + user.getId(), + e.getMessage()); + } + } + /** * @return null on success, or an error message on failure */ diff --git a/src/main/java/com/empress/usermanagementapi/service/PasswordResetService.java b/src/main/java/com/empress/usermanagementapi/service/PasswordResetService.java index e58beb8..e3234f3 100644 --- a/src/main/java/com/empress/usermanagementapi/service/PasswordResetService.java +++ b/src/main/java/com/empress/usermanagementapi/service/PasswordResetService.java @@ -7,6 +7,7 @@ import com.empress.usermanagementapi.util.LoggingUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -22,13 +23,19 @@ public class PasswordResetService { private final PasswordResetTokenRepository tokenRepo; private final UserRepository userRepo; private final PasswordEncoder passwordEncoder; + private final EmailService emailService; + + @Value("${app.base-url}") + private String baseUrl; public PasswordResetService(PasswordResetTokenRepository tokenRepo, UserRepository userRepo, - PasswordEncoder passwordEncoder) { + PasswordEncoder passwordEncoder, + EmailService emailService) { this.tokenRepo = tokenRepo; this.userRepo = userRepo; this.passwordEncoder = passwordEncoder; + this.emailService = emailService; } public PasswordResetToken createPasswordResetTokenForEmail(String email) { @@ -70,6 +77,19 @@ public PasswordResetToken createPasswordResetTokenForEmail(String email) { return saved; } + /** + * Creates a password reset token and sends the reset email. + * + * Callers only need to validate who is allowed to request a reset; this service owns + * the token creation and email-delivery workflow. Email delivery failures are allowed + * to propagate so the controller can show the user that the reset email was not sent. + */ + public void createTokenAndSendResetEmail(String email) { + PasswordResetToken tokenEntity = createPasswordResetTokenForEmail(email); + String resetLink = baseUrl + "/reset-password?token=" + tokenEntity.getToken(); + emailService.sendPasswordResetEmail(email, resetLink); + } + public String validatePasswordResetToken(String token) { String cleanToken = token == null ? null : token.trim();