2FA: Implement emergency tokens

This commit is contained in:
Raphael Michel
2016-10-08 15:16:42 +02:00
parent 68a9f98f23
commit 582d9dca25
6 changed files with 72 additions and 2 deletions

View File

@@ -11,7 +11,7 @@
</p>
<p>
{% 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." %}
</p>
<div class="form-group submit-group">
<a href="{% url "control:user.settings.2fa" %}" class="btn btn-default btn-cancel">

View File

@@ -71,4 +71,24 @@
</li>
</ul>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Emergency tokens" %}</h3>
</div>
<div class="panel-body">
<p>
{% 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." %}
</p>
<p>{% trans "Unused tokens:" %}</p>
<ul>
{% for t in static_tokens %}
<li><code>{{ t.token }}</code></li>
{% endfor %}
</ul>
<a href="{% url "control:user.settings.2fa.regenemergency" %}" class="btn btn-default">
<span class="fa fa-refresh"></span>
{% trans "Generate new emergency tokens" %}
</a>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends "pretixcontrol/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Regenerate emergency codes" %}{% endblock %}
{% block content %}
<h1>{% trans "Regenerate emergency codes" %}</h1>
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
<p>
{% trans "Do you really want to regenerate your emergency codes?" %}
</p>
<p>
{% trans "The old codes will no longer work." %}
</p>
<div class="form-group submit-group">
<a href="{% url "control:user.settings.2fa" %}" class="btn btn-default btn-cancel">
{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-danger btn-save">
{% trans "Regenerate" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -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<device>[0-9]+)/confirm', user.User2FADeviceConfirmTOTPView.as_view(),
name='user.settings.2fa.confirm.totp'),
url(r'^settings/2fa/(?P<devicetype>[^/]+)/(?P<device>[0-9]+)/delete', user.User2FADeviceDeleteView.as_view(),

View File

@@ -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']

View File

@@ -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'))