🔧 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:
Claude 2025-08-16 12:35:26 +05:30
parent 8ed4596453
commit e8ade314a6
7 changed files with 27 additions and 334 deletions

View File

@ -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",
),
]

View File

@ -11,7 +11,6 @@ class User(AbstractUser):
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)
updated_at = models.DateTimeField(auto_now=True)
email_verified = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
@ -120,27 +119,3 @@ class PasswordResetToken(models.Model):
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}"

View File

@ -10,6 +10,4 @@ urlpatterns = [
path('profile/', views.profile_view, name='profile'),
path('forgot-password/', views.forgot_password_view, name='forgot_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'),
]

View File

@ -10,7 +10,7 @@ from django.urls import reverse
from django_ratelimit.decorators import ratelimit
from django_ratelimit import UNSAFE
from django_ratelimit.exceptions import Ratelimited
from .models import User, PasswordResetToken, EmailVerificationToken
from .models import User, PasswordResetToken
import logging
logger = logging.getLogger(__name__)
@ -35,48 +35,6 @@ def validate_password_strength(password):
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):
@ -108,12 +66,6 @@ def login_view(request):
user = authenticate(request, username=email, password=password)
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)
# Clear rate limit flag on successful login
@ -168,21 +120,7 @@ def register_view(request):
password=password1
)
# Handle email verification based on settings
if settings.REQUIRE_EMAIL_VERIFICATION:
# Don't automatically login - require email verification first
# Send verification email
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()
# Auto-login new users (no email verification required)
login(request, user)
messages.success(request, f'Welcome {user.username}!')
return redirect('core:homepage')
@ -353,53 +291,5 @@ def reset_password_view(request, 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')

View File

@ -286,7 +286,7 @@ def health_check_view(request):
# Simple external service wrapper configurations
EXTERNAL_PAGES = {
'event-invitation': {
'event': {
'title': 'Event Registration',
'description': 'Register for our upcoming event',
'external_url': 'https://form.jotform.com/252214924850455',

View File

@ -4,7 +4,7 @@
"builder": "NIXPACKS"
},
"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",
"restartPolicyMaxRetries": 3
},

View File

@ -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 %}