mirror of
https://github.com/eduardogsilva/wireguard_webadmin.git
synced 2026-03-17 22:36:17 +00:00
add password and TOTP PIN fields to user form with validation and QR code generation
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import re
|
||||
|
||||
import pyotp
|
||||
from argon2 import PasswordHasher
|
||||
from crispy_forms.bootstrap import PrependedText
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout, Submit, HTML, Div, Field
|
||||
from crispy_forms.layout import Layout, Submit, HTML, Div
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@@ -10,13 +13,30 @@ from gatekeeper.models import GatekeeperUser, GatekeeperGroup, AuthMethod, AuthM
|
||||
|
||||
|
||||
class GatekeeperUserForm(forms.ModelForm):
|
||||
password = forms.CharField(
|
||||
label=_('Password'),
|
||||
required=False,
|
||||
widget=forms.PasswordInput(render_value=False),
|
||||
help_text=_('Minimum 8 characters, with at least one uppercase letter, one lowercase letter, and one number.'),
|
||||
)
|
||||
password_confirm = forms.CharField(
|
||||
label=_('Confirm Password'),
|
||||
required=False,
|
||||
widget=forms.PasswordInput(render_value=False),
|
||||
)
|
||||
totp_pin = forms.CharField(
|
||||
label=_('TOTP Validation PIN'),
|
||||
max_length=6,
|
||||
required=False,
|
||||
help_text=_('Enter a 6-digit PIN generated by your authenticator app to validate the secret.'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = GatekeeperUser
|
||||
fields = ['username', 'email', 'password', 'totp_secret']
|
||||
fields = ['username', 'email', 'totp_secret']
|
||||
labels = {
|
||||
'username': _('Username'),
|
||||
'email': _('Email'),
|
||||
'password': _('Password'),
|
||||
'totp_secret': _('TOTP Secret'),
|
||||
}
|
||||
|
||||
@@ -24,6 +44,10 @@ class GatekeeperUserForm(forms.ModelForm):
|
||||
cancel_url = kwargs.pop('cancel_url', '#')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
is_edit = bool(self.instance and self.instance.pk)
|
||||
self.fields['password'].required = not is_edit
|
||||
self.fields['password_confirm'].required = not is_edit
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.layout = Layout(
|
||||
Div(
|
||||
@@ -32,8 +56,13 @@ class GatekeeperUserForm(forms.ModelForm):
|
||||
css_class='row'
|
||||
),
|
||||
Div(
|
||||
Div(Field('password', type='password'), css_class='col-xl-6'),
|
||||
Div('password', css_class='col-xl-6'),
|
||||
Div('password_confirm', css_class='col-xl-6'),
|
||||
css_class='row'
|
||||
),
|
||||
Div(
|
||||
Div('totp_secret', css_class='col-xl-6'),
|
||||
Div('totp_pin', css_class='col-xl-6'),
|
||||
css_class='row'
|
||||
),
|
||||
Div(
|
||||
@@ -46,6 +75,52 @@ class GatekeeperUserForm(forms.ModelForm):
|
||||
)
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
password = cleaned_data.get('password')
|
||||
password_confirm = cleaned_data.get('password_confirm')
|
||||
totp_secret = cleaned_data.get('totp_secret')
|
||||
is_new = not (self.instance and self.instance.pk)
|
||||
|
||||
if password or is_new:
|
||||
if not password:
|
||||
self.add_error('password', _('Password is required.'))
|
||||
else:
|
||||
if len(password) < 8:
|
||||
self.add_error('password', _('Password must be at least 8 characters long.'))
|
||||
elif not re.search(r'[a-z]', password):
|
||||
self.add_error('password', _('Password must contain at least one lowercase letter.'))
|
||||
elif not re.search(r'[A-Z]', password):
|
||||
self.add_error('password', _('Password must contain at least one uppercase letter.'))
|
||||
elif not re.search(r'[0-9]', password):
|
||||
self.add_error('password', _('Password must contain at least one number.'))
|
||||
elif password != password_confirm:
|
||||
self.add_error('password_confirm', _('Passwords do not match.'))
|
||||
|
||||
if totp_secret:
|
||||
totp_pin = cleaned_data.get('totp_pin')
|
||||
if not totp_pin:
|
||||
self.add_error('totp_pin', _('Please provide a PIN to validate the TOTP secret.'))
|
||||
else:
|
||||
try:
|
||||
totp = pyotp.TOTP(totp_secret)
|
||||
if not totp.verify(totp_pin):
|
||||
self.add_error('totp_pin', _('Invalid TOTP PIN.'))
|
||||
except Exception:
|
||||
self.add_error('totp_secret', _('Invalid TOTP secret format. Must be a valid Base32 string.'))
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def save(self, commit=True):
|
||||
user = super().save(commit=False)
|
||||
password = self.cleaned_data.get('password')
|
||||
if password:
|
||||
ph = PasswordHasher()
|
||||
user.password = ph.hash(password)
|
||||
if commit:
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
class GatekeeperGroupForm(forms.ModelForm):
|
||||
class Meta:
|
||||
|
||||
Reference in New Issue
Block a user