diff --git a/src/pretix/control/templates/pretixcontrol/user/2fa_delete.html b/src/pretix/control/templates/pretixcontrol/user/2fa_delete.html
new file mode 100644
index 0000000000..882b29f8ff
--- /dev/null
+++ b/src/pretix/control/templates/pretixcontrol/user/2fa_delete.html
@@ -0,0 +1,25 @@
+{% extends "pretixcontrol/base.html" %}
+{% load i18n %}
+{% load bootstrap3 %}
+{% block title %}{% trans "Delete a two-factor authentication device" %}{% endblock %}
+{% block content %}
+
{% trans "Delete a two-factor authentication device" %}
+
+{% endblock %}
diff --git a/src/pretix/control/templates/pretixcontrol/user/2fa_main.html b/src/pretix/control/templates/pretixcontrol/user/2fa_main.html
index eb9d0646de..bbb0051a5e 100644
--- a/src/pretix/control/templates/pretixcontrol/user/2fa_main.html
+++ b/src/pretix/control/templates/pretixcontrol/user/2fa_main.html
@@ -48,7 +48,11 @@
{% for d in devices %}
-
- {% if d.devicetype == "totp" %}
+
+ Delete
+
+ {% if d.devicetype == "totp" %}
{% endif %}
{{ d.name }}
diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py
index a9ade95bb5..41c2d4b849 100644
--- a/src/pretix/control/urls.py
+++ b/src/pretix/control/urls.py
@@ -17,6 +17,8 @@ urlpatterns = [
url(r'^settings/2fa/add$', user.User2FADeviceAddView.as_view(), name='user.settings.2fa.add'),
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(),
+ name='user.settings.2fa.delete'),
url(r'^organizers/$', organizer.OrganizerList.as_view(), name='organizers'),
url(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'),
url(r'^organizer/(?P[^/]+)/edit$', organizer.OrganizerUpdate.as_view(), name='organizer.edit'),
diff --git a/src/pretix/control/views/user.py b/src/pretix/control/views/user.py
index 07ce8d3e19..1fe7ab7aa3 100644
--- a/src/pretix/control/views/user.py
+++ b/src/pretix/control/views/user.py
@@ -15,6 +15,9 @@ from pretix.base.forms.user import User2FADeviceAddForm, UserSettingsForm
from pretix.base.models import User
+REAL_DEVICE_TYPES = (TOTPDevice,)
+
+
class UserSettings(UpdateView):
model = User
form_class = UserSettingsForm
@@ -49,7 +52,7 @@ class User2FAMainView(TemplateView):
ctx = super().get_context_data()
ctx['devices'] = []
- for dt in (TOTPDevice,):
+ for dt in REAL_DEVICE_TYPES:
objs = list(dt.objects.filter(user=self.request.user, confirmed=True))
for obj in objs:
if dt == TOTPDevice:
@@ -74,6 +77,28 @@ class User2FADeviceAddView(FormView):
}))
+class User2FADeviceDeleteView(TemplateView):
+ template_name = 'pretixcontrol/user/2fa_delete.html'
+
+ @cached_property
+ def device(self):
+ if self.kwargs['devicetype'] == 'totp':
+ return get_object_or_404(TOTPDevice, user=self.request.user, pk=self.kwargs['device'], confirmed=True)
+
+ def get_context_data(self, **kwargs):
+ ctx = super().get_context_data()
+ ctx['device'] = self.device
+ return ctx
+
+ def post(self, request, *args, **kwargs):
+ self.device.delete()
+ if not any(dt.objects.filter(user=self.request.user, confirmed=True) for dt in REAL_DEVICE_TYPES):
+ self.request.user.require_2fa = False
+ self.request.user.save()
+ messages.success(request, _('The device has been removed.'))
+ return redirect(reverse('control:user.settings.2fa'))
+
+
class User2FADeviceConfirmTOTPView(TemplateView):
template_name = 'pretixcontrol/user/2fa_confirm_totp.html'