mirror of
https://github.com/thecyberlearn/quantum-ai-v3.git
synced 2026-08-18 12:52:58 +00:00
🔧 Remove email verification system and update event URL
- Remove email_verified field from User model - Delete EmailVerificationToken model and related views - Update registration to auto-login users without verification - Change event URL from /event-invitation/ to /event/ - Update Railway deployment configuration - Remove email verification templates and URLs 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8ed4596453
commit
e8ade314a6
@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 5.2.4 on 2025-08-16 06:59
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentication", "0004_user_email_verified_emailverificationtoken"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="user",
|
||||||
|
name="email_verified",
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name="EmailVerificationToken",
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -11,7 +11,6 @@ class User(AbstractUser):
|
|||||||
wallet_balance = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0.00'), db_index=True)
|
wallet_balance = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0.00'), db_index=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
|
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
email_verified = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
USERNAME_FIELD = 'email'
|
USERNAME_FIELD = 'email'
|
||||||
REQUIRED_FIELDS = ['username']
|
REQUIRED_FIELDS = ['username']
|
||||||
@ -120,27 +119,3 @@ class PasswordResetToken(models.Model):
|
|||||||
return f"Password reset token for {self.user.email}"
|
return f"Password reset token for {self.user.email}"
|
||||||
|
|
||||||
|
|
||||||
class EmailVerificationToken(models.Model):
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='email_verification_tokens')
|
|
||||||
token = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
expires_at = models.DateTimeField()
|
|
||||||
is_used = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
if not self.expires_at:
|
|
||||||
self.expires_at = timezone.now() + timedelta(hours=24) # 24-hour expiration for email verification
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
return not self.is_used and timezone.now() < self.expires_at
|
|
||||||
|
|
||||||
def mark_as_used(self):
|
|
||||||
self.is_used = True
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ['-created_at']
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"Email verification token for {self.user.email}"
|
|
||||||
|
|||||||
@ -10,6 +10,4 @@ urlpatterns = [
|
|||||||
path('profile/', views.profile_view, name='profile'),
|
path('profile/', views.profile_view, name='profile'),
|
||||||
path('forgot-password/', views.forgot_password_view, name='forgot_password'),
|
path('forgot-password/', views.forgot_password_view, name='forgot_password'),
|
||||||
path('reset-password/<uuid:token>/', views.reset_password_view, name='reset_password'),
|
path('reset-password/<uuid:token>/', views.reset_password_view, name='reset_password'),
|
||||||
path('verify-email/<uuid:token>/', views.verify_email_view, name='verify_email'),
|
|
||||||
path('resend-verification/', views.resend_verification_view, name='resend_verification'),
|
|
||||||
]
|
]
|
||||||
@ -10,7 +10,7 @@ from django.urls import reverse
|
|||||||
from django_ratelimit.decorators import ratelimit
|
from django_ratelimit.decorators import ratelimit
|
||||||
from django_ratelimit import UNSAFE
|
from django_ratelimit import UNSAFE
|
||||||
from django_ratelimit.exceptions import Ratelimited
|
from django_ratelimit.exceptions import Ratelimited
|
||||||
from .models import User, PasswordResetToken, EmailVerificationToken
|
from .models import User, PasswordResetToken
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -35,48 +35,6 @@ def validate_password_strength(password):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def send_verification_email(user):
|
|
||||||
"""Send email verification to new user"""
|
|
||||||
try:
|
|
||||||
# Create verification token
|
|
||||||
verification_token = EmailVerificationToken.objects.create(user=user)
|
|
||||||
|
|
||||||
# Build verification URL
|
|
||||||
verification_path = reverse('authentication:verify_email', kwargs={'token': verification_token.token})
|
|
||||||
verification_url = f"{settings.SITE_URL}{verification_path}"
|
|
||||||
|
|
||||||
# Send email
|
|
||||||
subject = 'Verify Your Email Address - Quantum Tasks AI'
|
|
||||||
message = f'''
|
|
||||||
Hello {user.username},
|
|
||||||
|
|
||||||
Welcome to Quantum Tasks AI! Please verify your email address to complete your account setup.
|
|
||||||
|
|
||||||
Click the link below to verify your email:
|
|
||||||
{verification_url}
|
|
||||||
|
|
||||||
This link will expire in 24 hours.
|
|
||||||
|
|
||||||
If you didn't create this account, please ignore this email.
|
|
||||||
|
|
||||||
Best regards,
|
|
||||||
Quantum Tasks AI Team
|
|
||||||
'''
|
|
||||||
|
|
||||||
send_mail(
|
|
||||||
subject,
|
|
||||||
message,
|
|
||||||
settings.DEFAULT_FROM_EMAIL,
|
|
||||||
[user.email],
|
|
||||||
fail_silently=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"Verification email sent successfully to {user.email}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to send verification email to {user.email}: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def handle_ratelimited(request, exception):
|
def handle_ratelimited(request, exception):
|
||||||
@ -108,12 +66,6 @@ def login_view(request):
|
|||||||
|
|
||||||
user = authenticate(request, username=email, password=password)
|
user = authenticate(request, username=email, password=password)
|
||||||
if user is not None:
|
if user is not None:
|
||||||
# Check if email is verified (only if email verification is required)
|
|
||||||
if settings.REQUIRE_EMAIL_VERIFICATION and not user.email_verified:
|
|
||||||
messages.warning(request, 'Please verify your email address before logging in. Check your inbox for the verification link.')
|
|
||||||
# Store resend URL in context for template
|
|
||||||
context = {'show_resend_verification': True}
|
|
||||||
return render(request, 'authentication/login.html', context)
|
|
||||||
|
|
||||||
login(request, user)
|
login(request, user)
|
||||||
# Clear rate limit flag on successful login
|
# Clear rate limit flag on successful login
|
||||||
@ -168,24 +120,10 @@ def register_view(request):
|
|||||||
password=password1
|
password=password1
|
||||||
)
|
)
|
||||||
|
|
||||||
# Handle email verification based on settings
|
# Auto-login new users (no email verification required)
|
||||||
if settings.REQUIRE_EMAIL_VERIFICATION:
|
login(request, user)
|
||||||
# Don't automatically login - require email verification first
|
messages.success(request, f'Welcome {user.username}!')
|
||||||
# Send verification email
|
return redirect('core:homepage')
|
||||||
if send_verification_email(user):
|
|
||||||
messages.success(request, 'Account created. Check your email to verify.')
|
|
||||||
else:
|
|
||||||
messages.warning(request, 'Account created. Email verification failed - try again later.')
|
|
||||||
|
|
||||||
return redirect('authentication:login')
|
|
||||||
else:
|
|
||||||
# Skip email verification - auto-verify and login
|
|
||||||
user.email_verified = True
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
login(request, user)
|
|
||||||
messages.success(request, f'Welcome {user.username}!')
|
|
||||||
return redirect('core:homepage')
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating account for {email}: {str(e)}")
|
logger.error(f"Error creating account for {email}: {str(e)}")
|
||||||
messages.error(request, 'Error creating account')
|
messages.error(request, 'Error creating account')
|
||||||
@ -353,53 +291,5 @@ def reset_password_view(request, token):
|
|||||||
return render(request, 'authentication/reset_password.html', {'token': token})
|
return render(request, 'authentication/reset_password.html', {'token': token})
|
||||||
|
|
||||||
|
|
||||||
def verify_email_view(request, token):
|
|
||||||
"""Email verification view"""
|
|
||||||
verification_token = get_object_or_404(EmailVerificationToken, token=token)
|
|
||||||
|
|
||||||
if not verification_token.is_valid():
|
|
||||||
messages.error(request, 'This verification link has expired or is invalid.')
|
|
||||||
return redirect('authentication:login')
|
|
||||||
|
|
||||||
# Mark email as verified
|
|
||||||
user = verification_token.user
|
|
||||||
user.email_verified = True
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
# Mark token as used
|
|
||||||
verification_token.mark_as_used()
|
|
||||||
|
|
||||||
messages.success(request, 'Email verified. You can now log in.')
|
|
||||||
return redirect('authentication:login')
|
|
||||||
|
|
||||||
|
|
||||||
@ratelimit(key='ip', rate='2/5m', method=UNSAFE, block=False)
|
|
||||||
def resend_verification_view(request):
|
|
||||||
"""Resend email verification"""
|
|
||||||
# Check if rate limited
|
|
||||||
if getattr(request, 'limited', False):
|
|
||||||
logger.warning(f"Verification resend rate limit exceeded for IP {request.META.get('REMOTE_ADDR')}")
|
|
||||||
messages.error(request, 'Too many verification requests. Please try again in a few minutes.')
|
|
||||||
return redirect('authentication:login')
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
email = request.POST.get('email')
|
|
||||||
|
|
||||||
try:
|
|
||||||
user = User.objects.get(email=email)
|
|
||||||
|
|
||||||
if user.email_verified:
|
|
||||||
messages.info(request, 'Your email is already verified. You can log in.')
|
|
||||||
return redirect('authentication:login')
|
|
||||||
|
|
||||||
# Send new verification email
|
|
||||||
if send_verification_email(user):
|
|
||||||
messages.success(request, 'Verification email sent.')
|
|
||||||
else:
|
|
||||||
messages.error(request, 'Unable to send verification email at this time.')
|
|
||||||
|
|
||||||
except User.DoesNotExist:
|
|
||||||
# Show same success message to prevent user enumeration
|
|
||||||
messages.success(request, 'If an account with that email exists, a verification email has been sent.')
|
|
||||||
|
|
||||||
return render(request, 'authentication/resend_verification.html')
|
|
||||||
|
|||||||
@ -286,7 +286,7 @@ def health_check_view(request):
|
|||||||
|
|
||||||
# Simple external service wrapper configurations
|
# Simple external service wrapper configurations
|
||||||
EXTERNAL_PAGES = {
|
EXTERNAL_PAGES = {
|
||||||
'event-invitation': {
|
'event': {
|
||||||
'title': 'Event Registration',
|
'title': 'Event Registration',
|
||||||
'description': 'Register for our upcoming event',
|
'description': 'Register for our upcoming event',
|
||||||
'external_url': 'https://form.jotform.com/252214924850455',
|
'external_url': 'https://form.jotform.com/252214924850455',
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"builder": "NIXPACKS"
|
"builder": "NIXPACKS"
|
||||||
},
|
},
|
||||||
"deploy": {
|
"deploy": {
|
||||||
"startCommand": "python manage.py migrate; python manage.py reset_admin; python manage.py verify_email admin@quantumtaskai.com --force || true; python manage.py collectstatic --noinput && gunicorn netcop_hub.wsgi:application --bind 0.0.0.0:$PORT --workers 1 --timeout 60",
|
"startCommand": "python manage.py migrate; python manage.py reset_admin; python manage.py collectstatic --noinput && gunicorn netcop_hub.wsgi:application --bind 0.0.0.0:$PORT --workers 1 --timeout 60",
|
||||||
"restartPolicyType": "ON_FAILURE",
|
"restartPolicyType": "ON_FAILURE",
|
||||||
"restartPolicyMaxRetries": 3
|
"restartPolicyMaxRetries": 3
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,190 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block title %}Resend Email Verification{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
|
||||||
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
|
|
||||||
<style>
|
|
||||||
/* Override main-container for full-width sections */
|
|
||||||
.main-container {
|
|
||||||
max-width: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Login page using agent-base.css */
|
|
||||||
.login-page {
|
|
||||||
background: var(--background);
|
|
||||||
min-height: calc(100vh - 84px);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-container {
|
|
||||||
background: var(--surface);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
padding: 48px;
|
|
||||||
box-shadow: var(--shadow-lg);
|
|
||||||
width: 100%;
|
|
||||||
max-width: 450px;
|
|
||||||
border: 1px solid var(--outline);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-title {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--on-surface);
|
|
||||||
margin: 0 0 var(--spacing-sm) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-subtitle {
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--on-surface-variant);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-btn {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages {
|
|
||||||
margin-bottom: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
margin-bottom: var(--spacing-sm);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
border: 1px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message.success {
|
|
||||||
background: var(--surface-variant);
|
|
||||||
color: var(--on-surface);
|
|
||||||
border-color: var(--outline);
|
|
||||||
}
|
|
||||||
|
|
||||||
.message.error {
|
|
||||||
background: var(--surface-variant);
|
|
||||||
color: var(--on-surface);
|
|
||||||
border-color: var(--outline);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-auth-links {
|
|
||||||
text-align: center;
|
|
||||||
padding-top: 24px;
|
|
||||||
border-top: 1px solid var(--outline);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-auth-links p {
|
|
||||||
margin: var(--spacing-sm) 0;
|
|
||||||
color: var(--on-surface-variant);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-auth-links a {
|
|
||||||
color: var(--primary);
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-auth-links a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.login-page {
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-container {
|
|
||||||
padding: 32px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-title {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="login-page theme-professional">
|
|
||||||
<div class="login-container">
|
|
||||||
<div class="login-header">
|
|
||||||
<h1 class="login-title">Resend Verification</h1>
|
|
||||||
<p class="login-subtitle">Enter your email to resend verification link</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if messages %}
|
|
||||||
<div class="messages">
|
|
||||||
{% for message in messages %}
|
|
||||||
<div class="message {{ message.tags }}">{{ message }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<form method="post" class="login-form">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email" class="form-label">Email Address</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
class="form-input"
|
|
||||||
placeholder="Enter your email address"
|
|
||||||
required
|
|
||||||
autocomplete="email"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary login-btn">
|
|
||||||
Send Verification Email
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="login-auth-links">
|
|
||||||
<p><a href="{% url 'authentication:login' %}">Back to Sign In</a></p>
|
|
||||||
<p>Don't have an account? <a href="{% url 'authentication:register' %}">Create account</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_js %}
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const emailField = document.getElementById('email');
|
|
||||||
const form = document.querySelector('.login-form');
|
|
||||||
const submitBtn = document.querySelector('.login-btn');
|
|
||||||
|
|
||||||
// Focus on email field
|
|
||||||
if (emailField) {
|
|
||||||
emailField.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Form submission
|
|
||||||
if (form && submitBtn) {
|
|
||||||
form.addEventListener('submit', function() {
|
|
||||||
submitBtn.innerHTML = 'Sending...';
|
|
||||||
submitBtn.disabled = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
Loading…
Reference in New Issue
Block a user