snapshot: preserve account security and profile UI cleanup
This commit is contained in:
@@ -11,7 +11,7 @@ from .branding import get_company_email_domain
|
||||
from .form_builder import apply_form_field_config
|
||||
from .models import EmployeeProfile, FormOption, OffboardingRequest, OnboardingRequest, PortalBranding, PortalCompanyConfig, PortalTrialConfig, UserProfile, WorkflowConfig
|
||||
from .roles import ROLE_ADMIN, ROLE_GROUP_NAMES, ROLE_IT_STAFF, ROLE_LABELS, ROLE_PLATFORM_OWNER, ROLE_STAFF, ROLE_SUPER_ADMIN, assign_user_role
|
||||
from .totp import normalize_totp_token, verify_totp_token
|
||||
from .totp import normalize_recovery_code, normalize_totp_token, verify_totp_token
|
||||
|
||||
|
||||
YES_NO_CHOICES = [('', '--'), ('ja', 'Ja'), ('nein', 'Nein')]
|
||||
@@ -111,6 +111,12 @@ class AppAuthenticationForm(AuthenticationForm):
|
||||
max_length=12,
|
||||
widget=forms.TextInput(attrs={'autocomplete': 'one-time-code', 'inputmode': 'numeric'}),
|
||||
)
|
||||
recovery_code = forms.CharField(
|
||||
label=gettext_lazy('Recovery-Code'),
|
||||
required=False,
|
||||
max_length=32,
|
||||
widget=forms.TextInput(attrs={'autocomplete': 'one-time-code'}),
|
||||
)
|
||||
|
||||
error_messages = {
|
||||
**AuthenticationForm.error_messages,
|
||||
@@ -126,6 +132,14 @@ class AppAuthenticationForm(AuthenticationForm):
|
||||
profile, _ = UserProfile.objects.get_or_create(user=user)
|
||||
if profile.totp_enabled:
|
||||
otp_code = normalize_totp_token(cleaned_data.get('otp_code'))
|
||||
recovery_code = normalize_recovery_code(cleaned_data.get('recovery_code'))
|
||||
if recovery_code:
|
||||
if not profile.consume_recovery_code(recovery_code):
|
||||
raise ValidationError(
|
||||
self.error_messages['invalid_otp'],
|
||||
code='invalid_otp',
|
||||
)
|
||||
return cleaned_data
|
||||
if not otp_code:
|
||||
raise ValidationError(
|
||||
self.error_messages['missing_otp'],
|
||||
@@ -296,8 +310,15 @@ class AccountTOTPDisableForm(forms.Form):
|
||||
verification_code = forms.CharField(
|
||||
label=gettext_lazy('TOTP-Code'),
|
||||
max_length=12,
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'autocomplete': 'one-time-code', 'inputmode': 'numeric'}),
|
||||
)
|
||||
recovery_code = forms.CharField(
|
||||
label=gettext_lazy('Recovery-Code'),
|
||||
max_length=32,
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'autocomplete': 'one-time-code'}),
|
||||
)
|
||||
|
||||
def __init__(self, *args, user=None, profile=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -310,14 +331,66 @@ class AccountTOTPDisableForm(forms.Form):
|
||||
raise ValidationError(_('Das aktuelle Passwort ist nicht korrekt.'))
|
||||
return password
|
||||
|
||||
def clean_verification_code(self):
|
||||
code = normalize_totp_token(self.cleaned_data.get('verification_code'))
|
||||
if not code:
|
||||
raise ValidationError(_('Bitte geben Sie einen gültigen TOTP-Code ein.'))
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
code = normalize_totp_token(cleaned_data.get('verification_code'))
|
||||
recovery_code = normalize_recovery_code(cleaned_data.get('recovery_code'))
|
||||
if not code and not recovery_code:
|
||||
raise ValidationError(_('Bitte geben Sie einen gültigen TOTP-Code oder Recovery-Code ein.'))
|
||||
secret = getattr(self.profile, 'totp_secret', '') or ''
|
||||
if not secret or not verify_totp_token(secret, code, for_time=int(timezone.now().timestamp())):
|
||||
raise ValidationError(_('Der TOTP-Code ist ungültig.'))
|
||||
return code
|
||||
if code:
|
||||
if not secret or not verify_totp_token(secret, code, for_time=int(timezone.now().timestamp())):
|
||||
raise ValidationError(_('Der TOTP-Code ist ungültig.'))
|
||||
return cleaned_data
|
||||
if not self.profile.consume_recovery_code(recovery_code):
|
||||
raise ValidationError(_('Der Recovery-Code ist ungültig.'))
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class AccountTOTPRegenerateRecoveryCodesForm(forms.Form):
|
||||
current_password = forms.CharField(
|
||||
label=gettext_lazy('Aktuelles Passwort'),
|
||||
strip=False,
|
||||
widget=forms.PasswordInput(attrs={'autocomplete': 'current-password'}),
|
||||
)
|
||||
verification_code = forms.CharField(
|
||||
label=gettext_lazy('TOTP-Code'),
|
||||
max_length=12,
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'autocomplete': 'one-time-code', 'inputmode': 'numeric'}),
|
||||
)
|
||||
recovery_code = forms.CharField(
|
||||
label=gettext_lazy('Recovery-Code'),
|
||||
max_length=32,
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'autocomplete': 'one-time-code'}),
|
||||
)
|
||||
|
||||
def __init__(self, *args, user=None, profile=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user = user
|
||||
self.profile = profile
|
||||
|
||||
def clean_current_password(self):
|
||||
password = self.cleaned_data.get('current_password') or ''
|
||||
if not self.user or not self.user.check_password(password):
|
||||
raise ValidationError(_('Das aktuelle Passwort ist nicht korrekt.'))
|
||||
return password
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
code = normalize_totp_token(cleaned_data.get('verification_code'))
|
||||
recovery_code = normalize_recovery_code(cleaned_data.get('recovery_code'))
|
||||
if not code and not recovery_code:
|
||||
raise ValidationError(_('Bitte geben Sie einen gültigen TOTP-Code oder Recovery-Code ein.'))
|
||||
secret = getattr(self.profile, 'totp_secret', '') or ''
|
||||
if code:
|
||||
if not secret or not verify_totp_token(secret, code, for_time=int(timezone.now().timestamp())):
|
||||
raise ValidationError(_('Der TOTP-Code ist ungültig.'))
|
||||
return cleaned_data
|
||||
if not self.profile.consume_recovery_code(recovery_code):
|
||||
raise ValidationError(_('Der Recovery-Code ist ungültig.'))
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class UserManagementCreateForm(forms.Form):
|
||||
|
||||
Reference in New Issue
Block a user