""" 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' )