diff --git a/src/pretix/control/templates/pretixcontrol/user/2fa_enable.html b/src/pretix/control/templates/pretixcontrol/user/2fa_enable.html index 32a65aa770..39f41268d4 100644 --- a/src/pretix/control/templates/pretixcontrol/user/2fa_enable.html +++ b/src/pretix/control/templates/pretixcontrol/user/2fa_enable.html @@ -11,7 +11,7 @@

{% trans "You will no longer be able to log in to pretix without one of your configured devices." %} - {% trans "Please make sure to print out or copy the emergency keys and store them in a safe place." %} + {% trans "Please make sure to print out or copy the emergency tokens and store them in a safe place." %}

diff --git a/src/pretix/control/templates/pretixcontrol/user/2fa_main.html b/src/pretix/control/templates/pretixcontrol/user/2fa_main.html index cbb73dfae4..8b251e4085 100644 --- a/src/pretix/control/templates/pretixcontrol/user/2fa_main.html +++ b/src/pretix/control/templates/pretixcontrol/user/2fa_main.html @@ -71,4 +71,24 @@
+
+
+

{% trans "Emergency tokens" %}

+
+
+

+ {% trans "If you lose access to your devices, you can use one of the following keys to log in. We recommend to store them in a safe place, e.g. printed out or in a password manager. Every token can be used at most once." %} +

+

{% trans "Unused tokens:" %}

+
    + {% for t in static_tokens %} +
  • {{ t.token }}
  • + {% endfor %} +
+
+ + {% trans "Generate new emergency tokens" %} + +
+
{% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/user/2fa_regenermergency.html b/src/pretix/control/templates/pretixcontrol/user/2fa_regenermergency.html new file mode 100644 index 0000000000..88b8a491cd --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/user/2fa_regenermergency.html @@ -0,0 +1,24 @@ +{% extends "pretixcontrol/base.html" %} +{% load i18n %} +{% load bootstrap3 %} +{% block title %}{% trans "Regenerate emergency codes" %}{% endblock %} +{% block content %} +

{% trans "Regenerate emergency codes" %}

+
+ {% csrf_token %} +

+ {% trans "Do you really want to regenerate your emergency codes?" %} +

+

+ {% trans "The old codes will no longer work." %} +

+
+ + {% trans "Cancel" %} + + +
+
+{% endblock %} diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index 6665ea6f3d..e7a6a239cb 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -18,6 +18,8 @@ urlpatterns = [ url(r'^settings/2fa/add$', user.User2FADeviceAddView.as_view(), name='user.settings.2fa.add'), url(r'^settings/2fa/enable', user.User2FAEnableView.as_view(), name='user.settings.2fa.enable'), url(r'^settings/2fa/disable', user.User2FADisableView.as_view(), name='user.settings.2fa.disable'), + url(r'^settings/2fa/regenemergency', user.User2FARegenerateEmergencyView.as_view(), + name='user.settings.2fa.regenemergency'), url(r'^settings/2fa/totp/(?P[0-9]+)/confirm', user.User2FADeviceConfirmTOTPView.as_view(), name='user.settings.2fa.confirm.totp'), url(r'^settings/2fa/(?P[^/]+)/(?P[0-9]+)/delete', user.User2FADeviceDeleteView.as_view(), diff --git a/src/pretix/control/views/auth.py b/src/pretix/control/views/auth.py index bb7bbef8cd..baeea7ce86 100644 --- a/src/pretix/control/views/auth.py +++ b/src/pretix/control/views/auth.py @@ -225,7 +225,8 @@ class Login2FAView(TemplateView): return super().dispatch(request, *args, **kwargs) def post(self, request, *args, **kwargs): - if match_token(self.user, request.POST.get('token', '')): + token = request.POST.get('token', '').strip().replace(' ', '') + if match_token(self.user, token): auth_login(request, self.user) del request.session['pretix_auth_2fa_user'] del request.session['pretix_auth_2fa_time'] diff --git a/src/pretix/control/views/user.py b/src/pretix/control/views/user.py index 26fc535540..67522dfb30 100644 --- a/src/pretix/control/views/user.py +++ b/src/pretix/control/views/user.py @@ -6,9 +6,11 @@ from django.contrib import messages from django.contrib.auth import update_session_auth_hash from django.core.urlresolvers import reverse from django.shortcuts import get_object_or_404, redirect +from django.utils.crypto import get_random_string from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from django.views.generic import FormView, TemplateView, UpdateView +from django_otp.plugins.otp_static.models import StaticDevice from django_otp.plugins.otp_totp.models import TOTPDevice from pretix.base.forms.user import User2FADeviceAddForm, UserSettingsForm @@ -51,6 +53,14 @@ class User2FAMainView(TemplateView): def get_context_data(self, **kwargs): ctx = super().get_context_data() + try: + ctx['static_tokens'] = StaticDevice.objects.get(user=self.request.user, name='emergency').token_set.all() + except StaticDevice.DoesNotExist: + d = StaticDevice.objects.create(user=self.request.user, name='emergency') + for i in range(10): + d.token_set.create(token=get_random_string(length=12, allowed_chars='1234567890')) + ctx['static_tokens'] = d.token_set.all() + ctx['devices'] = [] for dt in REAL_DEVICE_TYPES: objs = list(dt.objects.filter(user=self.request.user, confirmed=True)) @@ -158,3 +168,16 @@ class User2FADisableView(TemplateView): self.request.user.save() messages.success(request, _('Two-factor authentication is now disabled for your account.')) return redirect(reverse('control:user.settings.2fa')) + + +class User2FARegenerateEmergencyView(TemplateView): + template_name = 'pretixcontrol/user/2fa_regenermergency.html' + + def post(self, request, *args, **kwargs): + d = StaticDevice.objects.get(user=self.request.user, name='emergency') + d.token_set.all().delete() + for i in range(10): + d.token_set.create(token=get_random_string(length=12, allowed_chars='1234567890')) + messages.success(request, _('Your emergency codes have been newly generated. Remember to store them in a safe ' + 'place in case you lose access to your devices.')) + return redirect(reverse('control:user.settings.2fa'))