RecoveryService.java
package com.edtech.service;
import com.edtech.model.RecoveryToken;
import com.edtech.model.User;
import com.edtech.repository.RecoveryTokenRepository;
import com.edtech.repository.UserRepository;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.Locale;
import java.util.Optional;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/** Documentação para RecoveryService. */
@Service
public class RecoveryService {
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
private final UserRepository userRepository;
private final RecoveryTokenRepository recoveryTokenRepository;
private final EmailService emailService;
private final PasswordEncoder passwordEncoder;
/** Documentação. */
public RecoveryService(
UserRepository userRepository,
RecoveryTokenRepository recoveryTokenRepository,
EmailService emailService,
PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.recoveryTokenRepository = recoveryTokenRepository;
this.emailService = emailService;
this.passwordEncoder = passwordEncoder;
}
/** Documentação. */
@Transactional
public void requestRecovery(String email) {
String normalizedEmail = email.trim().toLowerCase(Locale.ROOT);
Optional<User> userOpt = userRepository.findByEmailIgnoreCase(normalizedEmail);
// Demo accounts cannot recover password
if (normalizedEmail.endsWith(".demo@unb.br")) {
return;
}
// Sempre retorna sucesso rapidamente por seguranca (evitar email enumeration), mas soh envia se
// existir
if (userOpt.isPresent()) {
recoveryTokenRepository.deleteByEmail(normalizedEmail); // Limpa tokens antigos
String code = String.format("%06d", SECURE_RANDOM.nextInt(999999));
RecoveryToken token =
new RecoveryToken(code, normalizedEmail, LocalDateTime.now().plusMinutes(15));
recoveryTokenRepository.save(token);
emailService.sendRecoveryEmail(normalizedEmail, code);
}
}
/** Documentação. */
@Transactional(readOnly = true)
public boolean verifyCode(String email, String code) {
String normalizedEmail = email.trim().toLowerCase(Locale.ROOT);
Optional<RecoveryToken> tokenOpt =
recoveryTokenRepository.findByEmailAndToken(normalizedEmail, code);
if (tokenOpt.isPresent() && !tokenOpt.get().isExpired()) {
return true;
}
return false;
}
/** Documentação. */
@Transactional
public boolean resetPassword(String email, String code, String newPassword) {
String normalizedEmail = email.trim().toLowerCase(Locale.ROOT);
// Demo accounts cannot reset password
if (normalizedEmail.endsWith(".demo@unb.br")) {
return false;
}
Optional<RecoveryToken> tokenOpt =
recoveryTokenRepository.findByEmailAndToken(normalizedEmail, code);
if (tokenOpt.isPresent() && !tokenOpt.get().isExpired()) {
Optional<User> userOpt = userRepository.findByEmailIgnoreCase(normalizedEmail);
if (userOpt.isPresent()) {
User user = userOpt.get();
user.setPasswordHash(passwordEncoder.encode(newPassword));
userRepository.save(user);
recoveryTokenRepository.deleteByEmail(normalizedEmail);
return true;
}
}
return false;
}
}