Files
NightSpire/home/views/_base.py
T
Inna 8920585b24 Implementa la recuperación de cuenta (contraseña / cuentas / activación) + islas
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>
2026-07-12 21:00:42 +00:00

70 lines
2.8 KiB
Python

"""Imports y utilidades compartidas por el paquete de vistas (`home.views`).
Cada submódulo de vistas hace `from ._base import *` para heredar estos imports
y el decorador `require_paid_stripe`.
"""
import socket, hashlib, gmpy2, binascii, os, re, json, logging, secrets
from collections import Counter
from datetime import datetime, timedelta
from decimal import Decimal, ROUND_HALF_UP
from functools import wraps
from django import forms
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import logout
from django.db import connections
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.urls import reverse
from django.utils import timezone
from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.views.decorators.csrf import csrf_exempt
import stripe
from ..ac_soap import execute_soap_command
from ..library_correo import enviar_correo
from ..pagos_stripe import create_checkout_session, is_session_paid
from ..pagos_sumup import crear_checkout, crear_checkout_battlepay, obtener_checkout
from ..zone_definitions import get_zone_name
from .. import bnet
from ..models import (
Noticia, ClienteCategoria, ServerSelection, RecruitAFriend, DownloadClientPage,
ContentCreator, RecruitReward, ClaimedReward, AccountActivation, SecurityToken,
GuildRenameSettings, VoteSite, VoteLog, HomeApiPoints, UnstuckHistory, ReviveHistory,
RenamePrice, CustomizePrice, ChangeRacePrice, ChangeFactionPrice, LevelUpPrice,
GoldPrice, TransferPrice, Category, Item, StripeLog, LoginAttempt, Pedido,
PasswordReset,
)
# Configuración del logger
logger = logging.getLogger(__name__)
def require_paid_stripe(redirect_to='index'):
"""
Protege las vistas de "éxito" de pago: exige un `session_id` de Stripe
realmente PAGADO en la URL (Stripe lo añade tras el pago) y evita re-entregas
marcando el StripeLog como `fulfilled`. Cierra el bypass de ir directo a la
success-url sin pagar.
"""
def deco(view):
@wraps(view)
def wrapper(request, *args, **kwargs):
session_id = request.GET.get('session_id')
if not is_session_paid(session_id):
messages.error(request, 'No se ha podido verificar el pago.')
return redirect(redirect_to)
log = StripeLog.objects.filter(session_id=session_id).first()
if log is not None and log.fulfilled:
messages.info(request, 'Este pago ya había sido procesado.')
return redirect(redirect_to)
response = view(request, *args, **kwargs)
if log is not None:
log.fulfilled = True
log.save(update_fields=['fulfilled'])
return response
return wrapper
return deco