Implementado Registro de Cuentas y Activacion de Email

This commit is contained in:
adevopg
2024-11-12 15:30:01 +01:00
parent 6b976046cb
commit 996c93aafb
22 changed files with 301 additions and 4 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+35
View File
@@ -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),
),
]
+17 -1
View File
@@ -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>
+6
View File
@@ -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>
+1
View File
@@ -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>
-1
View File
@@ -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&amp;k=6LcsJH4UAAAAAKqW0y4c-OVFijZjtZqkadRlpbRI&amp;co=aHR0cHM6Ly91bHRpbW93b3cuY29tOjQ0Mw..&amp;hl=en&amp;v=-ZG7BC9TxCVEbzIO2m429usb&amp;theme=dark&amp;size=normal&amp;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>
+1
View File
@@ -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
View File
@@ -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.
+10
View File
@@ -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': {