add 'Global TOTP Before Authentication' option and update form layouts

This commit is contained in:
Eduardo Silva
2026-03-14 08:52:31 -03:00
parent 72e3e2e1ca
commit 06426b3852
4 changed files with 55 additions and 30 deletions

View File

@@ -27,20 +27,20 @@ class GatekeeperUserForm(forms.ModelForm):
self.helper = FormHelper() self.helper = FormHelper()
self.helper.layout = Layout( self.helper.layout = Layout(
Div( Div(
Div('username', css_class='col-md-6'), Div('username', css_class='col-xl-6'),
Div('email', css_class='col-md-6'), Div('email', css_class='col-xl-6'),
css_class='row' css_class='row'
), ),
Div( Div(
Div(Field('password', type='password'), css_class='col-md-6'), Div(Field('password', type='password'), css_class='col-xl-6'),
Div('totp_secret', css_class='col-md-6'), Div('totp_secret', css_class='col-xl-6'),
css_class='row' css_class='row'
), ),
Div( Div(
Div( Div(
Submit('submit', _('Save'), css_class='btn btn-primary'), Submit('submit', _('Save'), css_class='btn btn-primary'),
HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'), HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
css_class='col-md-12' css_class='col-xl-12'
), ),
css_class='row' css_class='row'
) )
@@ -63,18 +63,18 @@ class GatekeeperGroupForm(forms.ModelForm):
self.helper = FormHelper() self.helper = FormHelper()
self.helper.layout = Layout( self.helper.layout = Layout(
Div( Div(
Div('name', css_class='col-md-12'), Div('name', css_class='col-xl-12'),
css_class='row' css_class='row'
), ),
Div( Div(
Div('users', css_class='col-md-12'), Div('users', css_class='col-xl-12'),
css_class='row' css_class='row'
), ),
Div( Div(
Div( Div(
Submit('submit', _('Save'), css_class='btn btn-primary'), Submit('submit', _('Save'), css_class='btn btn-primary'),
HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'), HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
css_class='col-md-12' css_class='col-xl-12'
), ),
css_class='row' css_class='row'
) )
@@ -92,13 +92,14 @@ class AuthMethodForm(forms.ModelForm):
class Meta: class Meta:
model = AuthMethod model = AuthMethod
fields = [ fields = [
'name', 'auth_type', 'totp_secret', 'name', 'auth_type', 'totp_secret', 'totp_before_auth',
'oidc_provider', 'oidc_client_id', 'oidc_client_secret' 'oidc_provider', 'oidc_client_id', 'oidc_client_secret'
] ]
labels = { labels = {
'name': _('Name'), 'name': _('Name'),
'auth_type': _('Authentication Type'), 'auth_type': _('Authentication Type'),
'totp_secret': _('Global TOTP Secret'), 'totp_secret': _('Global TOTP Secret'),
'totp_before_auth': _('Global TOTP Before Authentication'),
'oidc_provider': _('OIDC Provider URL'), 'oidc_provider': _('OIDC Provider URL'),
'oidc_client_id': _('OIDC Client ID'), 'oidc_client_id': _('OIDC Client ID'),
'oidc_client_secret': _('OIDC Client Secret'), 'oidc_client_secret': _('OIDC Client Secret'),
@@ -114,29 +115,30 @@ class AuthMethodForm(forms.ModelForm):
self.helper = FormHelper() self.helper = FormHelper()
self.helper.layout = Layout( self.helper.layout = Layout(
Div( Div(
Div('name', css_class='col-md-6'), Div('name', css_class='col-xl-6'),
Div('auth_type', css_class='col-md-6'), Div('auth_type', css_class='col-xl-6'),
css_class='row auth-type-group' css_class='row auth-type-group'
), ),
Div( Div(
Div('totp_secret', css_class='col-md-6'), Div('totp_secret', css_class='col-xl-6'),
Div('totp_pin', css_class='col-md-6'), Div('totp_pin', css_class='col-xl-6'),
Div('totp_before_auth', css_class='col-xl-12'),
css_class='row totp-group' css_class='row totp-group'
), ),
Div( Div(
Div('oidc_provider', css_class='col-md-12'), Div('oidc_provider', css_class='col-xl-12'),
css_class='row oidc-group' css_class='row oidc-group'
), ),
Div( Div(
Div('oidc_client_id', css_class='col-md-6'), Div('oidc_client_id', css_class='col-xl-6'),
Div('oidc_client_secret', css_class='col-md-6'), Div('oidc_client_secret', css_class='col-xl-6'),
css_class='row oidc-group' css_class='row oidc-group'
), ),
Div( Div(
Div( Div(
Submit('submit', _('Save'), css_class='btn btn-primary'), Submit('submit', _('Save'), css_class='btn btn-primary'),
HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'), HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
css_class='col-md-12' css_class='col-xl-12'
), ),
css_class='row' css_class='row'
) )
@@ -206,7 +208,7 @@ class GatekeeperIPAddressForm(forms.ModelForm):
self.helper = FormHelper() self.helper = FormHelper()
self.helper.layout = Layout( self.helper.layout = Layout(
Div( Div(
Div('auth_method', css_class='col-md-12'), Div('auth_method', css_class='col-xl-12'),
css_class='row' css_class='row'
), ),
Div( Div(
@@ -223,7 +225,7 @@ class GatekeeperIPAddressForm(forms.ModelForm):
Div( Div(
Submit('submit', _('Save'), css_class='btn btn-primary'), Submit('submit', _('Save'), css_class='btn btn-primary'),
HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'), HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
css_class='col-md-12' css_class='col-xl-12'
), ),
css_class='row' css_class='row'
) )
@@ -245,7 +247,7 @@ class AuthMethodAllowedDomainForm(forms.ModelForm):
self.helper = FormHelper() self.helper = FormHelper()
self.helper.layout = Layout( self.helper.layout = Layout(
Div( Div(
Div('auth_method', css_class='col-md-6'), Div('auth_method', css_class='col-xl-6'),
Div(PrependedText('domain', '@'), css_class='col-xl-6'), Div(PrependedText('domain', '@'), css_class='col-xl-6'),
css_class='row' css_class='row'
), ),
@@ -253,7 +255,7 @@ class AuthMethodAllowedDomainForm(forms.ModelForm):
Div( Div(
Submit('submit', _('Save'), css_class='btn btn-primary'), Submit('submit', _('Save'), css_class='btn btn-primary'),
HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'), HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
css_class='col-md-12' css_class='col-xl-12'
), ),
css_class='row' css_class='row'
) )
@@ -276,15 +278,15 @@ class AuthMethodAllowedEmailForm(forms.ModelForm):
self.helper = FormHelper() self.helper = FormHelper()
self.helper.layout = Layout( self.helper.layout = Layout(
Div( Div(
Div('auth_method', css_class='col-md-6'), Div('auth_method', css_class='col-xl-6'),
Div('email', css_class='col-md-6'), Div('email', css_class='col-xl-6'),
css_class='row' css_class='row'
), ),
Div( Div(
Div( Div(
Submit('submit', _('Save'), css_class='btn btn-primary'), Submit('submit', _('Save'), css_class='btn btn-primary'),
HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'), HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
css_class='col-md-12' css_class='col-xl-12'
), ),
css_class='row' css_class='row'
) )

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.12 on 2026-03-14 11:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gatekeeper', '0004_alter_gatekeeperipaddress_prefix_length'),
]
operations = [
migrations.AddField(
model_name='authmethod',
name='totp_before_auth',
field=models.BooleanField(default=False),
),
]

View File

@@ -15,6 +15,7 @@ class AuthMethod(models.Model):
# TOTP-specific fields # TOTP-specific fields
totp_secret = models.CharField(max_length=255, blank=True, help_text=_("Shared/global TOTP secret key")) totp_secret = models.CharField(max_length=255, blank=True, help_text=_("Shared/global TOTP secret key"))
totp_before_auth = models.BooleanField(default=False)
# OIDC-specific fields # OIDC-specific fields
oidc_provider = models.CharField(max_length=64, blank=True) oidc_provider = models.CharField(max_length=64, blank=True)

View File

@@ -173,13 +173,17 @@ def view_manage_auth_method(request):
form_description = { form_description = {
'size': '', 'size': '',
'content': _(''' 'content': _('''
<h5>Authentication Types</h5> <h4>Authentication Types</h4>
<p>Select how users will authenticate through this method.</p> <p>Select how users will authenticate through this method.</p>
<ul>
<li><strong>Local Password</strong>: Users will authenticate using a standard username and password stored locally. Only one of this type can be created.</li> <h5>Local Password</h5>
<li><strong>TOTP (Time-Based One-Time Password)</strong>: Users will need to enter a rotating token from an authenticator app. Requires setting a Global TOTP Secret.</li> <p>Users will authenticate using a standard username and password stored locally. Only one of this type can be created.</p>
<li><strong>OIDC (OpenID Connect)</strong>: Users will authenticate via an external identity provider (like Keycloak, Google, or Authelia). Requires Provider URL, Client ID, and Client Secret.</li>
</ul> <h5>OIDC (OpenID Connect)</h5>
<p>Users will authenticate via an external identity provider (like Keycloak, Google, or Authelia). Requires Provider URL, Client ID, and Client Secret.</p>
<h5>TOTP (Time-Based One-Time Password)</h5>
<p>Users will need to enter a rotating token from an authenticator app. Requires setting a Global TOTP Secret. <br>If <strong>Global TOTP Before Authentication</strong> is enabled, the PIN is required before the username and password to help combat bruteforce attacks.</p>
''') ''')
} }