mirror of
https://github.com/pretix/pretix.git
synced 2026-03-27 16:32:26 +00:00
Compare commits
6 Commits
calendar-n
...
2fa-flowto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9da8c1f7b2 | ||
|
|
6b340682b2 | ||
|
|
0dc436067f | ||
|
|
db66c91108 | ||
|
|
3a1db55e8b | ||
|
|
57da5cbae2 |
@@ -6,9 +6,38 @@
|
||||
<h1>{% trans "Add a two-factor authentication device" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="flow_token" value="{{ flow_token }}">
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.name layout='horizontal' %}
|
||||
{% bootstrap_field form.devicetype layout='horizontal' %}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label">{% trans "Device type" %}</label>
|
||||
<div class="col-md-9">
|
||||
<div class="big-radio radio">
|
||||
<label>
|
||||
<input type="radio" value="totp" name="{{ form.devicetype.html_name }}" {% if form.devicetype.value == "totp" %}checked{% endif %}>
|
||||
<strong>{% trans "Smartphone with the Authenticator application" %}</strong><br>
|
||||
<div class="help-block">
|
||||
{% blocktrans trimmed %}
|
||||
Use your smartphone with any Time-based One-Time-Password app like freeOTP, Google Authenticator or Proton Authenticator.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="big-radio radio">
|
||||
<label>
|
||||
<input type="radio" value="webauthn" name="{{ form.devicetype.html_name }}" {% if form.devicetype.value == "webauthn" %}checked{% endif %}>
|
||||
<strong>{% trans "WebAuthn-compatible hardware token" %}</strong><br>
|
||||
<div class="help-block">
|
||||
{% blocktrans trimmed %}
|
||||
Use a hardware token like the Yubikey, or biometric authentication on iOS, macOS and Android.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Continue" %}
|
||||
|
||||
@@ -28,11 +28,6 @@
|
||||
{% trans "iOS (iTunes)" %}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://m.google.com/authenticator">
|
||||
{% trans "Blackberry (Link via Google)" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
@@ -74,14 +69,11 @@
|
||||
{% trans "Enter the displayed code here:" %}
|
||||
<form class="form form-inline" method="post" action="">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="flow_token" value="{{ flow_token }}">
|
||||
<input type="number" name="token" class="form-control" required="required">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{% trans "Continue" %}
|
||||
</button><br>
|
||||
<label>
|
||||
<input type="checkbox" name="activate" checked="checked" value="on">
|
||||
{% trans "Require second factor for future logins" %}
|
||||
</label>
|
||||
</form>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
@@ -12,13 +12,9 @@
|
||||
</p>
|
||||
<form class="form form-inline" method="post" action="" id="webauthn-form">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="flow_token" value="{{ flow_token }}">
|
||||
<input type="hidden" id="webauthn-response" name="token" class="form-control" required="required">
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="activate" checked="checked" value="on">
|
||||
{% trans "Require second factor for future logins" %}
|
||||
</label>
|
||||
</p>
|
||||
|
||||
<button class="btn btn-primary sr-only" type="submit"></button>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<h1>{% trans "Delete a two-factor authentication device" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="flow_token" value="{{ flow_token }}">
|
||||
<p>{% blocktrans trimmed with device=device.name %}
|
||||
Are you sure you want to delete the authentication device "{{ device }}"?
|
||||
{% endblocktrans %}</p>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<h1>{% trans "Disable two-factor authentication" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="flow_token" value="{{ flow_token }}">
|
||||
<p>
|
||||
{% trans "Do you really want to disable two-factor authentication?" %}
|
||||
</p>
|
||||
|
||||
@@ -1,23 +1,58 @@
|
||||
{% extends "pretixcontrol/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load icon %}
|
||||
{% block title %}{% trans "Enable two-factor authentication" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Enable two-factor authentication" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="flow_token" value="{{ flow_token }}">
|
||||
<p>
|
||||
{% trans "Do you really want to enable two-factor authentication?" %}
|
||||
</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 tokens and store them in a safe place." %}
|
||||
</p>
|
||||
{% if new_emergency_tokens %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% trans "Your emergency codes" %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
{% 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 %}
|
||||
</p>
|
||||
<ul>
|
||||
{% for code in new_emergency_tokens %}
|
||||
<li>{{ code }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" required>
|
||||
{% trans "I stored my emergency tokens in a safe place." %}
|
||||
</label>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<p>
|
||||
{% icon "info-circle" %}
|
||||
{% blocktrans trimmed with generation_date_time=static_tokens_device.created_at %}
|
||||
You generated your emergency tokens on {{ generation_date_time }}.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
<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">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Enable" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<h1>{% trans "Leave teams that require two-factor authentication" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="flow_token" value="{{ flow_token }}">
|
||||
<p>
|
||||
<strong>{% trans "Do you really want to leave the following teams?" %}</strong>
|
||||
</p>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends "pretixcontrol/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load icon %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Two-factor authentication" %}{% endblock %}
|
||||
{% block content %}
|
||||
@@ -120,7 +121,7 @@
|
||||
Delete
|
||||
</a>
|
||||
{% if d.devicetype == "totp" %}
|
||||
<span class="fa fa-mobile"></span>
|
||||
<span class="fa fa-mobile fa-lg"></span>
|
||||
{% elif d.devicetype == "webauthn" %}
|
||||
<span class="fa fa-usb"></span>
|
||||
{% elif d.devicetype == "u2f" %}
|
||||
@@ -152,19 +153,30 @@
|
||||
</p>
|
||||
{% if static_tokens_device %}
|
||||
<p>
|
||||
{% icon "info-circle" %}
|
||||
{% blocktrans trimmed with generation_date_time=static_tokens_device.created_at %}
|
||||
You generated your emergency tokens on {{ generation_date_time }}.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% else %}
|
||||
<a href="{% url "control:user.settings.2fa.regenemergency" %}" class="btn btn-default">
|
||||
<span class="fa fa-refresh"></span>
|
||||
{% trans "Generate new emergency tokens" %}
|
||||
</a>
|
||||
{% elif user.require_2fa %}
|
||||
<p>
|
||||
{% trans "You don't have any emergency tokens yet." %}
|
||||
{% icon "warning" %}
|
||||
<strong>{% trans "You don't have any emergency tokens yet." %}</strong>
|
||||
</p>
|
||||
<a href="{% url "control:user.settings.2fa.regenemergency" %}" class="btn btn-default">
|
||||
<span class="fa fa-refresh"></span>
|
||||
{% trans "Generate emergency tokens" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<p class="help-block">
|
||||
{% icon "info-circle" %}
|
||||
{% trans "Emergency tokens will be generated when you enable two-factor authentication." %}
|
||||
</p>
|
||||
{% endif %}
|
||||
<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 %}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<h1>{% trans "Regenerate emergency codes" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="flow_token" value="{{ flow_token }}">
|
||||
<p>
|
||||
{% trans "Do you really want to regenerate your emergency codes?" %}
|
||||
</p>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
{% trans "Change login email address" %}
|
||||
</h1>
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="flow_token" value="{{ flow_token }}">
|
||||
{% bootstrap_form_errors form %}
|
||||
<p class="text-muted">
|
||||
{% trans "This changes the email address used to login to your account, as well as where we send email notifications." %}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
</h1>
|
||||
<br>
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="flow_token" value="{{ flow_token }}">
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.email %}
|
||||
{% bootstrap_field form.old_pw %}
|
||||
|
||||
@@ -89,13 +89,31 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class RecentAuthenticationRequiredMixin:
|
||||
max_time = 900
|
||||
max_form_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:
|
||||
auth_is_recent = time.time() - request.session.get('pretix_auth_login_time', 0) < self.max_time
|
||||
allowed_by_token = (
|
||||
request.session.pop('pretix_reauthed_flow_token', None) == request.POST.get('flow_token', '')
|
||||
and request.session.pop('pretix_reauthed_flow_allowed_url', None) == request.get_full_path()
|
||||
and time.time() - request.session.pop('pretix_reauthed_flow_start_time', 0) < self.max_form_time
|
||||
)
|
||||
if auth_is_recent or allowed_by_token:
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
else:
|
||||
return redirect(reverse('control:user.reauth') + '?next=' + quote(request.get_full_path()))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_flow_token(self):
|
||||
self.request.session['pretix_reauthed_flow_allowed_url'] = self.request.get_full_path()
|
||||
self.request.session['pretix_reauthed_flow_token'] = get_random_string(22)
|
||||
self.request.session['pretix_reauthed_flow_start_time'] = time.time()
|
||||
return self.request.session['pretix_reauthed_flow_token']
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data()
|
||||
ctx['flow_token'] = self.get_flow_token()
|
||||
return ctx
|
||||
|
||||
|
||||
class ReauthView(TemplateView):
|
||||
@@ -283,6 +301,7 @@ class UserHistoryView(ListView):
|
||||
|
||||
|
||||
class User2FAMainView(RecentAuthenticationRequiredMixin, TemplateView):
|
||||
max_time = 7200
|
||||
template_name = 'pretixcontrol/user/2fa_main.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@@ -465,25 +484,15 @@ class User2FADeviceConfirmWebAuthnView(RecentAuthenticationRequiredMixin, Templa
|
||||
notices = [
|
||||
_('A new two-factor authentication device has been added to your account.')
|
||||
]
|
||||
activate = request.POST.get('activate', '')
|
||||
if activate == 'on' and not self.request.user.require_2fa:
|
||||
self.request.user.require_2fa = True
|
||||
self.request.user.save()
|
||||
self.request.user.log_action('pretix.user.settings.2fa.enabled', user=self.request.user)
|
||||
notices.append(
|
||||
_('Two-factor authentication has been enabled.')
|
||||
)
|
||||
self.request.user.send_security_notice(notices)
|
||||
self.request.user.update_session_token()
|
||||
update_session_auth_hash(self.request, self.request.user)
|
||||
|
||||
note = ''
|
||||
if not self.request.user.require_2fa:
|
||||
note = ' ' + str(_('Please note that you still need to enable two-factor authentication for your '
|
||||
'account using the buttons below to make a second factor required for logging '
|
||||
'into your account.'))
|
||||
messages.success(request, str(_('The device has been verified and can now be used.')) + note)
|
||||
return redirect(reverse('control:user.settings.2fa'))
|
||||
messages.success(request, str(_('The device has been verified and can now be used.')))
|
||||
if self.request.user.require_2fa:
|
||||
return redirect(reverse('control:user.settings.2fa'))
|
||||
else:
|
||||
return redirect(reverse('control:user.settings.2fa.enable'))
|
||||
except Exception:
|
||||
messages.error(request, _('The registration could not be completed. Please try again.'))
|
||||
logger.exception('WebAuthn registration failed')
|
||||
@@ -494,6 +503,7 @@ class User2FADeviceConfirmWebAuthnView(RecentAuthenticationRequiredMixin, Templa
|
||||
|
||||
class User2FADeviceConfirmTOTPView(RecentAuthenticationRequiredMixin, TemplateView):
|
||||
template_name = 'pretixcontrol/user/2fa_confirm_totp.html'
|
||||
max_form_time = 7200 # this should have effectively no timeout, as the user might need to download the 2fa app first
|
||||
|
||||
@cached_property
|
||||
def device(self):
|
||||
@@ -514,7 +524,6 @@ class User2FADeviceConfirmTOTPView(RecentAuthenticationRequiredMixin, TemplateVi
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
token = request.POST.get('token', '')
|
||||
activate = request.POST.get('activate', '')
|
||||
if self.device.verify_token(token):
|
||||
self.device.confirmed = True
|
||||
self.device.save()
|
||||
@@ -526,24 +535,15 @@ class User2FADeviceConfirmTOTPView(RecentAuthenticationRequiredMixin, TemplateVi
|
||||
notices = [
|
||||
_('A new two-factor authentication device has been added to your account.')
|
||||
]
|
||||
if activate == 'on' and not self.request.user.require_2fa:
|
||||
self.request.user.require_2fa = True
|
||||
self.request.user.save()
|
||||
self.request.user.log_action('pretix.user.settings.2fa.enabled', user=self.request.user)
|
||||
notices.append(
|
||||
_('Two-factor authentication has been enabled.')
|
||||
)
|
||||
self.request.user.send_security_notice(notices)
|
||||
self.request.user.update_session_token()
|
||||
update_session_auth_hash(self.request, self.request.user)
|
||||
|
||||
note = ''
|
||||
if not self.request.user.require_2fa:
|
||||
note = ' ' + str(_('Please note that you still need to enable two-factor authentication for your '
|
||||
'account using the buttons below to make a second factor required for logging '
|
||||
'into your account.'))
|
||||
messages.success(request, str(_('The device has been verified and can now be used.')) + note)
|
||||
return redirect(reverse('control:user.settings.2fa'))
|
||||
messages.success(request, str(_('The device has been verified and can now be used.')))
|
||||
if self.request.user.require_2fa:
|
||||
return redirect(reverse('control:user.settings.2fa'))
|
||||
else:
|
||||
return redirect(reverse('control:user.settings.2fa.enable'))
|
||||
else:
|
||||
messages.error(request, _('The code you entered was not valid. If this problem persists, please check '
|
||||
'that the date and time of your phone are configured correctly.'))
|
||||
@@ -576,6 +576,7 @@ class User2FALeaveTeamsView(RecentAuthenticationRequiredMixin, TemplateView):
|
||||
|
||||
class User2FAEnableView(RecentAuthenticationRequiredMixin, TemplateView):
|
||||
template_name = 'pretixcontrol/user/2fa_enable.html'
|
||||
max_form_time = 7200 # this should have effectively no timeout, as the user might take some time to print out their emergency codes, and they would become invalid in case of a timeout
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not any(dt.objects.filter(user=self.request.user, confirmed=True) for dt in REAL_DEVICE_TYPES):
|
||||
@@ -584,14 +585,39 @@ class User2FAEnableView(RecentAuthenticationRequiredMixin, TemplateView):
|
||||
return redirect(reverse('control:user.settings.2fa'))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
new_tokens = None
|
||||
try:
|
||||
static_tokens_device = StaticDevice.objects.get(user=self.request.user, name='emergency')
|
||||
except StaticDevice.MultipleObjectsReturned:
|
||||
static_tokens_device = StaticDevice.objects.filter(
|
||||
user=self.request.user, name='emergency'
|
||||
).first()
|
||||
except StaticDevice.DoesNotExist:
|
||||
static_tokens_device = None
|
||||
|
||||
new_tokens = [get_random_string(length=12, allowed_chars='1234567890') for _ in range(10)]
|
||||
request.session['pretix_2fa_new_emergency_tokens'] = new_tokens
|
||||
return super().get(request, *args, new_emergency_tokens=new_tokens, static_tokens_device=static_tokens_device, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
notices = [
|
||||
_('Two-factor authentication has been enabled.')
|
||||
]
|
||||
if 'pretix_2fa_new_emergency_tokens' in request.session:
|
||||
d = StaticDevice.objects.create(user=self.request.user, name='emergency')
|
||||
for code in request.session['pretix_2fa_new_emergency_tokens']:
|
||||
d.token_set.create(token=code)
|
||||
self.request.user.log_action('pretix.user.settings.2fa.regenemergency', user=self.request.user)
|
||||
notices += [
|
||||
_('Your two-factor emergency codes have been regenerated.')
|
||||
]
|
||||
del request.session['pretix_2fa_new_emergency_tokens']
|
||||
self.request.user.require_2fa = True
|
||||
self.request.user.save()
|
||||
self.request.user.log_action('pretix.user.settings.2fa.enabled', user=self.request.user)
|
||||
messages.success(request, _('Two-factor authentication is now enabled for your account.'))
|
||||
self.request.user.send_security_notice([
|
||||
_('Two-factor authentication has been enabled.')
|
||||
])
|
||||
self.request.user.send_security_notice(notices)
|
||||
self.request.user.update_session_token()
|
||||
update_session_auth_hash(self.request, self.request.user)
|
||||
return redirect(reverse('control:user.settings.2fa'))
|
||||
|
||||
Reference in New Issue
Block a user