diff --git a/src/pretix/control/templates/pretixcontrol/user/2fa_main.html b/src/pretix/control/templates/pretixcontrol/user/2fa_main.html index 308c57851..0f0899834 100644 --- a/src/pretix/control/templates/pretixcontrol/user/2fa_main.html +++ b/src/pretix/control/templates/pretixcontrol/user/2fa_main.html @@ -144,14 +144,23 @@

- {% 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." %} + {% blocktrans trimmed %} + If you lose access to your devices, you can use one of your emergency tokens 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. + {% endblocktrans %}

-

{% trans "Unused tokens:" %}

- + {% if static_tokens_device %} +

+ {% blocktrans trimmed with generation_date_time=static_tokens_device.created_at %} + You generated your emergency tokens on {{ generation_date_time }}. + {% endblocktrans %} +

+ {% else %} +

+ {% trans "You don't have any emergency tokens yet." %} +

+ {% endif %} {% trans "Generate new emergency tokens" %} diff --git a/src/pretix/control/views/user.py b/src/pretix/control/views/user.py index 89364f1f2..6abf17d3c 100644 --- a/src/pretix/control/views/user.py +++ b/src/pretix/control/views/user.py @@ -49,12 +49,14 @@ from django.db import transaction from django.shortcuts import get_object_or_404, redirect from django.urls import reverse from django.utils.crypto import get_random_string +from django.utils.decorators import method_decorator from django.utils.functional import cached_property from django.utils.html import format_html from django.utils.http import url_has_allowed_host_and_scheme from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ from django.views import View +from django.views.decorators.cache import never_cache from django.views.generic import FormView, ListView, TemplateView, UpdateView from django_otp.plugins.otp_static.models import StaticDevice from django_otp.plugins.otp_totp.models import TOTPDevice @@ -85,8 +87,9 @@ logger = logging.getLogger(__name__) class RecentAuthenticationRequiredMixin: - max_time = 3600 + max_time = 900 + @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): tdelta = time.time() - request.session.get('pretix_auth_login_time', 0) if tdelta > self.max_time: @@ -289,16 +292,13 @@ class User2FAMainView(RecentAuthenticationRequiredMixin, TemplateView): ctx = super().get_context_data() try: - ctx['static_tokens'] = StaticDevice.objects.get(user=self.request.user, name='emergency').token_set.all() + ctx['static_tokens_device'] = StaticDevice.objects.get(user=self.request.user, name='emergency') except StaticDevice.MultipleObjectsReturned: - ctx['static_tokens'] = StaticDevice.objects.filter( + ctx['static_tokens_device'] = StaticDevice.objects.filter( user=self.request.user, name='emergency' - ).first().token_set.all() + ).first() 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['static_tokens_device'] = None ctx['devices'] = [] for dt in REAL_DEVICE_TYPES: @@ -631,7 +631,8 @@ class User2FARegenerateEmergencyView(RecentAuthenticationRequiredMixin, Template self.request.user.update_session_token() update_session_auth_hash(self.request, self.request.user) 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.')) + 'place in case you lose access to your devices. You will not be able to view them ' + 'again here.\n\nYour emergency codes:\n- ' + '\n- '.join(t.token for t in d.token_set.all()))) return redirect(reverse('control:user.settings.2fa')) diff --git a/src/tests/control/test_user.py b/src/tests/control/test_user.py index 95d9023a5..68a14d9dc 100644 --- a/src/tests/control/test_user.py +++ b/src/tests/control/test_user.py @@ -339,13 +339,17 @@ class UserSettings2FATest(SoupTest): def test_gen_emergency(self): self.client.get('/control/settings/2fa/') + assert not StaticDevice.objects.filter(user=self.user, name='emergency').exists() + + self.client.post('/control/settings/2fa/regenemergency') d = StaticDevice.objects.get(user=self.user, name='emergency') assert d.token_set.count() == 10 old_tokens = set(t.token for t in d.token_set.all()) + self.client.post('/control/settings/2fa/regenemergency') - new_tokens = set(t.token for t in d.token_set.all()) d = StaticDevice.objects.get(user=self.user, name='emergency') assert d.token_set.count() == 10 + new_tokens = set(t.token for t in d.token_set.all()) assert old_tokens != new_tokens def test_delete_u2f(self):