Notificación intentos fallidos

This commit is contained in:
2025-05-27 16:02:13 -06:00
parent 303bf955c3
commit aa793813e8
2 changed files with 655 additions and 54 deletions

View File

@@ -145,9 +145,7 @@ function validar()
header('Location: /IMPORTADORES/login');
}
/**
* Función auxiliar para redirección por rol
*/
/** Función auxiliar para redirección por rol **/
function redirectByRole($tipoUsuario)
{
switch ($tipoUsuario) {
@@ -498,7 +496,6 @@ function verificarCodigoVista()
function verificarCodigo()
{
// ❌ REMOVER session_start() - ya se maneja en session.php
header('Content-Type: application/json');
$conn = getConnection();
@@ -507,8 +504,24 @@ function verificarCodigo()
$codigo = isset($_POST['codigo']) ? trim($_POST['codigo']) : '';
$email = $_SESSION['email_recuperacion'] ?? '';
if (!$conn || empty($codigo) || empty($email)) {
echo json_encode(['success' => false, 'message' => '❌ Código o correo faltante.']);
if (!$conn) {
echo json_encode(['success' => false, 'message' => '❌ Error de conexión a la base de datos.']);
exit;
}
if (empty($codigo)) {
echo json_encode(['success' => false, 'message' => '❌ Código requerido.']);
exit;
}
if (empty($email)) {
echo json_encode(['success' => false, 'message' => '❌ Sesión expirada. Solicita un nuevo código.']);
exit;
}
// Validar formato del código
if (!preg_match('/^\d{6}$/', $codigo)) {
echo json_encode(['success' => false, 'message' => '❌ Formato de código inválido. Debe ser de 6 dígitos.']);
exit;
}
@@ -521,11 +534,14 @@ function verificarCodigo()
$stmt = sqlsrv_query($conn, $sql, [$emailEncrypted]);
if ($stmt === false) {
$errors = sqlsrv_errors();
error_log("Error SQL en verificarCodigo: " . print_r($errors, true));
echo json_encode(['success' => false, 'message' => '❌ Error al consultar el código.']);
exit;
}
$row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
sqlsrv_free_stmt($stmt);
if (!$row) {
echo json_encode(['success' => false, 'message' => '❌ Código no encontrado o ya usado.']);
@@ -539,18 +555,43 @@ function verificarCodigo()
$_SESSION['intentos_codigo']++;
if ($_SESSION['intentos_codigo'] > 5) {
// Bloquea el código en la base y resetea intentos
sqlsrv_query($conn, "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?", [$row['id']]);
// Verificar si se han excedido los intentos
if ($_SESSION['intentos_codigo'] >= 5) {
// Bloquear el código en la base de datos
$sqlUpdate = "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?";
$stmtUpdate = sqlsrv_query($conn, $sqlUpdate, [$row['id']]);
if ($stmtUpdate) {
sqlsrv_free_stmt($stmtUpdate);
}
// 🚨 ENVIAR NOTIFICACIÓN DE SEGURIDAD
try {
enviarNotificacionIntentosExcedidos($email);
} catch (Exception $e) {
error_log("❌ Error al enviar notificación de intentos excedidos: " . $e->getMessage());
}
// Limpiar sesión
unset($_SESSION['intentos_codigo']);
echo json_encode(['success' => false, 'message' => '🚫 Has excedido el número de intentos. Solicita un nuevo código.']);
unset($_SESSION['email_recuperacion']);
echo json_encode([
'success' => false,
'message' => '🚫 Has excedido el número de intentos permitidos. Por seguridad, se ha bloqueado el código. Solicita uno nuevo.',
'blocked' => true
]);
exit;
}
// Compara códigos - asegurando que no haya problemas con mayúsculas o espacios
if (strcasecmp($codigo, $row['codigo']) !== 0) {
echo json_encode(['success' => false, 'message' => "❌ Código incorrecto. Intento {$_SESSION['intentos_codigo']} de 5."]);
$intentosRestantes = 5 - $_SESSION['intentos_codigo'];
echo json_encode([
'success' => false,
'message' => "❌ Código incorrecto. Te quedan {$intentosRestantes} intentos.",
'intentos_restantes' => $intentosRestantes
]);
exit;
}
@@ -566,21 +607,218 @@ function verificarCodigo()
if ($expiracionTimestamp === false || $expiracionTimestamp < time()) {
// Código expirado: marcar como usado y enviar mensaje
sqlsrv_query($conn, "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?", [$row['id']]);
echo json_encode(['success' => false, 'message' => '⏰ El código ha expirado.']);
$sqlExpired = "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?";
$stmtExpired = sqlsrv_query($conn, $sqlExpired, [$row['id']]);
if ($stmtExpired) {
sqlsrv_free_stmt($stmtExpired);
}
echo json_encode(['success' => false, 'message' => '⏰ El código ha expirado. Solicita uno nuevo.']);
exit;
}
// Código correcto: actualizar estado y permitir cambio de contraseña
sqlsrv_query($conn, "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?", [$row['id']]);
$sqlSuccess = "UPDATE recuperacion_password SET estatus = 1 WHERE id = ?";
$stmtSuccess = sqlsrv_query($conn, $sqlSuccess, [$row['id']]);
if ($stmtSuccess) {
sqlsrv_free_stmt($stmtSuccess);
}
$_SESSION['recuperacion_autorizada'] = true;
unset($_SESSION['intentos_codigo']);
echo json_encode(['success' => true, 'message' => '✅ Código verificado. Redirigiendo...']);
echo json_encode(['success' => true, 'message' => '✅ Código verificado correctamente. Redirigiendo...']);
exit;
}
/** Envía notificación cuando se exceden los intentos de verificación **/
function enviarNotificacionIntentosExcedidos($email)
{
$conn = getConnection();
if (!$conn) {
throw new Exception("No se pudo conectar a la base de datos");
}
// Buscar información del usuario por email
$emailEncrypted = encrypt($email);
$sqlNotif = "
SELECT
u.nombre,
u.email,
u.notificaciones,
u.notificaciones_extra,
COALESCE(p.intentos_fallidos, 0) as intentos_fallidos,
ce.correo as correo_extra
FROM usuarios_sistema u
LEFT JOIN preferencias_notificaciones_usuario p ON u.id_usuario = p.id_usuario
LEFT JOIN correo_extra ce ON u.id_usuario = ce.id_usuario
WHERE u.email = ?";
$stmtUsuario = sqlsrv_query($conn, $sqlNotif, [$emailEncrypted]);
if ($stmtUsuario === false) {
throw new Exception("Error al consultar información del usuario");
}
$usuario = sqlsrv_fetch_array($stmtUsuario, SQLSRV_FETCH_ASSOC);
sqlsrv_free_stmt($stmtUsuario);
if (!$usuario) {
error_log("⚠️ No se encontró usuario para email: $email");
return false;
}
// 🔐 Desencriptar datos sensibles
$usuario['email'] = decrypt($usuario['email']);
$usuario['nombre'] = decrypt($usuario['nombre']);
// Verificar si el usuario tiene habilitadas las notificaciones de seguridad
if ($usuario['notificaciones'] != 1 || $usuario['intentos_fallidos'] != 1) {
error_log(" Usuario {$usuario['email']} no tiene habilitadas las notificaciones de intentos fallidos");
return false;
}
// Preparar datos para la notificación
$datosNotificacion = [
'email' => $usuario['email'],
'nombre' => $usuario['nombre'],
'fecha_hora' => date('Y-m-d H:i:s'),
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'Desconocida',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Desconocido'
];
$resultadoEnvio = false;
// Enviar al correo principal
$resultadoPrincipal = enviarNotificacionSeguridadIntentos(
$usuario['email'],
$usuario['nombre'],
$datosNotificacion
);
if ($resultadoPrincipal) {
$resultadoEnvio = true;
error_log("✅ Notificación de seguridad enviada a: {$usuario['email']}");
}
// Enviar al correo adicional si está configurado
if ($usuario['notificaciones_extra'] == 1 && !empty($usuario['correo_extra'])) {
$resultadoExtra = enviarNotificacionSeguridadIntentos(
$usuario['correo_extra'],
$usuario['nombre'],
$datosNotificacion,
true // Indicar que es correo adicional
);
if ($resultadoExtra) {
$resultadoEnvio = true;
error_log("✅ Notificación de seguridad enviada a correo adicional: {$usuario['correo_extra']}");
}
}
// Registrar el evento de seguridad en logs del sistema
registrarEventoSeguridad($usuario['id_usuario'], 'INTENTOS_CODIGO_EXCEDIDOS', $datosNotificacion);
return $resultadoEnvio;
}
/** Función para enviar el email de notificación de seguridad **/
function enviarNotificacionSeguridadIntentos($emailDestino, $nombreUsuario, $datos, $esCorreoAdicional = false)
{
try {
$mail = new PHPMailer(true);
// Configuración de servidor SMTP
$mail->isSMTP();
$mail->Host = 'secure.emailsrvr.com';
$mail->SMTPAuth = true;
$mail->Username = 'noreply@aduanasoft.com.mx';
$mail->Password = $_ENV['SMTP_PASS'];
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
// Configuración del correo
$mail->setFrom('noreply@aduanasoft.com.mx', 'SIIH | AduanaSoft');
$mail->addAddress($emailDestino);
$mail->CharSet = 'UTF-8';
$mail->isHTML(true);
$asunto = "🚨 Alerta de Seguridad - Intentos de Verificación Excedidos";
$mail->Subject = $asunto;
$tipoCorreo = $esCorreoAdicional ? "(Correo Adicional)" : "";
$mail->Body = "
<html>
<head>
<meta charset='UTF-8'>
<title>Alerta de Seguridad</title>
</head>
<body style='font-family: Arial, sans-serif; line-height: 1.6; color: #333;'>
<div style='max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #ddd; border-radius: 10px;'>
<div style='text-align: center; margin-bottom: 30px;'>
<h1 style='color: #dc3545; margin: 0;'>🚨 Alerta de Seguridad</h1>
<p style='color: #666; margin: 5px 0;'>$tipoCorreo</p>
</div>
<div style='background-color: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 20px;'>
<h2 style='color: #dc3545; margin-top: 0;'>Intentos de Verificación Excedidos</h2>
<p>Hola <strong>" . htmlspecialchars($nombreUsuario) . "</strong>,</p>
<p>Se han detectado <strong>múltiples intentos fallidos</strong> para verificar el código de recuperación de contraseña en tu cuenta.</p>
</div>
<div style='background-color: #fff3cd; padding: 15px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #ffc107;'>
<h3 style='color: #856404; margin-top: 0;'>📋 Detalles del Evento:</h3>
<ul style='margin: 10px 0; padding-left: 20px;'>
<li><strong>Fecha y Hora:</strong> " . htmlspecialchars($datos['fecha_hora']) . "</li>
<li><strong>Dirección IP:</strong> " . htmlspecialchars($datos['ip']) . "</li>
<li><strong>Navegador:</strong> " . htmlspecialchars(substr($datos['user_agent'], 0, 100)) . "...</li>
</ul>
</div>
<div style='background-color: #d1ecf1; padding: 15px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #bee5eb;'>
<h3 style='color: #0c5460; margin-top: 0;'>🔒 Medidas de Seguridad Aplicadas:</h3>
<ul style='margin: 10px 0; padding-left: 20px;'>
<li>El código de verificación ha sido <strong>bloqueado automáticamente</strong></li>
<li>Será necesario solicitar un <strong>nuevo código de recuperación</strong></li>
<li>Este evento ha sido <strong>registrado en nuestros logs de seguridad</strong></li>
</ul>
</div>
<div style='background-color: #f8d7da; padding: 15px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #f5c6cb;'>
<h3 style='color: #721c24; margin-top: 0;'>⚠️ ¿No fuiste tú?</h3>
<p>Si <strong>no intentaste recuperar tu contraseña</strong>, esto podría indicar que alguien está tratando de acceder a tu cuenta.</p>
<p><strong>Te recomendamos:</strong></p>
<ul style='margin: 10px 0; padding-left: 20px;'>
<li>Verificar la seguridad de tu cuenta</li>
<li>Cambiar tu contraseña inmediatamente</li>
<li>Revisar tus configuraciones de seguridad</li>
<li>Contactar a soporte si sospechas actividad no autorizada</li>
</ul>
</div>
<div style='text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee;'>
<p style='color: #666; font-size: 12px; margin: 0;'>
Esta es una notificación automática de seguridad.<br>
Si tienes preguntas, contacta a nuestro equipo de soporte.
</p>
</div>
</div>
</body>
</html>";
// Enviar el correo
$mail->send();
return true;
} catch (Exception $e) {
error_log("❌ Error al enviar notificación de seguridad: " . $e->getMessage());
return false;
}
}
function cambiarPasswordVista()
{
if (!isset($_SESSION['recuperacion_autorizada'])) {