Internal
Public Access
Implement comprehensive email verification and admin approval system for user registration. Users must verify their email before logging in, and admins must approve new users before they can access the system. Major features: - Email verification with UUID tokens (24hr expiry, one-time use) - Admin approval workflow via Django admin interface - Custom authentication backend enforcing verification and approval - Password change functionality for authenticated users - Enhanced profile page with proper styling and theme support - Collect first name, last name, and timezone during registration - Profile link added to header navigation Email notifications: - Verification email sent after registration - Admin notification when users need approval - Approval notification sent to users Security features: - Cryptographically secure UUID tokens - Token expiration and one-time use enforcement - Email enumeration protection - Custom JWT token validation for API access - Existing users auto-approved via data migration Templates added: - Email templates (verification, approval, admin notification) - Web templates (password change, verification pages) - Enhanced profile page with dark/light mode support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
129 lines
3.6 KiB
Python
129 lines
3.6 KiB
Python
"""
|
|
Utility functions for user management.
|
|
"""
|
|
|
|
from datetime import timedelta
|
|
from django.conf import settings
|
|
from django.core.mail import send_mail
|
|
from django.template.loader import render_to_string
|
|
from django.utils import timezone
|
|
from .models import EmailVerificationToken
|
|
|
|
|
|
TOKEN_EXPIRY_HOURS = getattr(settings, 'EMAIL_VERIFICATION_TOKEN_EXPIRY_HOURS', 24)
|
|
|
|
|
|
def generate_verification_token(user):
|
|
"""
|
|
Generate a new verification token for user.
|
|
Invalidates any existing unused tokens before creating a new one.
|
|
"""
|
|
# Invalidate any existing unused tokens
|
|
EmailVerificationToken.objects.filter(
|
|
user=user,
|
|
is_used=False
|
|
).update(is_used=True)
|
|
|
|
# Create new token
|
|
token = EmailVerificationToken.objects.create(
|
|
user=user,
|
|
expires_at=timezone.now() + timedelta(hours=TOKEN_EXPIRY_HOURS)
|
|
)
|
|
return token
|
|
|
|
|
|
def send_verification_email(user, request=None):
|
|
"""
|
|
Send email verification link to user.
|
|
"""
|
|
token = generate_verification_token(user)
|
|
|
|
# Build verification URL
|
|
if request:
|
|
domain = request.get_host()
|
|
protocol = 'https' if request.is_secure() else 'http'
|
|
else:
|
|
domain = settings.SITE_DOMAIN
|
|
protocol = 'https' if not settings.DEBUG else 'http'
|
|
|
|
verification_url = f"{protocol}://{domain}/verify-email/{token.token}/"
|
|
|
|
# Render email from template
|
|
context = {
|
|
'user': user,
|
|
'verification_url': verification_url,
|
|
'expiry_hours': TOKEN_EXPIRY_HOURS,
|
|
}
|
|
|
|
html_message = render_to_string('emails/verify_email.html', context)
|
|
text_message = render_to_string('emails/verify_email.txt', context)
|
|
|
|
send_mail(
|
|
subject='Verify your KeepItGoing email',
|
|
message=text_message,
|
|
from_email=settings.DEFAULT_FROM_EMAIL,
|
|
recipient_list=[user.email],
|
|
html_message=html_message,
|
|
fail_silently=False,
|
|
)
|
|
|
|
|
|
def send_approval_notification(user):
|
|
"""
|
|
Notify user their account has been approved.
|
|
"""
|
|
protocol = 'https' if not settings.DEBUG else 'http'
|
|
login_url = f"{protocol}://{settings.SITE_DOMAIN}/login/"
|
|
|
|
context = {
|
|
'user': user,
|
|
'login_url': login_url,
|
|
}
|
|
|
|
html_message = render_to_string('emails/account_approved.html', context)
|
|
text_message = render_to_string('emails/account_approved.txt', context)
|
|
|
|
send_mail(
|
|
subject='Your KeepItGoing account has been approved',
|
|
message=text_message,
|
|
from_email=settings.DEFAULT_FROM_EMAIL,
|
|
recipient_list=[user.email],
|
|
html_message=html_message,
|
|
fail_silently=False,
|
|
)
|
|
|
|
|
|
def notify_admins_new_user(user):
|
|
"""
|
|
Notify all staff users about new registration awaiting approval.
|
|
"""
|
|
from django.contrib.auth import get_user_model
|
|
User = get_user_model()
|
|
|
|
# Get all staff users
|
|
staff_users = User.objects.filter(is_staff=True, is_active=True)
|
|
staff_emails = [u.email for u in staff_users if u.email]
|
|
|
|
if not staff_emails:
|
|
return
|
|
|
|
protocol = 'https' if not settings.DEBUG else 'http'
|
|
admin_url = f"{protocol}://{settings.SITE_DOMAIN}/admin/users/user/{user.id}/change/"
|
|
|
|
context = {
|
|
'user': user,
|
|
'admin_url': admin_url,
|
|
}
|
|
|
|
html_message = render_to_string('emails/admin_new_user.html', context)
|
|
text_message = render_to_string('emails/admin_new_user.txt', context)
|
|
|
|
send_mail(
|
|
subject=f'New user registration: {user.email}',
|
|
message=text_message,
|
|
from_email=settings.DEFAULT_FROM_EMAIL,
|
|
recipient_list=staff_emails,
|
|
html_message=html_message,
|
|
fail_silently=True, # Don't fail registration if admin email fails
|
|
)
|