Adapta el portal a 3.4.3 (cuentas Battle.net + selector de cuenta de juego)
- home/bnet.py: SRP6 v2 (PBKDF2-HMAC-SHA512) para battlenet_accounts y SRP6 Grunt (SHA1) para las cuentas de juego, con helpers bnet<->game account - login por email contra battlenet_accounts; selector de cuenta de juego cuando la cuenta bnet tiene varias (my_account redirige al selector) - registro/activación crean battlenet_accounts + account enlazada (<bnetId>#1, battlenet_account/battlenet_index) - cambio de contraseña sobre battlenet_accounts; cambio de email recalcula salt/verifier (el usuario SRP depende del email) - AccountActivation: identidad por email (username/salt/verifier opcionales) + migración - plantillas login/register por email + nueva plantilla del selector - docs/MIGRACION_3.4.3.md con el detalle y los pasos de verificación Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# Migración del portal 3.3.5a → 3.4.3 (cuentas Battle.net)
|
||||
|
||||
Este documento describe los cambios hechos para adaptar el portal (antes
|
||||
AzerothCore 3.3.5a) a un servidor **TrinityCore 3.4.3** con **cuentas
|
||||
Battle.net** (login por email + cuentas de juego + selector).
|
||||
|
||||
## Resumen del cambio de modelo
|
||||
|
||||
| Concepto | 3.3.5a (antes) | 3.4.3 (ahora) |
|
||||
|---|---|---|
|
||||
| Login | `username` en `account` | **email** en `battlenet_accounts` |
|
||||
| Contraseña | SRP6 Grunt (SHA1) | SRP6 **v2** (PBKDF2-HMAC-SHA512) para bnet |
|
||||
| Cuenta de juego | la propia `account` | 1..N filas en `account` enlazadas por `battlenet_account` + `battlenet_index` |
|
||||
| Nombre de cuenta de juego | elegido por el usuario | generado: `<bnetId>#<index>` (p.ej. `5#1`) |
|
||||
| Selector | — | sí, si la cuenta bnet tiene varias cuentas de juego |
|
||||
|
||||
Un registro crea **dos filas**: una en `battlenet_accounts` y otra en `account`.
|
||||
|
||||
## Algoritmos SRP6 (`home/bnet.py`)
|
||||
|
||||
- **`battlenet_accounts` (SRP6 v2)** — replica `BnetSRP6v2<SHA256>` del source:
|
||||
- `N` de 2048 bits, `g = 2`, salt de 32 bytes.
|
||||
- usuario SRP = `HEX(SHA256(UPPER(email)))`.
|
||||
- `x = PBKDF2-HMAC-SHA512(usuario + ":" + password, salt, 15000, 64)`, con
|
||||
ajuste del bit alto y `x %= (N-1)`.
|
||||
- `verifier = g^x mod N`, almacenado en little-endian de longitud mínima.
|
||||
- `srp_version = 2`, `email` en MAYÚSCULAS.
|
||||
- **`account` (SRP6 Grunt/SHA1)** — el clásico de 3.3.5a, reutilizado para la
|
||||
cuenta de juego. Username `<bnetId>#<index>` y password (16, mayúsculas).
|
||||
|
||||
> ⚠️ **Verificación pendiente (requiere el servidor real):** el orden de bytes
|
||||
> del `verifier` de bnet se ha fijado a little-endian según el source. El login
|
||||
> del PORTAL funciona siempre (registra y verifica igual). Para confirmar que el
|
||||
> CLIENTE 3.4.3 puede conectar con una cuenta creada por el portal, crea una
|
||||
> cuenta desde la web y prueba a entrar en el juego. Si fallara, cambia
|
||||
> `_BNET_VERIFIER_LITTLE_ENDIAN = False` en `home/bnet.py`. Alternativa: compara
|
||||
> los bytes de `salt`/`verifier` de una cuenta creada con `.bnetaccount create`
|
||||
> frente a una creada por el portal con el mismo email+password.
|
||||
|
||||
## Cambios en el código
|
||||
|
||||
- `home/bnet.py` (NUEVO): SRP6 v2 + Grunt y helpers bnet↔cuentas de juego.
|
||||
- `home/views.py`:
|
||||
- `login_view`: login por **email** contra `battlenet_accounts`; resuelve las
|
||||
cuentas de juego (autoselecciona si hay una, deja pendiente si hay varias).
|
||||
- `select_account_view` (NUEVO) + `auth/select_account.html`: selector.
|
||||
- `my_account`: si hay login bnet sin cuenta de juego elegida → selector.
|
||||
- `register_view` / `activate_account_view`: crean bnet + cuenta de juego.
|
||||
- `change_password_view`: cambia la contraseña en `battlenet_accounts`.
|
||||
- `change_email_view` / `confirm_new_email_view`: cambian el email en
|
||||
`battlenet_accounts` **recalculando** salt/verifier (dependen del email).
|
||||
- `logout_view`: limpia `username`, `account_id`, `bnet_id`, `bnet_email`.
|
||||
- `home/models.py`: `AccountActivation` con `username`/`salt`/`verifier`
|
||||
opcionales (la identidad es el email). Migración `0012_*`.
|
||||
- Plantillas: `partials/login.html` (campo email) y `partials/register.html`
|
||||
(registro por email, sin username).
|
||||
|
||||
### Sesión
|
||||
|
||||
Tras elegir cuenta de juego se guardan en sesión, **igual que antes**:
|
||||
`request.session['username']` (= `<bnetId>#<index>`) y `account_id`. Por eso las
|
||||
~60 vistas de tienda/personajes/puntos siguen funcionando sin cambios. Además se
|
||||
guardan `bnet_id` y `bnet_email` para cambios de contraseña/email.
|
||||
|
||||
## Requisitos de esquema (BD `auth`)
|
||||
|
||||
- `battlenet_accounts`: `id, email, srp_version, salt, verifier, ...`
|
||||
(columnas base de TrinityCore). Custom del source: `battletag`,
|
||||
`trial_expiry`, `tournament_expiry`, `game_time_expiry`, `battlePayCredits`,
|
||||
`voteCredits`.
|
||||
- `account`: debe tener `battlenet_account` y `battlenet_index` (además de
|
||||
`username, salt, verifier, email, reg_mail, expansion, recruiter, ...`).
|
||||
|
||||
## Pasos para desplegar
|
||||
|
||||
1. Aplicar la migración del portal:
|
||||
```bash
|
||||
python manage.py migrate home
|
||||
```
|
||||
2. Apuntar `DB_NAME_AUTH` al `auth` del servidor 3.4.3.
|
||||
3. Registrar una cuenta desde la web y comprobar el email de activación.
|
||||
4. Activar y **probar login en el cliente 3.4.3** (ver nota de verificación).
|
||||
|
||||
## Pendiente / opcional
|
||||
|
||||
- **Battlepay/SumUp**: el source trae `battlePayCredits` (en `battlenet_accounts`)
|
||||
y las tablas `battlepay_orders` / `battlepay_price` (`sql/custom/battlepay_sumup.sql`).
|
||||
Ojo: `battlepay_orders.account_id` es el **id de cuenta de juego**
|
||||
(`account.id`), mientras que `battlePayCredits` está en la **cuenta bnet**
|
||||
(`battlenet_accounts.id`); se unen por `account.battlenet_account`. La
|
||||
integración de la pasarela con estas tablas se puede abordar como paso
|
||||
siguiente.
|
||||
- Mostrar el email (en lugar de `<bnetId>#1`) como nombre visible en
|
||||
«Mi cuenta» es un retoque cosmético opcional.
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
"""
|
||||
Utilidades de autenticación para servidores 3.4.3 (TrinityCore) con cuentas
|
||||
Battle.net.
|
||||
|
||||
Dos algoritmos SRP6 distintos conviven:
|
||||
|
||||
* ``battlenet_accounts`` -> SRP6 **v2**: PBKDF2-HMAC-SHA512, N de 2048 bits,
|
||||
g = 2. El "usuario" SRP es HEX(SHA256(UPPER(email))). Es el que usa el
|
||||
cliente 3.4.3 para conectarse (login por email).
|
||||
|
||||
* ``account`` (cuenta de juego) -> SRP6 **Grunt/SHA1**: el clásico de
|
||||
AzerothCore/3.3.5a. Cada cuenta bnet tiene 1..N cuentas de juego con
|
||||
username ``<bnetId>#<index>``.
|
||||
|
||||
Referencias del propio source 3.4.3:
|
||||
src/common/Cryptography/Authentication/SRP6.cpp (CalculateX / CalculateVerifier)
|
||||
src/server/game/Accounts/BattlenetAccountMgr.cpp (CreateBattlenetAccount)
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SRP6 v2 (battlenet_accounts) — PBKDF2-HMAC-SHA512
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# BnetSRP6v2Base::N (2048 bits) y g, tal cual en SRP6.cpp
|
||||
_BNET_N = int(
|
||||
"AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050"
|
||||
"A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50"
|
||||
"E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B8"
|
||||
"55F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773B"
|
||||
"CA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748"
|
||||
"544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6"
|
||||
"AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB6"
|
||||
"94B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73",
|
||||
16,
|
||||
)
|
||||
_BNET_g = 2
|
||||
_BNET_SALT_LEN = 32
|
||||
_BNET_ITERATIONS = 15000 # GetXIterations() para v2
|
||||
_BNET_SRP_VERSION = 2
|
||||
_TWO_POW_512 = 1 << 512
|
||||
|
||||
# TrinityCore almacena el verifier con BigNumber::ToByteVector(), que por
|
||||
# defecto es little-endian. Si al comparar contra una cuenta creada por el
|
||||
# propio servidor (`.bnetaccount create`) el login del CLIENTE fallara, prueba a
|
||||
# poner esto en False (big-endian). El login del PORTAL funciona en cualquier
|
||||
# caso porque registra y verifica con el mismo criterio.
|
||||
_BNET_VERIFIER_LITTLE_ENDIAN = True
|
||||
|
||||
|
||||
def bnet_srp_username(email):
|
||||
"""SRP username de bnet = HEX(SHA256(UPPER(email))) en mayúsculas."""
|
||||
email_up = email.strip().upper()
|
||||
return hashlib.sha256(email_up.encode("utf-8")).hexdigest().upper()
|
||||
|
||||
|
||||
def _bnet_calculate_x(email, password, salt):
|
||||
"""Replica BnetSRP6v2Base::CalculateX (SRP6.cpp)."""
|
||||
srp_user = bnet_srp_username(email)
|
||||
tmp = (srp_user + ":" + password).encode("utf-8")
|
||||
x_bytes = hashlib.pbkdf2_hmac("sha512", tmp, salt, _BNET_ITERATIONS, dklen=64)
|
||||
x = int.from_bytes(x_bytes, "big")
|
||||
if x_bytes[0] & 0x80: # si el bit alto está puesto, resta 2^512
|
||||
x -= _TWO_POW_512
|
||||
return x % (_BNET_N - 1)
|
||||
|
||||
|
||||
def _int_to_verifier_bytes(value):
|
||||
"""Emula BigNumber::ToByteVector(): longitud mínima, endianness configurable."""
|
||||
length = max(1, (value.bit_length() + 7) // 8)
|
||||
order = "little" if _BNET_VERIFIER_LITTLE_ENDIAN else "big"
|
||||
return value.to_bytes(length, order)
|
||||
|
||||
|
||||
def _verifier_bytes_to_int(data):
|
||||
order = "little" if _BNET_VERIFIER_LITTLE_ENDIAN else "big"
|
||||
return int.from_bytes(data, order)
|
||||
|
||||
|
||||
def bnet_make_registration(email, password):
|
||||
"""
|
||||
Genera (salt, verifier, srp_version) para una nueva cuenta Battle.net.
|
||||
|
||||
salt: 32 bytes aleatorios (se guarda tal cual).
|
||||
verifier: g^x mod N en longitud mínima (como TrinityCore::ToByteVector).
|
||||
"""
|
||||
salt = os.urandom(_BNET_SALT_LEN)
|
||||
x = _bnet_calculate_x(email, password, salt)
|
||||
v = pow(_BNET_g, x, _BNET_N)
|
||||
return salt, _int_to_verifier_bytes(v), _BNET_SRP_VERSION
|
||||
|
||||
|
||||
def bnet_verify(email, password, salt, stored_verifier):
|
||||
"""Comprueba la contraseña de una cuenta Battle.net (login por email)."""
|
||||
if salt is None or stored_verifier is None:
|
||||
return False
|
||||
if isinstance(salt, memoryview):
|
||||
salt = bytes(salt)
|
||||
if isinstance(stored_verifier, memoryview):
|
||||
stored_verifier = bytes(stored_verifier)
|
||||
x = _bnet_calculate_x(email, password, salt)
|
||||
v = pow(_BNET_g, x, _BNET_N)
|
||||
# Comparación numérica (robusta frente a padding).
|
||||
return v == _verifier_bytes_to_int(stored_verifier)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SRP6 Grunt/SHA1 (cuenta de juego `account`) — el clásico de 3.3.5a
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_GRUNT_g = 7
|
||||
_GRUNT_N = int("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", 16)
|
||||
_GRUNT_SALT_LEN = 32
|
||||
|
||||
|
||||
def _sha1(data):
|
||||
return hashlib.sha1(data).digest()
|
||||
|
||||
|
||||
def game_calculate_verifier(username, password, salt):
|
||||
"""
|
||||
SRP6 Grunt (SHA1) para la tabla `account`. ``username`` y ``password`` se
|
||||
pasan en mayúsculas (TrinityCore hace Utf8ToUpperOnlyLatin en ambos).
|
||||
Devuelve el verifier en bytes little-endian (32).
|
||||
"""
|
||||
if isinstance(salt, memoryview):
|
||||
salt = bytes(salt)
|
||||
h1 = _sha1((username.upper() + ":" + password.upper()).encode("utf-8"))
|
||||
h2 = _sha1(salt + h1)
|
||||
h2_int = int.from_bytes(h2, "little")
|
||||
verifier = pow(_GRUNT_g, h2_int, _GRUNT_N)
|
||||
return verifier.to_bytes(32, "little")
|
||||
|
||||
|
||||
def game_make_registration(username, password):
|
||||
"""Genera (salt, verifier) Grunt para una cuenta de juego nueva."""
|
||||
salt = os.urandom(_GRUNT_SALT_LEN)
|
||||
verifier = game_calculate_verifier(username, password[:16], salt)
|
||||
return salt, verifier
|
||||
|
||||
|
||||
def game_verify(username, password, salt, stored_verifier):
|
||||
"""Comprueba la contraseña de una cuenta de juego (SRP6 Grunt)."""
|
||||
if salt is None or stored_verifier is None:
|
||||
return False
|
||||
if isinstance(stored_verifier, memoryview):
|
||||
stored_verifier = bytes(stored_verifier)
|
||||
calc = game_calculate_verifier(username, password[:16], salt)
|
||||
return int.from_bytes(calc, "little") == int.from_bytes(stored_verifier, "little")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers de acceso a la BD auth (bnet <-> cuentas de juego)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def normalize_email(email):
|
||||
"""El email en battlenet_accounts se almacena en MAYÚSCULAS."""
|
||||
return email.strip().upper()
|
||||
|
||||
|
||||
def get_bnet_account_by_email(cursor, email):
|
||||
"""Devuelve dict {id, email, srp_version, salt, verifier} o None."""
|
||||
cursor.execute(
|
||||
"SELECT id, email, srp_version, salt, verifier "
|
||||
"FROM battlenet_accounts WHERE email = %s",
|
||||
[normalize_email(email)],
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
return None
|
||||
return {
|
||||
"id": row[0],
|
||||
"email": row[1],
|
||||
"srp_version": row[2],
|
||||
"salt": row[3],
|
||||
"verifier": row[4],
|
||||
}
|
||||
|
||||
|
||||
def get_game_accounts_for_bnet(cursor, bnet_id):
|
||||
"""Lista de cuentas de juego de una cuenta bnet: [{id, username, index}]."""
|
||||
cursor.execute(
|
||||
"SELECT id, username, battlenet_index "
|
||||
"FROM account WHERE battlenet_account = %s ORDER BY battlenet_index",
|
||||
[bnet_id],
|
||||
)
|
||||
return [{"id": r[0], "username": r[1], "index": r[2]} for r in cursor.fetchall()]
|
||||
|
||||
|
||||
def get_next_battlenet_index(cursor, bnet_id):
|
||||
"""Siguiente battlenet_index libre para una cuenta bnet (1-based)."""
|
||||
cursor.execute(
|
||||
"SELECT MAX(battlenet_index) FROM account WHERE battlenet_account = %s",
|
||||
[bnet_id],
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
return (row[0] or 0) + 1
|
||||
|
||||
|
||||
def make_game_account_username(bnet_id, index=1):
|
||||
"""Nombre de la cuenta de juego: ``<bnetId>#<index>`` (como TrinityCore)."""
|
||||
return f"{bnet_id}#{index}"
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 6.0.7 on 2026-07-12 16:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0011_pedido_alter_guildrenamesettings_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='accountactivation',
|
||||
name='salt',
|
||||
field=models.BinaryField(blank=True, max_length=32, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='accountactivation',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=17, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='accountactivation',
|
||||
name='verifier',
|
||||
field=models.BinaryField(blank=True, max_length=256, null=True),
|
||||
),
|
||||
]
|
||||
+8
-3
@@ -194,12 +194,17 @@ from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
class AccountActivation(models.Model):
|
||||
username = models.CharField(max_length=17)
|
||||
# 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 = models.BinaryField(max_length=32)
|
||||
verifier = models.BinaryField(max_length=32)
|
||||
# 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)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
|
||||
<div class="main-page">
|
||||
<div class="middle-content">
|
||||
<div class="body-content">
|
||||
<div class="title-content">
|
||||
<h1>Selecciona tu cuenta de juego</h1>
|
||||
</div>
|
||||
<div class="box-content">
|
||||
<div class="body-box-content centered">
|
||||
{% if bnet_email %}
|
||||
<p>Conectado como <strong>{{ bnet_email }}</strong></p>
|
||||
<br>
|
||||
{% endif %}
|
||||
|
||||
{% if game_accounts %}
|
||||
<p>Tu cuenta Battle.net tiene varias cuentas de juego. Elige con cuál quieres continuar:</p>
|
||||
<br>
|
||||
<form action="{% url 'select-account' %}" method="POST" accept-charset="utf-8">
|
||||
{% csrf_token %}
|
||||
<table class="middle-center-table">
|
||||
{% for account in game_accounts %}
|
||||
<tr>
|
||||
<td>
|
||||
<button type="submit" name="account_id" value="{{ account.id }}" class="login-button">
|
||||
{{ account.username }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</form>
|
||||
{% else %}
|
||||
<p>No se han encontrado cuentas de juego asociadas a esta cuenta Battle.net.</p>
|
||||
<br>
|
||||
<p>Contacta con el soporte si crees que es un error.</p>
|
||||
{% endif %}
|
||||
|
||||
<hr>
|
||||
<p><a href="log-out">Cerrar sesión</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -27,7 +27,7 @@
|
||||
{% csrf_token %}
|
||||
<table class="middle-center-table">
|
||||
<tr>
|
||||
<td><input type="text" maxlength="20" name="username" id="username" placeholder="Nombre de usuario" autofocus required></td>
|
||||
<td><input type="email" maxlength="320" name="email" id="username" placeholder="Correo electrónico" autofocus required></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
<div class="title-content"><h1>Creación de cuenta</h1></div>
|
||||
<div class="box-content" id="focus" tabindex="1">
|
||||
<div class="body-box-content info-box-light justified">
|
||||
<p>El usuario debe ser alfanumérico y con una longitud de hasta 17 caracteres.</p>
|
||||
<p>Tu cuenta es de tipo Battle.net: se identifica con tu <strong>correo electrónico</strong>.</p>
|
||||
<p>La contraseña debe ser alfanumérica y con una longitud de hasta 16 caracteres.</p>
|
||||
<p>El correo debe pertenecer a gmail y con acceso al mismo. Un mismo correo puede usarse para registrar hasta 10 cuentas.</p>
|
||||
<p>El correo debe pertenecer a gmail y con acceso al mismo.</p>
|
||||
<br>
|
||||
<p>Una vez que la cuenta se haya creado exitosamente, se enviará un correo con un enlace de activación.</p>
|
||||
<p>De no usar el enlace de activación, la cuenta no podrá usarse para conectar al servidor.</p>
|
||||
<p><i class="fas fa-exclamation-triangle"></i> Al iniciar sesión, ingresa con el NOMBRE de la cuenta.</p>
|
||||
<p><i class="fas fa-exclamation-triangle"></i> Al iniciar sesión, ingresa con tu <strong>correo electrónico</strong>.</p>
|
||||
<br>
|
||||
<p>La cuenta del foro es independiente y se puede crear <a href="https://foro.novawow.com/register" target="_blank" rel="noopener noreferrer">aquí</a>.</p>
|
||||
</div>
|
||||
@@ -37,13 +37,13 @@
|
||||
<div class="title-content"><h1>Creación de cuenta</h1></div>
|
||||
<div class="box-content" id="focus" tabindex="1">
|
||||
<div class="body-box-content info-box-light justified">
|
||||
<p>El usuario debe ser alfanumérico y con una longitud de hasta 17 caracteres.</p>
|
||||
<p>Tu cuenta es de tipo Battle.net: se identifica con tu <strong>correo electrónico</strong>.</p>
|
||||
<p>La contraseña debe ser alfanumérica y con una longitud de hasta 16 caracteres.</p>
|
||||
<p>El correo debe pertenecer a gmail y con acceso al mismo. Un mismo correo puede usarse para registrar hasta 10 cuentas.</p>
|
||||
<p>El correo debe pertenecer a gmail y con acceso al mismo.</p>
|
||||
<br>
|
||||
<p>Una vez que la cuenta se haya creado exitosamente, se enviará un correo con un enlace de activación.</p>
|
||||
<p>De no usar el enlace de activación, la cuenta no podrá usarse para conectar al servidor.</p>
|
||||
<p><i class="fas fa-exclamation-triangle"></i> Al iniciar sesión, ingresa con el NOMBRE de la cuenta.</p>
|
||||
<p><i class="fas fa-exclamation-triangle"></i> Al iniciar sesión, ingresa con tu <strong>correo electrónico</strong>.</p>
|
||||
<br>
|
||||
<p>La cuenta del foro es independiente y se puede crear <a href="https://foro.novawow.com/register" target="_blank" rel="noopener noreferrer">aquí</a>.</p>
|
||||
</div>
|
||||
@@ -58,9 +58,6 @@
|
||||
<table class="middle-center-table">
|
||||
<script src="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-js-handlers/register.js"></script>
|
||||
<tbody><tr>
|
||||
<td><input type="text" maxlength="17" name="username" id="username" placeholder="Nombre de usuario"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="password" maxlength="16" name="password" id="password" placeholder="Contraseña">
|
||||
<span class="far fa-eye toggle-password"></span></td>
|
||||
</tr>
|
||||
|
||||
@@ -8,6 +8,7 @@ urlpatterns = [
|
||||
path('content-creators/', views.content_creators_view, name='content_creators'),
|
||||
path('log-in/', views.login_view, name='login'),
|
||||
path('log-out/', views.logout_view, name='log-out'),
|
||||
path('select-account/', views.select_account_view, name='select-account'),
|
||||
path('create-account/', views.register_view, name='register'),
|
||||
path('novawow-realm/', views.novawow_realm_view, name='novawow_realm'),
|
||||
path('recover/', views.recover_account_view, name='recover'),
|
||||
|
||||
+203
-122
@@ -15,6 +15,7 @@ from django.views.decorators.csrf import csrf_exempt
|
||||
from datetime import datetime, timedelta
|
||||
from .zone_definitions import get_zone_name
|
||||
from .ac_soap import execute_soap_command
|
||||
from . import bnet
|
||||
from django.utils import timezone
|
||||
from .library_correo import enviar_correo
|
||||
from django.utils.crypto import get_random_string
|
||||
@@ -131,9 +132,9 @@ def formatear_fecha(fecha):
|
||||
return f"{dia_semana} {fecha.day:02d} de {mes} del {fecha.year}"
|
||||
|
||||
|
||||
# Clase de formulario de inicio de sesión
|
||||
# Clase de formulario de inicio de sesión (Battle.net: login por email)
|
||||
class LoginForm(forms.Form):
|
||||
username = forms.CharField(max_length=32, required=True)
|
||||
email = forms.EmailField(max_length=320, required=True)
|
||||
password = forms.CharField(widget=forms.PasswordInput, required=True)
|
||||
|
||||
def sha1(data):
|
||||
@@ -164,60 +165,96 @@ def calculate_srp6_verifier(username, password, salt):
|
||||
# Convertir el resultado a hexadecimal en mayúsculas
|
||||
return binascii.hexlify(verifier_bytes).decode('utf-8').upper()
|
||||
|
||||
def authenticate(username, password):
|
||||
def authenticate(email, password):
|
||||
"""Autentica una cuenta Battle.net por email (SRP6 v2)."""
|
||||
try:
|
||||
# Obtener el salt y el verifier de la base de datos
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT salt, verifier FROM account WHERE username = %s", [username])
|
||||
result = cursor.fetchone()
|
||||
|
||||
if not result:
|
||||
return False
|
||||
|
||||
salt, stored_verifier = result
|
||||
|
||||
# Convertir `salt` y `verifier` a formato hexadecimal si son bytes
|
||||
if isinstance(salt, bytes):
|
||||
salt = salt.hex()
|
||||
if isinstance(stored_verifier, bytes):
|
||||
stored_verifier = stored_verifier.hex()
|
||||
|
||||
# Calcular el verifier con el username y password proporcionados
|
||||
calculated_verifier = calculate_srp6_verifier(username, password, salt)
|
||||
|
||||
# Comparar el verifier calculado con el almacenado en la base de datos
|
||||
return calculated_verifier.strip().upper() == stored_verifier.strip().upper()
|
||||
|
||||
account = bnet.get_bnet_account_by_email(cursor, email)
|
||||
if not account:
|
||||
return None
|
||||
if bnet.bnet_verify(account['email'], password, account['salt'], account['verifier']):
|
||||
return account
|
||||
return None
|
||||
except Exception:
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
def _set_game_account_session(request, game_account):
|
||||
"""Fija en sesión la cuenta de juego seleccionada (para las demás vistas)."""
|
||||
request.session['username'] = game_account['username'].upper()
|
||||
request.session['account_id'] = game_account['id']
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def login_view(request):
|
||||
if request.method == 'POST':
|
||||
form = LoginForm(request.POST)
|
||||
if form.is_valid():
|
||||
username = form.cleaned_data['username']
|
||||
email = form.cleaned_data['email']
|
||||
password = form.cleaned_data['password']
|
||||
ip_address = request.META.get('REMOTE_ADDR') # Obtener la IP del cliente
|
||||
ip_address = request.META.get('REMOTE_ADDR')
|
||||
|
||||
# Llamar a la función de autenticación
|
||||
if authenticate(username, password):
|
||||
# Guardar el intento exitoso
|
||||
log_login_attempt(username, "Exito", ip_address)
|
||||
account = authenticate(email, password)
|
||||
if account:
|
||||
log_login_attempt(email, "Exito", ip_address)
|
||||
|
||||
# Guardar la identidad Battle.net en la sesión
|
||||
request.session['bnet_id'] = account['id']
|
||||
request.session['bnet_email'] = account['email']
|
||||
|
||||
# Resolver las cuentas de juego enlazadas
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
game_accounts = bnet.get_game_accounts_for_bnet(cursor, account['id'])
|
||||
|
||||
if len(game_accounts) == 1:
|
||||
# Una sola cuenta de juego: seleccionar automáticamente
|
||||
_set_game_account_session(request, game_accounts[0])
|
||||
elif len(game_accounts) > 1:
|
||||
# Varias: dejar la selección pendiente (my-account redirige al selector)
|
||||
request.session.pop('username', None)
|
||||
request.session.pop('account_id', None)
|
||||
# 0 cuentas: se queda sin username; el selector mostrará el aviso
|
||||
|
||||
# Guardar el nombre de usuario en mayúsculas en la sesión
|
||||
request.session['username'] = username.upper()
|
||||
return JsonResponse({'success': True})
|
||||
else:
|
||||
# Guardar el intento fallido
|
||||
log_login_attempt(username, "Fracaso", ip_address)
|
||||
|
||||
return JsonResponse({'success': False, 'error': "Usuario o contraseña incorrectos"})
|
||||
log_login_attempt(email, "Fracaso", ip_address)
|
||||
return JsonResponse({'success': False, 'error': "Correo o contraseña incorrectos"})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'error': "Formulario no valido"})
|
||||
|
||||
|
||||
form = LoginForm()
|
||||
return render(request, 'auth/login.html', {'form': form})
|
||||
|
||||
|
||||
def select_account_view(request):
|
||||
"""Selector de cuenta de juego para cuentas Battle.net con varias."""
|
||||
bnet_id = request.session.get('bnet_id')
|
||||
if not bnet_id:
|
||||
return redirect('login')
|
||||
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
game_accounts = bnet.get_game_accounts_for_bnet(cursor, bnet_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
selected_id = int(request.POST.get('account_id', ''))
|
||||
except (TypeError, ValueError):
|
||||
selected_id = None
|
||||
chosen = next((g for g in game_accounts if g['id'] == selected_id), None)
|
||||
if chosen:
|
||||
_set_game_account_session(request, chosen)
|
||||
return redirect('my-account')
|
||||
messages.error(request, 'Cuenta de juego no válida.')
|
||||
|
||||
# Autoseleccionar si solo hay una
|
||||
if len(game_accounts) == 1:
|
||||
_set_game_account_session(request, game_accounts[0])
|
||||
return redirect('my-account')
|
||||
|
||||
return render(request, 'auth/select_account.html', {
|
||||
'game_accounts': game_accounts,
|
||||
'bnet_email': request.session.get('bnet_email'),
|
||||
})
|
||||
# Función para contar los personajes en línea
|
||||
def get_online_characters_count():
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
@@ -292,42 +329,46 @@ def server_status_view(request):
|
||||
|
||||
# Vista para cerrar sesión
|
||||
def logout_view(request):
|
||||
if 'username' in request.session:
|
||||
del request.session['username']
|
||||
for key in ('username', 'account_id', 'bnet_id', 'bnet_email'):
|
||||
request.session.pop(key, None)
|
||||
return redirect('index')
|
||||
|
||||
# Vistas adicionales
|
||||
@csrf_exempt
|
||||
def register_view(request):
|
||||
if request.method == 'POST':
|
||||
username = request.POST.get('username').strip()
|
||||
password = request.POST.get('password').strip()
|
||||
conf_password = request.POST.get('conf-password').strip()
|
||||
email = request.POST.get('email').strip()
|
||||
conf_email = request.POST.get('conf-email').strip()
|
||||
recruiter = request.POST.get('recruiter').strip()
|
||||
password = request.POST.get('password', '').strip()
|
||||
conf_password = request.POST.get('conf-password', '').strip()
|
||||
email = request.POST.get('email', '').strip()
|
||||
conf_email = request.POST.get('conf-email', '').strip()
|
||||
recruiter = request.POST.get('recruiter', '').strip()
|
||||
|
||||
# Validar entradas
|
||||
if not username or not password or not email:
|
||||
# En 3.4.3 la identidad es el email (cuenta Battle.net); no hay username.
|
||||
if not password or not email:
|
||||
return JsonResponse({'success': False, 'message': 'Por favor, complete todos los campos.'})
|
||||
|
||||
if len(username) > 17 or not username.isalnum():
|
||||
return JsonResponse({'success': False, 'message': 'Nombre de usuario no válido.'})
|
||||
|
||||
if password != conf_password:
|
||||
return JsonResponse({'success': False, 'message': 'Las contraseñas no coinciden.'})
|
||||
|
||||
if len(password) > 16:
|
||||
return JsonResponse({'success': False, 'message': 'La contraseña no debe exceder los 16 caracteres.'})
|
||||
|
||||
if email != conf_email or not re.match(r'^[a-zA-Z0-9._%+-]+@gmail\.com$', email):
|
||||
return JsonResponse({'success': False, 'message': 'El correo electrónico no es válido.'})
|
||||
|
||||
# Verificar si el usuario ya existe
|
||||
# Verificar si ya existe una cuenta Battle.net con ese email
|
||||
email_norm = bnet.normalize_email(email)
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT COUNT(*) FROM account WHERE username = %s", [username])
|
||||
cursor.execute("SELECT COUNT(*) FROM battlenet_accounts WHERE email = %s", [email_norm])
|
||||
if cursor.fetchone()[0] > 0:
|
||||
return JsonResponse({'success': False, 'message': 'El nombre de usuario ya está en uso.'})
|
||||
return JsonResponse({'success': False, 'message': 'Ya existe una cuenta con ese correo electrónico.'})
|
||||
|
||||
# Validar y obtener recruiter_id
|
||||
recruiter_id = 0 # Valor por defecto si no se proporciona un reclutador
|
||||
# Comprobar que no haya un registro de activación pendiente para ese email
|
||||
if AccountActivation.objects.filter(email=email, old_email__isnull=True).exists():
|
||||
AccountActivation.objects.filter(email=email, old_email__isnull=True).delete()
|
||||
|
||||
# Validar y obtener recruiter_id (id de cuenta de juego del reclutador)
|
||||
recruiter_id = 0
|
||||
if recruiter:
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT id FROM account WHERE username = %s", [recruiter])
|
||||
@@ -343,24 +384,13 @@ def register_view(request):
|
||||
else:
|
||||
return JsonResponse({'success': False, 'message': 'El reclutador ingresado no existe.'})
|
||||
|
||||
# Generar salt y verifier
|
||||
salt = binascii.hexlify(os.urandom(32)).decode('utf-8').upper()
|
||||
verifier = calculate_srp6_verifier(username, password, salt)
|
||||
|
||||
# Convertir `salt` y `verifier` a bytes antes de guardarlos en la base de datos
|
||||
salt_bytes = binascii.unhexlify(salt)
|
||||
verifier_bytes = binascii.unhexlify(verifier)
|
||||
|
||||
# Generar un hash único para activación
|
||||
# Generar un hash único para activación. Los salt/verifier se calculan al
|
||||
# activar (tanto el de bnet como el de la cuenta de juego).
|
||||
activation_hash = get_random_string(32)
|
||||
|
||||
# Guardar la información en la tabla `AccountActivation`
|
||||
AccountActivation.objects.create(
|
||||
username=username,
|
||||
email=email,
|
||||
password=password,
|
||||
salt=salt_bytes,
|
||||
verifier=verifier_bytes,
|
||||
recruiter_id=recruiter_id,
|
||||
hash=activation_hash
|
||||
)
|
||||
@@ -368,57 +398,82 @@ def register_view(request):
|
||||
# Enviar correo de activación
|
||||
activation_link = f"{settings.URL_PRINCIPAL}/es/activate-account?act={activation_hash}"
|
||||
context = {
|
||||
'username': username,
|
||||
'username': email,
|
||||
'password': password,
|
||||
'activation_link': activation_link,
|
||||
'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR
|
||||
}
|
||||
enviar_correo(
|
||||
subject=f'Activación de la cuenta {username} - {settings.NOMBRE_SERVIDOR}',
|
||||
subject=f'Activación de la cuenta {email} - {settings.NOMBRE_SERVIDOR}',
|
||||
to_email=email,
|
||||
template='emails/activation.html',
|
||||
context=context
|
||||
)
|
||||
|
||||
# Preparar el mensaje de respuesta
|
||||
message = f"""
|
||||
<div class="alert-message" id="create-response" style="display: block;">
|
||||
<span class="ok-form-response">La cuenta '{username}' ha sido creada.</span><br><br>
|
||||
<span class="ok-form-response">La cuenta '{email}' ha sido creada.</span><br><br>
|
||||
<span>Se ha enviado un enlace de activación al correo {email}.</span>
|
||||
</div>
|
||||
"""
|
||||
return JsonResponse({'success': True, 'message': message})
|
||||
|
||||
return render(request, 'auth/register.html')
|
||||
|
||||
|
||||
|
||||
|
||||
def activate_account_view(request):
|
||||
activation_hash = request.GET.get('act')
|
||||
|
||||
try:
|
||||
activation = AccountActivation.objects.get(hash=activation_hash)
|
||||
|
||||
|
||||
if activation.is_expired():
|
||||
return render(request, 'auth/activation_invalid.html')
|
||||
|
||||
# Insertar la cuenta en `acore_auth`
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
INSERT INTO account (username, salt, verifier, email, reg_mail, recruiter, joindate, last_ip)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s)
|
||||
""", [
|
||||
activation.username, activation.salt, activation.verifier,
|
||||
activation.email, activation.email, activation.recruiter_id,
|
||||
request.META.get('REMOTE_ADDR')
|
||||
])
|
||||
|
||||
# Eliminar el registro de activación
|
||||
activation.delete()
|
||||
email_norm = bnet.normalize_email(activation.email)
|
||||
password = activation.password
|
||||
last_ip = request.META.get('REMOTE_ADDR')
|
||||
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
# Evitar duplicados si el enlace se usa dos veces
|
||||
cursor.execute("SELECT id FROM battlenet_accounts WHERE email = %s", [email_norm])
|
||||
existing = cursor.fetchone()
|
||||
if existing:
|
||||
activation.delete()
|
||||
return render(request, 'auth/activation_success.html')
|
||||
|
||||
# 1) Crear la cuenta Battle.net (SRP6 v2)
|
||||
bnet_salt, bnet_verifier, srp_version = bnet.bnet_make_registration(email_norm, password)
|
||||
cursor.execute(
|
||||
"INSERT INTO battlenet_accounts (email, srp_version, salt, verifier) "
|
||||
"VALUES (%s, %s, %s, %s)",
|
||||
[email_norm, srp_version, bnet_salt, bnet_verifier]
|
||||
)
|
||||
|
||||
# 2) Recuperar el id de la cuenta bnet recién creada
|
||||
cursor.execute("SELECT id FROM battlenet_accounts WHERE email = %s", [email_norm])
|
||||
bnet_id = cursor.fetchone()[0]
|
||||
|
||||
# 3) Crear la cuenta de juego enlazada (<bnetId>#1, SRP6 Grunt)
|
||||
game_username = bnet.make_game_account_username(bnet_id, 1)
|
||||
game_salt, game_verifier = bnet.game_make_registration(game_username, password)
|
||||
cursor.execute(
|
||||
"INSERT INTO account "
|
||||
"(username, salt, verifier, email, reg_mail, recruiter, joindate, "
|
||||
" last_ip, expansion, battlenet_account, battlenet_index) "
|
||||
"VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, %s, %s, %s)",
|
||||
[
|
||||
game_username, game_salt, game_verifier,
|
||||
email_norm, email_norm, activation.recruiter_id or 0,
|
||||
last_ip, 2, bnet_id, 1
|
||||
]
|
||||
)
|
||||
|
||||
activation.delete()
|
||||
return render(request, 'auth/activation_success.html')
|
||||
|
||||
except AccountActivation.DoesNotExist:
|
||||
return render(request, 'auth/activation_invalid.html')
|
||||
return render(request, 'auth/activation_invalid.html')
|
||||
|
||||
def recover_account_view(request):
|
||||
return render(request, 'auth/recover_account.html')
|
||||
@@ -610,11 +665,14 @@ def get_recruited_friends_at_level_80(account_id, required_count):
|
||||
|
||||
|
||||
def my_account(request):
|
||||
# Verificar si la sesión contiene el usuario
|
||||
# Verificar si la sesión contiene la cuenta de juego seleccionada
|
||||
username = request.session.get('username')
|
||||
|
||||
# Si la sesión no tiene el usuario, redirigir al inicio de sesión
|
||||
|
||||
# Si aún no se ha elegido cuenta de juego pero hay login Battle.net,
|
||||
# enviar al selector; si no, al inicio de sesión.
|
||||
if not username:
|
||||
if request.session.get('bnet_id'):
|
||||
return redirect('select-account')
|
||||
return redirect('login')
|
||||
|
||||
is_logged_in = True # Si el usuario está en la sesión, asumimos que está conectado
|
||||
@@ -868,27 +926,29 @@ def change_password_view(request):
|
||||
if existing_token.token != security_token:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">El token ingresado es incorrecto.</span>'})
|
||||
|
||||
# Verificar la contraseña actual usando la función `authenticate`
|
||||
if not authenticate(username, current_password):
|
||||
# Verificar la contraseña actual contra la cuenta Battle.net (por email)
|
||||
if not authenticate(email, current_password):
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">La contraseña actual es incorrecta.</span>'})
|
||||
|
||||
# Generar salt y verifier para la nueva contraseña
|
||||
new_salt = binascii.hexlify(os.urandom(32)).decode('utf-8').upper()
|
||||
new_verifier = calculate_srp6_verifier(username, new_password, new_salt)
|
||||
# Generar salt y verifier SRP6 v2 para la nueva contraseña (bnet)
|
||||
new_salt_bytes, new_verifier_bytes, srp_version = bnet.bnet_make_registration(email, new_password)
|
||||
|
||||
# Convertir `salt` y `verifier` a bytes antes de guardarlos en la base de datos
|
||||
new_salt_bytes = binascii.unhexlify(new_salt)
|
||||
new_verifier_bytes = binascii.unhexlify(new_verifier)
|
||||
|
||||
# Actualizar la contraseña en la base de datos
|
||||
# Localizar la cuenta bnet (por sesión o por email) y actualizarla
|
||||
bnet_id = request.session.get('bnet_id')
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
UPDATE account SET salt = %s, verifier = %s WHERE username = %s
|
||||
""", [new_salt_bytes, new_verifier_bytes, username])
|
||||
if not bnet_id:
|
||||
account = bnet.get_bnet_account_by_email(cursor, email)
|
||||
bnet_id = account['id'] if account else None
|
||||
if not bnet_id:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">No se ha encontrado la cuenta.</span>'})
|
||||
cursor.execute(
|
||||
"UPDATE battlenet_accounts SET srp_version = %s, salt = %s, verifier = %s WHERE id = %s",
|
||||
[srp_version, new_salt_bytes, new_verifier_bytes, bnet_id]
|
||||
)
|
||||
|
||||
# Cerrar la sesión por seguridad
|
||||
if 'username' in request.session:
|
||||
del request.session['username']
|
||||
for key in ('username', 'account_id', 'bnet_id', 'bnet_email'):
|
||||
request.session.pop(key, None)
|
||||
|
||||
# Redirigir al usuario a la página de inicio de sesión
|
||||
return JsonResponse({
|
||||
@@ -1051,21 +1111,22 @@ def change_email_view(request):
|
||||
if not security_token or security_token.token != token:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Token de seguridad incorrecto.</span>'})
|
||||
|
||||
if not authenticate(username, current_password):
|
||||
# Verificar la contraseña contra la cuenta Battle.net (por email actual)
|
||||
if not authenticate(current_email, current_password):
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Contraseña incorrecta.</span>'})
|
||||
|
||||
# Generar hashes
|
||||
old_email_hash = secrets.token_urlsafe(16)
|
||||
new_email_hash = secrets.token_urlsafe(16)
|
||||
|
||||
# Guardar en `AccountActivation`
|
||||
# Guardar en `AccountActivation`. Se conserva la contraseña porque al
|
||||
# cambiar el email hay que recalcular el verifier de bnet (el usuario SRP
|
||||
# depende del email).
|
||||
AccountActivation.objects.create(
|
||||
username=username,
|
||||
email=new_email,
|
||||
old_email=current_email,
|
||||
password='',
|
||||
salt=b'',
|
||||
verifier=b'',
|
||||
password=current_password,
|
||||
recruiter_id=user_id,
|
||||
hash=new_email_hash,
|
||||
old_email_hash=old_email_hash
|
||||
@@ -1136,13 +1197,33 @@ def confirm_new_email_view(request):
|
||||
activation.is_new_email_used = True
|
||||
activation.save()
|
||||
|
||||
# Actualizar el correo en la base de datos
|
||||
# Cambiar el email en la cuenta Battle.net implica recalcular salt/verifier,
|
||||
# porque el usuario SRP = SHA256(UPPER(email)).
|
||||
new_email_norm = bnet.normalize_email(activation.email)
|
||||
old_email_norm = bnet.normalize_email(activation.old_email or '')
|
||||
new_salt, new_verifier, srp_version = bnet.bnet_make_registration(new_email_norm, activation.password)
|
||||
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("""
|
||||
UPDATE account
|
||||
SET email = %s, reg_mail = %s
|
||||
WHERE username = %s
|
||||
""", [activation.email, activation.email, activation.username])
|
||||
# Localizar la cuenta bnet por su email actual (el antiguo)
|
||||
cursor.execute("SELECT id FROM battlenet_accounts WHERE email = %s", [old_email_norm])
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': '<span class="red-form-response">No se ha encontrado la cuenta Battle.net.</span>',
|
||||
})
|
||||
bnet_id = row[0]
|
||||
|
||||
# Actualizar email + verifier en battlenet_accounts
|
||||
cursor.execute(
|
||||
"UPDATE battlenet_accounts SET email = %s, srp_version = %s, salt = %s, verifier = %s WHERE id = %s",
|
||||
[new_email_norm, srp_version, new_salt, new_verifier, bnet_id]
|
||||
)
|
||||
# Actualizar el email de las cuentas de juego enlazadas (cosmético)
|
||||
cursor.execute(
|
||||
"UPDATE account SET email = %s, reg_mail = %s WHERE battlenet_account = %s",
|
||||
[new_email_norm, new_email_norm, bnet_id]
|
||||
)
|
||||
|
||||
# Enviar notificación al correo anterior
|
||||
context_old_email = {
|
||||
@@ -2390,9 +2471,9 @@ def rename_guild_view(request):
|
||||
|
||||
if not old_guild_name or not new_guild_name or not conf_new_guild_name or not current_password or not token:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Por favor, completa todos los campos.</span>'})
|
||||
|
||||
if not authenticate(username, current_password):
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">La contraseña actual es incorrecta.</span>'})
|
||||
|
||||
if not authenticate(email, current_password):
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">La contraseña actual es incorrecta.</span>'})
|
||||
|
||||
if not security_token or security_token.token != token:
|
||||
return JsonResponse({'success': False, 'message': '<span class="red-form-response">Token de seguridad incorrecto.</span>'})
|
||||
|
||||
Reference in New Issue
Block a user