diff --git a/gatekeeper/forms.py b/gatekeeper/forms.py index 6d81bdc..71b893a 100644 --- a/gatekeeper/forms.py +++ b/gatekeeper/forms.py @@ -1,3 +1,4 @@ +import ipaddress import re import pyotp @@ -317,6 +318,25 @@ class GatekeeperIPAddressForm(forms.ModelForm): 'description': _('Description'), } + def clean(self): + cleaned_data = super().clean() + address = cleaned_data.get('address') + prefix_length = cleaned_data.get('prefix_length') + if address and prefix_length is not None: + try: + ip = ipaddress.ip_address(address) + max_prefix = 32 if ip.version == 4 else 128 + if prefix_length > max_prefix: + self.add_error( + 'prefix_length', + _('Prefix length for IPv%(version)d must be between 0 and %(max)d.') % { + 'version': ip.version, 'max': max_prefix, + }, + ) + except ValueError: + pass # address field validation handles invalid IPs + return cleaned_data + def __init__(self, *args, **kwargs): cancel_url = kwargs.pop('cancel_url', '#') super().__init__(*args, **kwargs) diff --git a/gatekeeper/views.py b/gatekeeper/views.py index f4053a8..0d6aaf3 100644 --- a/gatekeeper/views.py +++ b/gatekeeper/views.py @@ -255,9 +255,12 @@ def view_generate_totp_qr(request): if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists(): return HttpResponse("Access Denied", status=403) - totp_secret = request.GET.get('secret') - issuer = request.GET.get('issuer', 'wireguard_webadmin') - name = request.GET.get('name', 'Gatekeeper') + if request.method != 'POST': + return HttpResponse("Method Not Allowed", status=405) + + totp_secret = request.POST.get('secret') + issuer = request.POST.get('issuer', 'wireguard_webadmin') + name = request.POST.get('name', 'Gatekeeper') if not totp_secret: return HttpResponse("No secret provided", status=400) diff --git a/templates/gatekeeper/gatekeeper_auth_method_form.html b/templates/gatekeeper/gatekeeper_auth_method_form.html index 6c7d7bc..3d670c5 100644 --- a/templates/gatekeeper/gatekeeper_auth_method_form.html +++ b/templates/gatekeeper/gatekeeper_auth_method_form.html @@ -88,19 +88,44 @@ $('#qrCodeContainer').slideUp(); }); + var _qrObjectUrl = null; + $('#btnShowQr').click(function (e) { e.preventDefault(); + + if ($('#qrCodeContainer').is(':visible')) { + $('#qrCodeContainer').slideUp(); + return; + } + var secret = $('#id_totp_secret').val(); - var name = $('#id_name').val() || 'Gatekeeper'; + var name = $('#id_display_name').val() || 'Gatekeeper'; if (!secret) { alert("{% trans 'Please enter a TOTP Secret first to generate the QR code.' %}"); return; } - var url = '/gatekeeper/auth_method/qr/?secret=' + encodeURIComponent(secret) + '&name=' + encodeURIComponent(name); - $('#qrCodeImg').attr('src', url); - $('#qrCodeContainer').slideToggle(); + var csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value; + var formData = new FormData(); + formData.append('secret', secret); + formData.append('name', name); + formData.append('csrfmiddlewaretoken', csrfToken); + + fetch('/gatekeeper/auth_method/qr/', { method: 'POST', body: formData }) + .then(function (response) { + if (!response.ok) throw new Error('Failed'); + return response.blob(); + }) + .then(function (blob) { + if (_qrObjectUrl) { URL.revokeObjectURL(_qrObjectUrl); } + _qrObjectUrl = URL.createObjectURL(blob); + $('#qrCodeImg').attr('src', _qrObjectUrl); + $('#qrCodeContainer').slideDown(); + }) + .catch(function () { + alert("{% trans 'Error generating QR code.' %}"); + }); }); }); diff --git a/templates/gatekeeper/gatekeeper_user_form.html b/templates/gatekeeper/gatekeeper_user_form.html index 5536fa5..61bd81e 100644 --- a/templates/gatekeeper/gatekeeper_user_form.html +++ b/templates/gatekeeper/gatekeeper_user_form.html @@ -64,8 +64,16 @@ $('#qrCodeContainer').slideUp(); }); + var _qrObjectUrl = null; + $('#btnShowQr').click(function (e) { e.preventDefault(); + + if ($('#qrCodeContainer').is(':visible')) { + $('#qrCodeContainer').slideUp(); + return; + } + var secret = $('#id_totp_secret').val(); var name = $('#id_username').val() || 'Gatekeeper'; @@ -74,9 +82,26 @@ return; } - var url = '/gatekeeper/auth_method/qr/?secret=' + encodeURIComponent(secret) + '&name=' + encodeURIComponent(name); - $('#qrCodeImg').attr('src', url); - $('#qrCodeContainer').slideToggle(); + var csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value; + var formData = new FormData(); + formData.append('secret', secret); + formData.append('name', name); + formData.append('csrfmiddlewaretoken', csrfToken); + + fetch('/gatekeeper/auth_method/qr/', { method: 'POST', body: formData }) + .then(function (response) { + if (!response.ok) throw new Error('Failed'); + return response.blob(); + }) + .then(function (blob) { + if (_qrObjectUrl) { URL.revokeObjectURL(_qrObjectUrl); } + _qrObjectUrl = URL.createObjectURL(blob); + $('#qrCodeImg').attr('src', _qrObjectUrl); + $('#qrCodeContainer').slideDown(); + }) + .catch(function () { + alert("{% trans 'Error generating QR code.' %}"); + }); }); });