diff --git a/src/pretix/control/templates/pretixcontrol/user/reauth.html b/src/pretix/control/templates/pretixcontrol/user/reauth.html index b7a6447ff..4cc6297dd 100644 --- a/src/pretix/control/templates/pretixcontrol/user/reauth.html +++ b/src/pretix/control/templates/pretixcontrol/user/reauth.html @@ -1,9 +1,10 @@ {% extends "pretixcontrol/auth/base.html" %} {% load i18n %} {% load bootstrap3 %} +{% load compress %} +{% load staticfiles %} {% block content %} -
- + {% csrf_token %}

{% trans "Welcome back!" %}

@@ -17,6 +18,15 @@ + {% if jsondata %} +

+ {% trans "U2F failed. Check that the correct authentication device is correctly plugged in." %} +
+

+ + {% trans "Alternatively, you can use your U2F device." %} +

+ {% endif %}
+ {% if jsondata %} + + {% endif %} + {% compress js %} + + + + {% endcompress %}
{% endblock %} diff --git a/src/pretix/control/views/user.py b/src/pretix/control/views/user.py index 44f375683..a1d1066b4 100644 --- a/src/pretix/control/views/user.py +++ b/src/pretix/control/views/user.py @@ -20,6 +20,7 @@ from django_otp.plugins.otp_static.models import StaticDevice from django_otp.plugins.otp_totp.models import TOTPDevice from u2flib_server import u2f from u2flib_server.jsapi import DeviceRegistration +from u2flib_server.utils import rand_bytes from pretix.base.forms.user import User2FADeviceAddForm, UserSettingsForm from pretix.base.models import Event, NotificationSetting, U2FDevice, User @@ -48,9 +49,27 @@ class RecentAuthenticationRequiredMixin: class ReauthView(TemplateView): template_name = 'pretixcontrol/user/reauth.html' + @property + def app_id(self): + return get_u2f_appid(self.request) + def post(self, request, *args, **kwargs): password = request.POST.get("password", "") - if request.user.check_password(password): + valid = False + + if '_u2f_challenge' in self.request.session and password.startswith('{'): + devices = [DeviceRegistration.wrap(device.json_data) + for device in U2FDevice.objects.filter(confirmed=True, user=self.request.user)] + challenge = self.request.session.pop('_u2f_challenge') + try: + u2f.verify_authenticate(devices, challenge, password, [self.app_id]) + valid = True + except Exception: + logger.exception('U2F login failed') + + valid = valid or request.user.check_password(password) + + if valid: t = int(time.time()) request.session['pretix_auth_login_time'] = t request.session['pretix_auth_last_used'] = t @@ -61,6 +80,22 @@ class ReauthView(TemplateView): messages.error(request, _('The password you entered was invalid, please try again.')) return self.get(request, *args, **kwargs) + def get_context_data(self, **kwargs): + ctx = super().get_context_data() + + devices = [DeviceRegistration.wrap(device.json_data) + for device in U2FDevice.objects.filter(confirmed=True, user=self.request.user)] + if devices: + challenge = u2f.start_authenticate(devices, challenge=rand_bytes(32)) + self.request.session['_u2f_challenge'] = challenge.json + ctx['jsondata'] = challenge.json + else: + if '_u2f_challenge' in self.request.session: + del self.request.session['_u2f_challenge'] + ctx['jsondata'] = None + + return ctx + class UserSettings(UpdateView): model = User diff --git a/src/pretix/static/pretixbase/scss/_theme.scss b/src/pretix/static/pretixbase/scss/_theme.scss index fbe3ddf06..8f4a6adf9 100644 --- a/src/pretix/static/pretixbase/scss/_theme.scss +++ b/src/pretix/static/pretixbase/scss/_theme.scss @@ -104,6 +104,9 @@ -moz-osx-font-smoothing: grayscale; } } +.sr-only.alert::before { + background: none !important; +} .alert-success::before { background: $state-success-border; content: "\f00c"; diff --git a/src/pretix/static/pretixcontrol/js/ui/u2f.js b/src/pretix/static/pretixcontrol/js/ui/u2f.js index ae62c8649..7dc7c7856 100644 --- a/src/pretix/static/pretixcontrol/js/ui/u2f.js +++ b/src/pretix/static/pretixcontrol/js/ui/u2f.js @@ -31,7 +31,7 @@ $(function () { if (data.errorCode && data.errorCode != 5) { $("#u2f-error").removeClass("sr-only"); } else { - $('#u2f-response').val(JSON.stringify(data)); + $('#u2f-response, #id_password').val(JSON.stringify(data)); $('#u2f-form').submit(); } }, 300);