8920585b24
recover_account_view era un stub que solo renderizaba (los formularios no tenían backend y el HTML arrastraba un reCAPTCHA de otro dominio). Ahora es funcional: Backend: - Modelo PasswordReset (token+email+expiración 1h) + migración 0014. - recover_account_view maneja POST JSON con 3 tipos, todo por email (bnet), con respuestas genéricas anti-enumeración: * password: crea token y envía enlace de reset (emails/password_reset.html). * accountname: envía las cuentas de juego ligadas (emails/account_names.html). * activation: reenvía el enlace de activación pendiente (emails/activation.html). - reset_password_view: GET valida el token (isla), POST re-deriva el verifier SRP6 v2 de la cuenta bnet (bnet.bnet_make_registration) y lo actualiza; marca el token usado. Ruta 'reset-password'. - Resiliente si la BD de cuentas (AzerothCore) no está disponible: mensaje limpio, nunca 500. Frontend (islas Vite): RecoverForm.tsx (selector de tipo + email) y ResetPassword.tsx (nueva contraseña con token). Se elimina el reCAPTCHA roto; se puede añadir uno propio si se aportan claves. Verificado en producción: los 3 flujos devuelven éxito genérico; reset valida token y contraseñas; token válido no provoca 500. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
63 lines
2.7 KiB
Python
63 lines
2.7 KiB
Python
from django.db import models
|
|
from django.contrib.auth.models import User
|
|
from django.utils import timezone
|
|
|
|
|
|
class AccountActivation(models.Model):
|
|
# En 3.4.3 (Battle.net) la identidad es el email; el username de la cuenta
|
|
# de juego se genera al activar (<bnetId>#1), por eso es opcional aquí.
|
|
username = models.CharField(max_length=17, null=True, blank=True)
|
|
email = models.EmailField()
|
|
old_email = models.EmailField(null=True, blank=True)
|
|
password = models.CharField(max_length=64)
|
|
# salt/verifier ya no se precalculan en el registro (se calculan al activar,
|
|
# tanto el de bnet como el de la cuenta de juego). Se conservan opcionales
|
|
# por compatibilidad con el flujo de cambio de email.
|
|
salt = models.BinaryField(max_length=32, null=True, blank=True)
|
|
verifier = models.BinaryField(max_length=256, null=True, blank=True)
|
|
recruiter_id = models.IntegerField(null=True, blank=True)
|
|
hash = models.CharField(max_length=32, unique=True)
|
|
old_email_hash = models.CharField(max_length=32, null=True, blank=True)
|
|
is_used = models.BooleanField(default=False) # Indica si el old_email_hash ha sido usado
|
|
is_new_email_used = models.BooleanField(default=False) # Indica si el hash del nuevo correo ha sido usado
|
|
created_at = models.DateTimeField(default=timezone.now)
|
|
|
|
def is_expired(self):
|
|
return timezone.now() > self.created_at + timezone.timedelta(hours=1)
|
|
|
|
def __str__(self):
|
|
return f"Activation for {self.username}"
|
|
|
|
class PasswordReset(models.Model):
|
|
"""Token de restablecimiento de contraseña (cuenta Battle.net, por email)."""
|
|
email = models.EmailField()
|
|
token = models.CharField(max_length=64, unique=True)
|
|
created_at = models.DateTimeField(default=timezone.now)
|
|
used = models.BooleanField(default=False)
|
|
|
|
def is_expired(self):
|
|
return timezone.now() > self.created_at + timezone.timedelta(hours=1)
|
|
|
|
def __str__(self):
|
|
return f"PasswordReset for {self.email}"
|
|
|
|
|
|
class SecurityToken(models.Model):
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
|
|
token = models.CharField(max_length=6)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
expires_at = models.DateTimeField()
|
|
ip_address = models.GenericIPAddressField()
|
|
|
|
def __str__(self):
|
|
return f"Token for {self.user.username if self.user else 'Desconocido'}"
|
|
|
|
class LoginAttempt(models.Model):
|
|
username = models.CharField(max_length=100)
|
|
status = models.CharField(max_length=50) # Ejemplo: 'Éxito' o 'Fracaso'
|
|
ip_address = models.GenericIPAddressField() # IP del intento de inicio de sesión
|
|
timestamp = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return f"{self.username} - {self.status} - {self.timestamp}"
|