Implementado Registro de Cuentas y Activacion de Email
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,35 @@
|
||||
import os
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
def enviar_correo(subject, to_email, template, context):
|
||||
"""
|
||||
Envía un correo electrónico utilizando una plantilla HTML.
|
||||
|
||||
Args:
|
||||
subject (str): El asunto del correo.
|
||||
to_email (str): Dirección de correo del destinatario.
|
||||
template (str): Ruta de la plantilla HTML.
|
||||
context (dict): Contexto para renderizar la plantilla.
|
||||
"""
|
||||
try:
|
||||
# Renderizar el contenido del correo desde una plantilla
|
||||
html_message = render_to_string(template, context)
|
||||
plain_message = strip_tags(html_message)
|
||||
from_email = settings.DEFAULT_FROM_EMAIL
|
||||
|
||||
# Enviar el correo
|
||||
send_mail(
|
||||
subject,
|
||||
plain_message,
|
||||
from_email,
|
||||
[to_email],
|
||||
html_message=html_message,
|
||||
fail_silently=False,
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error al enviar el correo: {e}")
|
||||
return False
|
||||
@@ -0,0 +1,37 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-12 12:56
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AccountActivation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('username', models.CharField(max_length=17)),
|
||||
('email', models.EmailField(max_length=254)),
|
||||
('password', models.CharField(max_length=64)),
|
||||
('salt', models.CharField(max_length=64)),
|
||||
('verifier', models.CharField(max_length=128)),
|
||||
('recruiter_id', models.IntegerField(blank=True, null=True)),
|
||||
('hash', models.CharField(max_length=32, unique=True)),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='recruitreward',
|
||||
name='claimed_by',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='claimedreward',
|
||||
name='ip_address',
|
||||
field=models.CharField(default='127.0.0.1', max_length=45),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-12 13:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0002_accountactivation_remove_recruitreward_claimed_by_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='accountactivation',
|
||||
name='salt',
|
||||
field=models.CharField(max_length=128),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-12 14:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0003_alter_accountactivation_salt'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='accountactivation',
|
||||
name='salt',
|
||||
field=models.BinaryField(max_length=32),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='accountactivation',
|
||||
name='verifier',
|
||||
field=models.BinaryField(max_length=32),
|
||||
),
|
||||
]
|
||||
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
+17
-1
@@ -3,6 +3,7 @@ from django.db import models
|
||||
from django_ckeditor_5.fields import CKEditor5Field
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils import timezone
|
||||
import uuid
|
||||
|
||||
class Noticia(models.Model):
|
||||
titulo = models.CharField(max_length=200)
|
||||
@@ -95,7 +96,22 @@ class ClaimedReward(models.Model):
|
||||
character_name = models.CharField(max_length=100)
|
||||
username = models.CharField(max_length=100)
|
||||
claimed_at = models.DateTimeField(auto_now_add=True)
|
||||
ip_address = models.GenericIPAddressField()
|
||||
ip_address = models.CharField(max_length=45, default='127.0.0.1') # Añadir un valor predeterminado
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.username} - {self.recruit_reward.reward_name} - {self.claimed_at}"
|
||||
|
||||
class AccountActivation(models.Model):
|
||||
username = models.CharField(max_length=17)
|
||||
email = models.EmailField()
|
||||
password = models.CharField(max_length=64) # Hash de la contraseña
|
||||
salt = models.BinaryField(max_length=32)
|
||||
verifier = models.BinaryField(max_length=32)
|
||||
recruiter_id = models.IntegerField(null=True, blank=True)
|
||||
hash = models.CharField(max_length=32, unique=True)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
|
||||
def is_expired(self):
|
||||
return timezone.now() > self.created_at + timezone.timedelta(hours=1)
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
$(function() {
|
||||
// Manejar el envío del formulario de creación de cuenta
|
||||
$('#uw-create-form').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const data = $(this).serialize();
|
||||
const createResponse = $("#create-response");
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '',
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
createResponse.hide().empty();
|
||||
if (response.success) {
|
||||
createResponse.html(`<span class="ok-form-response">${response.message}</span>`).slideDown();
|
||||
} else {
|
||||
createResponse.html(`<span class="red-form-response">${response.message}</span>`).slideDown();
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
createResponse.html('<span class="red-form-response">Error en el servidor. Inténtalo de nuevo más tarde.</span>').slideDown();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Manejar el estado del botón según el checkbox
|
||||
$('#accept-terms-cookies').on('change', function() {
|
||||
$('#create-account-btn').prop('disabled', !this.checked);
|
||||
});
|
||||
});
|
||||
|
||||
// Función para habilitar/deshabilitar el botón según el estado del checkbox (alternativa sin jQuery)
|
||||
function click_checkbox() {
|
||||
const checkbox = document.getElementById('accept-terms-cookies');
|
||||
const createButton = document.getElementById('create-account-btn');
|
||||
createButton.disabled = !checkbox.checked;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<h1>Enlace inválido</h1>
|
||||
<p>El enlace de activación no es válido o ha expirado.</p>
|
||||
<a href="/register">Regístrate de nuevo</a>
|
||||
@@ -0,0 +1,3 @@
|
||||
<h1>Activación exitosa</h1>
|
||||
<p>Tu cuenta ha sido activada correctamente. Ya puedes iniciar sesión.</p>
|
||||
<a href="/log-in">Iniciar sesión</a>
|
||||
@@ -0,0 +1,6 @@
|
||||
<h1>Bienvenido, {{ username }}</h1>
|
||||
<p>Gracias por registrarte en {{ NOMBRE_SERVIDOR }}.</p>
|
||||
<p>Para activar tu cuenta, haz clic en el siguiente enlace:</p>
|
||||
<a href="{{ activation_link }}">ACTIVAR LA CUENTA</a>
|
||||
<p>Si el botón no funciona, copia y pega el siguiente enlace en tu navegador:</p>
|
||||
<p>{{ activation_link }}</p>
|
||||
@@ -47,6 +47,7 @@
|
||||
<script src="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-js-handlers/recruit-response.js"></script>
|
||||
<script src="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-js-handlers/trade_points_response.js"></script>
|
||||
<script src="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-js-handlers/change_email_response.js"></script>
|
||||
<script src="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-js-handlers/register.js"></script>
|
||||
<script>var aowow_tooltips = { "colorlinks": true, "iconizelinks": true, "renamelinks": true }</script>
|
||||
<script src="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-js/nw-scripts.js"></script>
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
<td><input type="text" maxlength="12" name="recruiter" id="recruiter" placeholder="Reclutante"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div class="g-recaptcha g-recaptcha-div" id="gcaptcha" data-theme="dark" data-sitekey="6LcsJH4UAAAAAKqW0y4c-OVFijZjtZqkadRlpbRI"><div style="width: 304px; height: 78px;"><div><iframe title="reCAPTCHA" width="304" height="78" role="presentation" name="a-416nydq03u12" frameborder="0" scrolling="no" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation allow-modals allow-popups-to-escape-sandbox allow-storage-access-by-user-activation" src="https://www.google.com/recaptcha/api2/anchor?ar=1&k=6LcsJH4UAAAAAKqW0y4c-OVFijZjtZqkadRlpbRI&co=aHR0cHM6Ly91bHRpbW93b3cuY29tOjQ0Mw..&hl=en&v=-ZG7BC9TxCVEbzIO2m429usb&theme=dark&size=normal&cb=fc78i952eknd"></iframe></div><textarea id="g-recaptcha-response" name="g-recaptcha-response" class="g-recaptcha-response" style="width: 250px; height: 40px; border: 1px solid rgb(193, 193, 193); margin: 10px 25px; padding: 0px; resize: none; display: none;"></textarea></div><iframe style="display: none;"></iframe></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
@@ -34,6 +34,7 @@ urlpatterns = [
|
||||
path('ban-history/', views.ban_history_view, name='ban-history'),
|
||||
path('security-history/', views.security_history_view, name='security_history'),
|
||||
path('trade-points/', views.trade_points_view, name='trade_points'),
|
||||
path('activate-account', views.activate_account_view, name='activate_account'),
|
||||
|
||||
]
|
||||
|
||||
|
||||
+108
-1
@@ -2,19 +2,23 @@ import socket
|
||||
import hashlib
|
||||
import gmpy2
|
||||
import binascii
|
||||
import os
|
||||
import re
|
||||
from django.shortcuts import render, redirect
|
||||
from django.http import JsonResponse
|
||||
from django.db import connections
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import logout
|
||||
from django import forms
|
||||
from .models import Noticia, ClienteCategoria, ServerSelection, RecruitAFriend, DownloadClientPage, ContentCreator, RecruitReward, ClaimedReward
|
||||
from .models import Noticia, ClienteCategoria, ServerSelection, RecruitAFriend, DownloadClientPage, ContentCreator, RecruitReward, ClaimedReward, AccountActivation
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from .zone_definitions import get_zone_name
|
||||
from .ac_soap import execute_soap_command
|
||||
from django.utils import timezone
|
||||
from .library_correo import enviar_correo
|
||||
from django.utils.crypto import get_random_string
|
||||
|
||||
|
||||
# Configuración del logger
|
||||
@@ -265,9 +269,112 @@ def logout_view(request):
|
||||
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()
|
||||
|
||||
# Validar entradas
|
||||
if not username or 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 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
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT COUNT(*) FROM account WHERE username = %s", [username])
|
||||
if cursor.fetchone()[0] > 0:
|
||||
return JsonResponse({'success': False, 'message': 'El nombre de usuario ya está en uso.'})
|
||||
|
||||
# Validar reclutador
|
||||
recruiter_id = None
|
||||
if recruiter:
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT id FROM account WHERE username = %s", [recruiter])
|
||||
recruiter_data = cursor.fetchone()
|
||||
recruiter_id = recruiter_data[0] if recruiter_data else None
|
||||
|
||||
# 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
|
||||
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
|
||||
)
|
||||
|
||||
# Enviar correo de activación
|
||||
activation_link = f"http://localhost:8009/activate-account?act={activation_hash}"
|
||||
context = {
|
||||
'username': username,
|
||||
'activation_link': activation_link,
|
||||
'NOMBRE_SERVIDOR': 'Nova WoW'
|
||||
}
|
||||
enviar_correo(
|
||||
subject='Activa tu cuenta en Nova WoW',
|
||||
to_email=email,
|
||||
template='emails/activation.html',
|
||||
context=context
|
||||
)
|
||||
|
||||
return JsonResponse({'success': True, 'message': 'Cuenta creada exitosamente. Revisa tu correo para activarla.'})
|
||||
|
||||
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()
|
||||
|
||||
return render(request, 'auth/activation_success.html')
|
||||
|
||||
except AccountActivation.DoesNotExist:
|
||||
return render(request, 'auth/activation_invalid.html')
|
||||
|
||||
def recover_account_view(request):
|
||||
return render(request, 'auth/recover_account.html')
|
||||
|
||||
|
||||
Binary file not shown.
@@ -47,6 +47,16 @@ AC_SOAP_USER = "AC_SOAP"
|
||||
AC_SOAP_PASSWORD = "Ladyamy89"
|
||||
AC_SOAP_URN = "urn:AC"
|
||||
|
||||
# Configuración del correo electrónico para SMTP de Gmail
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
EMAIL_HOST = 'smtp.gmail.com'
|
||||
EMAIL_PORT = 587
|
||||
EMAIL_USE_TLS = True
|
||||
EMAIL_USE_SSL = False
|
||||
EMAIL_HOST_USER = 'inna.aldaia@gmail.com' # Reemplaza con tu dirección de correo electrónico
|
||||
EMAIL_HOST_PASSWORD = 'whqa zzzs mqal vdih' # Reemplaza con tu contraseña de Gmail o App Password
|
||||
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
||||
|
||||
|
||||
CKEDITOR_5_CONFIGS = {
|
||||
'default': {
|
||||
|
||||
Reference in New Issue
Block a user