diff --git a/home/__pycache__/library_correo.cpython-313.pyc b/home/__pycache__/library_correo.cpython-313.pyc new file mode 100644 index 0000000..ce306bf Binary files /dev/null and b/home/__pycache__/library_correo.cpython-313.pyc differ diff --git a/home/__pycache__/models.cpython-313.pyc b/home/__pycache__/models.cpython-313.pyc index 7ffe54b..d1d3701 100644 Binary files a/home/__pycache__/models.cpython-313.pyc and b/home/__pycache__/models.cpython-313.pyc differ diff --git a/home/__pycache__/urls.cpython-313.pyc b/home/__pycache__/urls.cpython-313.pyc index 94e63b6..20e4bd8 100644 Binary files a/home/__pycache__/urls.cpython-313.pyc and b/home/__pycache__/urls.cpython-313.pyc differ diff --git a/home/__pycache__/views.cpython-313.pyc b/home/__pycache__/views.cpython-313.pyc index 933a8d8..5e21b44 100644 Binary files a/home/__pycache__/views.cpython-313.pyc and b/home/__pycache__/views.cpython-313.pyc differ diff --git a/home/library_correo.py b/home/library_correo.py new file mode 100644 index 0000000..580d624 --- /dev/null +++ b/home/library_correo.py @@ -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 diff --git a/home/migrations/0002_accountactivation_remove_recruitreward_claimed_by_and_more.py b/home/migrations/0002_accountactivation_remove_recruitreward_claimed_by_and_more.py new file mode 100644 index 0000000..d8b4045 --- /dev/null +++ b/home/migrations/0002_accountactivation_remove_recruitreward_claimed_by_and_more.py @@ -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), + ), + ] diff --git a/home/migrations/0003_alter_accountactivation_salt.py b/home/migrations/0003_alter_accountactivation_salt.py new file mode 100644 index 0000000..9d1de9f --- /dev/null +++ b/home/migrations/0003_alter_accountactivation_salt.py @@ -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), + ), + ] diff --git a/home/migrations/0004_alter_accountactivation_salt_and_more.py b/home/migrations/0004_alter_accountactivation_salt_and_more.py new file mode 100644 index 0000000..361ee3a --- /dev/null +++ b/home/migrations/0004_alter_accountactivation_salt_and_more.py @@ -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), + ), + ] diff --git a/home/migrations/__pycache__/0002_accountactivation_remove_recruitreward_claimed_by_and_more.cpython-313.pyc b/home/migrations/__pycache__/0002_accountactivation_remove_recruitreward_claimed_by_and_more.cpython-313.pyc new file mode 100644 index 0000000..0fd5d3f Binary files /dev/null and b/home/migrations/__pycache__/0002_accountactivation_remove_recruitreward_claimed_by_and_more.cpython-313.pyc differ diff --git a/home/migrations/__pycache__/0003_alter_accountactivation_salt.cpython-313.pyc b/home/migrations/__pycache__/0003_alter_accountactivation_salt.cpython-313.pyc new file mode 100644 index 0000000..6add166 Binary files /dev/null and b/home/migrations/__pycache__/0003_alter_accountactivation_salt.cpython-313.pyc differ diff --git a/home/migrations/__pycache__/0004_alter_accountactivation_salt_and_more.cpython-313.pyc b/home/migrations/__pycache__/0004_alter_accountactivation_salt_and_more.cpython-313.pyc new file mode 100644 index 0000000..e6af62b Binary files /dev/null and b/home/migrations/__pycache__/0004_alter_accountactivation_salt_and_more.cpython-313.pyc differ diff --git a/home/models.py b/home/models.py index 2dd9f1f..13ad8be 100644 --- a/home/models.py +++ b/home/models.py @@ -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}" \ No newline at end of file + 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) + + \ No newline at end of file diff --git a/home/static/nw-themes/nw-ryu/nw-js-handlers/register.js b/home/static/nw-themes/nw-ryu/nw-js-handlers/register.js new file mode 100644 index 0000000..741d09e --- /dev/null +++ b/home/static/nw-themes/nw-ryu/nw-js-handlers/register.js @@ -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(`${response.message}`).slideDown(); + } else { + createResponse.html(`${response.message}`).slideDown(); + } + }, + error: function() { + createResponse.html('Error en el servidor. Inténtalo de nuevo más tarde.').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; +} diff --git a/home/templates/auth/activation_invalid.html b/home/templates/auth/activation_invalid.html new file mode 100644 index 0000000..47bef81 --- /dev/null +++ b/home/templates/auth/activation_invalid.html @@ -0,0 +1,3 @@ +

Enlace inválido

+

El enlace de activación no es válido o ha expirado.

+Regístrate de nuevo diff --git a/home/templates/auth/activation_success.html b/home/templates/auth/activation_success.html new file mode 100644 index 0000000..704440a --- /dev/null +++ b/home/templates/auth/activation_success.html @@ -0,0 +1,3 @@ +

Activación exitosa

+

Tu cuenta ha sido activada correctamente. Ya puedes iniciar sesión.

+Iniciar sesión diff --git a/home/templates/emails/activation.html b/home/templates/emails/activation.html new file mode 100644 index 0000000..3342564 --- /dev/null +++ b/home/templates/emails/activation.html @@ -0,0 +1,6 @@ +

Bienvenido, {{ username }}

+

Gracias por registrarte en {{ NOMBRE_SERVIDOR }}.

+

Para activar tu cuenta, haz clic en el siguiente enlace:

+ACTIVAR LA CUENTA +

Si el botón no funciona, copia y pega el siguiente enlace en tu navegador:

+

{{ activation_link }}

diff --git a/home/templates/partials/head.html b/home/templates/partials/head.html index ed1e2c9..d858c7d 100644 --- a/home/templates/partials/head.html +++ b/home/templates/partials/head.html @@ -47,6 +47,7 @@ + diff --git a/home/templates/partials/register.html b/home/templates/partials/register.html index ac1fdb2..d43c3e6 100644 --- a/home/templates/partials/register.html +++ b/home/templates/partials/register.html @@ -75,7 +75,6 @@ -
diff --git a/home/urls.py b/home/urls.py index be99e0c..3b69ca8 100644 --- a/home/urls.py +++ b/home/urls.py @@ -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'), ] diff --git a/home/views.py b/home/views.py index 1df80ab..2c9c4cc 100644 --- a/home/views.py +++ b/home/views.py @@ -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,8 +269,111 @@ 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') diff --git a/novawow/__pycache__/settings.cpython-313.pyc b/novawow/__pycache__/settings.cpython-313.pyc index 5db2018..8246ab7 100644 Binary files a/novawow/__pycache__/settings.cpython-313.pyc and b/novawow/__pycache__/settings.cpython-313.pyc differ diff --git a/novawow/settings.py b/novawow/settings.py index 44f3cbe..6e4cc59 100644 --- a/novawow/settings.py +++ b/novawow/settings.py @@ -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': {