Files
erp-imprimelibros/src/main/java/com/imprimelibros/erp/auth/PasswordResetService.java
2025-10-04 16:51:22 +02:00

125 lines
4.5 KiB
Java

package com.imprimelibros.erp.auth;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.Locale;
import java.util.Map;
import org.springframework.context.MessageSource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring6.SpringTemplateEngine;
import com.imprimelibros.erp.common.email.EmailService;
import com.imprimelibros.erp.users.User;
import com.imprimelibros.erp.users.UserDao; // ajusta al path real de tu UserRepository
@Service
public class PasswordResetService {
private final PasswordResetTokenRepository tokenRepo;
private final UserDao userRepo;
private final PasswordEncoder passwordEncoder;
private final JavaMailSender mailSender;
private final SpringTemplateEngine templateEngine;
private final MessageSource messages;
private final EmailService emailService;
public PasswordResetService(
PasswordResetTokenRepository tokenRepo,
UserDao userRepo,
PasswordEncoder passwordEncoder,
JavaMailSender mailSender,
SpringTemplateEngine templateEngine,
MessageSource messages,
EmailService emailService
) {
this.tokenRepo = tokenRepo;
this.userRepo = userRepo;
this.passwordEncoder = passwordEncoder;
this.mailSender = mailSender;
this.templateEngine = templateEngine;
this.messages = messages;
this.emailService = emailService;
}
// 3.1 Solicitar reset (si el email existe, genera token y envía)
@Transactional
public void requestReset(String email, String baseUrl, String ip, String userAgent, int minutes, Locale locale) {
User user = userRepo.findByUserNameIgnoreCase(email).orElse(null);
// Siempre responder OK aunque no exista para evitar enumeración
if (user == null) return;
tokenRepo.invalidateActiveTokens(user.getId(), LocalDateTime.now());
String token = generateToken(); // token en claro SOLO para el enlace
String tokenHash = sha256(token); // guardamos hash en DB
PasswordResetToken row = new PasswordResetToken();
row.setUserId(user.getId());
row.setCreatedAt(LocalDateTime.now());
row.setExpiresAt(LocalDateTime.now().plusMinutes(minutes));
row.setRequestIp(ip);
row.setUserAgent(userAgent);
row.setTokenHash(tokenHash);
tokenRepo.save(row);
String resetUrl = baseUrl + "/auth/password/reset?uid=" + user.getId() + "&token=" + token;
emailService.sendPasswordResetMail(user.getUserName(), user.getFullName(), resetUrl, locale);
}
// 3.2 Validar token (para mostrar el formulario de nueva contraseña)
public boolean isValid(Long userId, String token) {
String hash = sha256(token);
return tokenRepo.findValidByUserAndHash(userId, hash, LocalDateTime.now()).isPresent();
}
// 3.3 Confirmar reseteo y marcar token como usado
@Transactional
public boolean resetPassword(Long userId, String token, String newPassword) {
String hash = sha256(token);
var opt = tokenRepo.findValidByUserAndHash(userId, hash, LocalDateTime.now());
if (opt.isEmpty()) return false;
var entry = opt.get();
var user = userRepo.findById(userId).orElse(null);
if (user == null) return false;
user.setPassword(passwordEncoder.encode(newPassword));
userRepo.save(user);
entry.setUsedAt(LocalDateTime.now());
tokenRepo.save(entry);
// (Opcional) invalidar otros tokens activos del usuario
tokenRepo.invalidateActiveTokens(userId, LocalDateTime.now());
return true;
}
// --- helpers ---
private String generateToken() {
byte[] buf = new byte[32];
new SecureRandom().nextBytes(buf);
return Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
}
private String sha256(String s) {
try {
var md = MessageDigest.getInstance("SHA-256");
return Base64.getEncoder().encodeToString(md.digest(s.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}