update
This commit is contained in:
138
accounts/forms.py
Normal file
138
accounts/forms.py
Normal file
@@ -0,0 +1,138 @@
|
||||
"""
|
||||
Forms for accounts app.
|
||||
"""
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||
from django.utils import timezone
|
||||
from .models import User, UserProfile
|
||||
from .security import InputSanitizer, PasswordSecurity
|
||||
from .form_mixins import BotProtectionMixin
|
||||
|
||||
|
||||
class UserRegistrationForm(BotProtectionMixin, UserCreationForm):
|
||||
"""User registration form with security validation and bot protection."""
|
||||
email = forms.EmailField(required=True)
|
||||
consent_given = forms.BooleanField(
|
||||
required=True,
|
||||
label='I agree to the Privacy Policy and Terms of Service'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'email', 'password1', 'password2', 'consent_given')
|
||||
|
||||
def clean_username(self):
|
||||
username = self.cleaned_data.get('username')
|
||||
if username:
|
||||
# Sanitize username
|
||||
username = InputSanitizer.sanitize_html(username)
|
||||
# Check for SQL injection patterns
|
||||
sanitized = InputSanitizer.sanitize_sql(username)
|
||||
if sanitized is None:
|
||||
raise forms.ValidationError('Invalid username format.')
|
||||
return username
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data.get('email')
|
||||
if email:
|
||||
# Validate email format
|
||||
if not InputSanitizer.validate_email(email):
|
||||
raise forms.ValidationError('Invalid email format.')
|
||||
# Sanitize email
|
||||
email = InputSanitizer.sanitize_html(email)
|
||||
return email
|
||||
|
||||
def clean_password1(self):
|
||||
password = self.cleaned_data.get('password1')
|
||||
if password:
|
||||
is_strong, message = PasswordSecurity.check_password_strength(password)
|
||||
if not is_strong:
|
||||
raise forms.ValidationError(message)
|
||||
return password
|
||||
|
||||
def save(self, commit=True):
|
||||
user = super().save(commit=False)
|
||||
user.email = self.cleaned_data['email']
|
||||
if commit:
|
||||
user.save()
|
||||
# Create profile with consent
|
||||
profile = UserProfile.objects.create(
|
||||
user=user,
|
||||
consent_given=self.cleaned_data['consent_given']
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
class UserProfileForm(forms.ModelForm):
|
||||
"""User profile edit form."""
|
||||
first_name = forms.CharField(max_length=100, required=False)
|
||||
last_name = forms.CharField(max_length=100, required=False)
|
||||
|
||||
class Meta:
|
||||
model = UserProfile
|
||||
fields = ('first_name', 'last_name', 'phone', 'preferred_language')
|
||||
widgets = {
|
||||
'phone': forms.TextInput(attrs={'placeholder': '+359...'}),
|
||||
}
|
||||
|
||||
|
||||
class MFAVerifyForm(forms.Form):
|
||||
"""MFA verification form."""
|
||||
token = forms.CharField(
|
||||
max_length=6,
|
||||
min_length=6,
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': '000000',
|
||||
'autofocus': True,
|
||||
'pattern': '[0-9]{6}',
|
||||
'maxlength': '6'
|
||||
}),
|
||||
label='Verification Code',
|
||||
help_text='Enter the 6-digit code from your authenticator app'
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def verify_token(self):
|
||||
"""Verify the TOTP token."""
|
||||
if not self.user:
|
||||
return False
|
||||
|
||||
token = self.cleaned_data.get('token')
|
||||
if not token:
|
||||
return False
|
||||
|
||||
# Get the TOTP device (for login, use confirmed device; for setup, use unconfirmed)
|
||||
try:
|
||||
# Try confirmed device first (for login)
|
||||
device = TOTPDevice.objects.get(user=self.user, name='default', confirmed=True)
|
||||
except TOTPDevice.DoesNotExist:
|
||||
# Try unconfirmed device (for setup)
|
||||
try:
|
||||
device = TOTPDevice.objects.get(user=self.user, name='default', confirmed=False)
|
||||
except TOTPDevice.DoesNotExist:
|
||||
return False
|
||||
|
||||
return device.verify_token(token)
|
||||
|
||||
|
||||
class MFASetupForm(forms.Form):
|
||||
"""MFA setup form (for confirmation)."""
|
||||
token = forms.CharField(
|
||||
max_length=6,
|
||||
min_length=6,
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': '000000',
|
||||
'autofocus': True,
|
||||
'pattern': '[0-9]{6}',
|
||||
'maxlength': '6'
|
||||
}),
|
||||
label='Verification Code',
|
||||
help_text='Enter the 6-digit code from your authenticator app to confirm setup'
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user