forked from CGM_Public/pretix_original
Basic password recovery (#5)
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
{% load i18n %}{% blocktrans with event=event.name url=url %}Hello,
|
||||||
|
|
||||||
|
you requested a new password. Please go to the following page to reset your password:
|
||||||
|
|
||||||
|
{{ url }}
|
||||||
|
|
||||||
|
Best regards,
|
||||||
|
Your {{ event }} team
|
||||||
|
{% endblocktrans %}
|
||||||
20
src/pretix/presale/templates/pretixpresale/event/forgot.html
Normal file
20
src/pretix/presale/templates/pretixpresale/event/forgot.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{% extends "pretixpresale/event/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% block title %}{% trans "Password recovery" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>{% trans "Password recovery" %}</h2>
|
||||||
|
<form class="form-horizontal" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form_errors form type='all' layout='inline' %}
|
||||||
|
{% bootstrap_field form.username layout="horizontal" %}
|
||||||
|
<input type="hidden" name="form" value="login" />
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="submit-group col-md-offset-2 col-md-4 text-right">
|
||||||
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
|
{% trans "Send recovery information" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -24,6 +24,9 @@
|
|||||||
<input type="hidden" name="form" value="login" />
|
<input type="hidden" name="form" value="login" />
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="submit-group col-md-offset-2 col-md-4 text-right">
|
<div class="submit-group col-md-offset-2 col-md-4 text-right">
|
||||||
|
<a href="{% url "presale:event.forgot" event=request.event.slug organizer=request.event.organizer.slug %}" class="btn btn-link">
|
||||||
|
{% trans "Lost password?" %}
|
||||||
|
</a>
|
||||||
<button type="submit" class="btn btn-primary btn-save">
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
{% trans "Login" %}
|
{% trans "Login" %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{% extends "pretixpresale/event/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% block title %}{% trans "Password recovery" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>{% trans "Password recovery" %}</h2>
|
||||||
|
<form class="form-horizontal" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form_errors form type='all' layout='inline' %}
|
||||||
|
{% bootstrap_field form.password layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.password_repeat layout="horizontal" %}
|
||||||
|
<input type="hidden" name="form" value="login" />
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="submit-group col-md-offset-2 col-md-4 text-right">
|
||||||
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
|
{% trans "Set new password" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -25,6 +25,8 @@ urlpatterns = [
|
|||||||
url(r'^order/(?P<order>[^/]+)/download/(?P<output>[^/]+)$', pretix.presale.views.order.OrderDownload.as_view(),
|
url(r'^order/(?P<order>[^/]+)/download/(?P<output>[^/]+)$', pretix.presale.views.order.OrderDownload.as_view(),
|
||||||
name='event.order.download'),
|
name='event.order.download'),
|
||||||
url(r'^login$', pretix.presale.views.event.EventLogin.as_view(), name='event.checkout.login'),
|
url(r'^login$', pretix.presale.views.event.EventLogin.as_view(), name='event.checkout.login'),
|
||||||
|
url(r'^forgot$', pretix.presale.views.event.EventForgot.as_view(), name='event.forgot'),
|
||||||
|
url(r'^forgot/recover$', pretix.presale.views.event.EventRecover.as_view(), name='event.forgot.recover'),
|
||||||
url(r'^logout$', pretix.presale.views.event.EventLogout.as_view(), name='event.logout'),
|
url(r'^logout$', pretix.presale.views.event.EventLogout.as_view(), name='event.logout'),
|
||||||
url(r'^orders$', pretix.presale.views.event.EventOrders.as_view(), name='event.orders'),
|
url(r'^orders$', pretix.presale.views.event.EventOrders.as_view(), name='event.orders'),
|
||||||
])),
|
])),
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import json
|
import json
|
||||||
|
from django.contrib import messages
|
||||||
from django.contrib.auth import authenticate, logout
|
from django.contrib.auth import authenticate, logout
|
||||||
|
from django.core import signing
|
||||||
|
from django.core.signing import SignatureExpired, BadSignature
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.forms import Form
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.contrib.auth.forms import AuthenticationForm as BaseAuthenticationForm
|
from django.contrib.auth.forms import AuthenticationForm as BaseAuthenticationForm
|
||||||
@@ -11,6 +15,7 @@ from django.contrib.auth import login
|
|||||||
from django.views.generic import TemplateView, View
|
from django.views.generic import TemplateView, View
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from pretix.base.mail import mail
|
||||||
from pretix.base.models import User
|
from pretix.base.models import User
|
||||||
|
|
||||||
from pretix.presale.views import EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin
|
from pretix.presale.views import EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin
|
||||||
@@ -258,7 +263,7 @@ class EventLogin(EventViewMixin, TemplateView):
|
|||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
user = User.objects.create_global_user(
|
user = User.objects.create_global_user(
|
||||||
form.cleaned_data['email'], form.cleaned_data['password'],
|
form.cleaned_data['email'], form.cleaned_data['password'],
|
||||||
)
|
)
|
||||||
user = authenticate(identifier=user.identifier, password=form.cleaned_data['password'])
|
user = authenticate(identifier=user.identifier, password=form.cleaned_data['password'])
|
||||||
login(request, user)
|
login(request, user)
|
||||||
return self.redirect_to_next()
|
return self.redirect_to_next()
|
||||||
@@ -295,6 +300,216 @@ class EventLogin(EventViewMixin, TemplateView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordRecoverForm(Form):
|
||||||
|
error_messages = {
|
||||||
|
'pw_mismatch': _("Please enter the same password twice"),
|
||||||
|
}
|
||||||
|
password = forms.CharField(
|
||||||
|
label=_('Password'),
|
||||||
|
widget=forms.PasswordInput,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
password_repeat = forms.CharField(
|
||||||
|
label=_('Repeat password'),
|
||||||
|
widget=forms.PasswordInput
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
password1 = self.cleaned_data.get('password')
|
||||||
|
password2 = self.cleaned_data.get('password_repeat')
|
||||||
|
|
||||||
|
if password1 and password1 != password2:
|
||||||
|
raise forms.ValidationError(
|
||||||
|
self.error_messages['pw_mismatch'],
|
||||||
|
code='pw_mismatch',
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.cleaned_data
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordForgotForm(Form):
|
||||||
|
username = forms.CharField(
|
||||||
|
label=_('Username or E-mail'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, event, *args, **kwargs):
|
||||||
|
self.event = event
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def clean_username(self):
|
||||||
|
username = self.cleaned_data['username']
|
||||||
|
try:
|
||||||
|
self.cleaned_data['user'] = User.objects.get(
|
||||||
|
identifier=username, event__isnull=True
|
||||||
|
)
|
||||||
|
return username
|
||||||
|
except User.DoesNotExist:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self.cleaned_data['user'] = User.objects.get(
|
||||||
|
username=username, event=self.event
|
||||||
|
)
|
||||||
|
return username
|
||||||
|
except User.DoesNotExist:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self.cleaned_data['user'] = User.objects.get(
|
||||||
|
email=username, event=self.event
|
||||||
|
)
|
||||||
|
return username
|
||||||
|
except:
|
||||||
|
raise forms.ValidationError(
|
||||||
|
_("We are unable to find a user matching the data you provided."),
|
||||||
|
code='unknown_user',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EventForgot(EventViewMixin, TemplateView):
|
||||||
|
template_name = 'pretixpresale/event/forgot.html'
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
if request.user.is_authenticated() and \
|
||||||
|
(request.user.event is None or request.user.event == request.event):
|
||||||
|
return redirect(reverse(
|
||||||
|
'presale:event.orders', kwargs={
|
||||||
|
'organizer': self.request.event.organizer.slug,
|
||||||
|
'event': self.request.event.slug,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def generate_token(self, user):
|
||||||
|
return signing.dumps({
|
||||||
|
"type": "reset",
|
||||||
|
"user": user.id
|
||||||
|
})
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
if self.form.is_valid():
|
||||||
|
user = self.form.cleaned_data['user']
|
||||||
|
if user.email:
|
||||||
|
mail(
|
||||||
|
user, _('Password recovery'),
|
||||||
|
'pretixpresale/email/forgot.txt',
|
||||||
|
{
|
||||||
|
'user': user,
|
||||||
|
'event': self.request.event,
|
||||||
|
'url': settings.SITE_URL + reverse('presale:event.forgot.recover', kwargs={
|
||||||
|
'event': self.request.event.slug,
|
||||||
|
'organizer': self.request.event.organizer.slug,
|
||||||
|
}) + '?token=' + self.generate_token(user),
|
||||||
|
},
|
||||||
|
self.request.event
|
||||||
|
)
|
||||||
|
messages.success(request, _('We sent you an e-mail containing further instructions.'))
|
||||||
|
else:
|
||||||
|
messages.success(request, _('We are unable to send you a new password, as you did not enter an e-mail '
|
||||||
|
'address at your registration.'))
|
||||||
|
return redirect(reverse(
|
||||||
|
'presale:event.forgot', kwargs={
|
||||||
|
'organizer': self.request.event.organizer.slug,
|
||||||
|
'event': self.request.event.slug,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
return self.get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def form(self):
|
||||||
|
return PasswordForgotForm(
|
||||||
|
event=self.request.event,
|
||||||
|
data=self.request.POST if self.request.method == 'POST' else None
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['form'] = self.form
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class EventRecover(EventViewMixin, TemplateView):
|
||||||
|
template_name = 'pretixpresale/event/recover.html'
|
||||||
|
|
||||||
|
error_messages = {
|
||||||
|
'invalid': _('You clicked on an invalid link. Please check that you copied the full '
|
||||||
|
'web address into your address bar.'),
|
||||||
|
'expired': _('This password recovery link has expired. Please request a new e-mail and '
|
||||||
|
'use the recovery link within 24 hours.'),
|
||||||
|
'unknownuser': _('We were unable to find the user you requested a new password for.')
|
||||||
|
}
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
if request.user.is_authenticated() and \
|
||||||
|
(request.user.event is None or request.user.event == request.event):
|
||||||
|
return redirect(reverse(
|
||||||
|
'presale:event.orders', kwargs={
|
||||||
|
'organizer': self.request.event.organizer.slug,
|
||||||
|
'event': self.request.event.slug,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
try:
|
||||||
|
self.get_user()
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return self.invalid('unknownuser')
|
||||||
|
except SignatureExpired:
|
||||||
|
return self.invalid('expired')
|
||||||
|
except BadSignature:
|
||||||
|
return self.invalid('invalid')
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_user(self):
|
||||||
|
token = signing.loads(self.request.GET.get('token', ''),
|
||||||
|
max_age=3600 * 24)
|
||||||
|
if token['type'] != 'reset':
|
||||||
|
raise BadSignature()
|
||||||
|
return User.objects.get(id=token['user'])
|
||||||
|
|
||||||
|
def invalid(self, msg):
|
||||||
|
messages.error(self.request, self.error_messages[msg])
|
||||||
|
return redirect(reverse(
|
||||||
|
'presale:event.forgot', kwargs={
|
||||||
|
'organizer': self.request.event.organizer.slug,
|
||||||
|
'event': self.request.event.slug
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
if self.form.is_valid():
|
||||||
|
try:
|
||||||
|
user = self.get_user()
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return self.invalid('unknownuser')
|
||||||
|
except SignatureExpired:
|
||||||
|
return self.invalid('expired')
|
||||||
|
except BadSignature:
|
||||||
|
return self.invalid('invalid')
|
||||||
|
else:
|
||||||
|
user.set_password(self.form.cleaned_data['password'])
|
||||||
|
messages.success(request, _('You can now login using your new password.'))
|
||||||
|
return redirect(reverse(
|
||||||
|
'presale:event.checkout.login', kwargs={
|
||||||
|
'organizer': self.request.event.organizer.slug,
|
||||||
|
'event': self.request.event.slug,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
return self.get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def form(self):
|
||||||
|
return PasswordRecoverForm(
|
||||||
|
data=self.request.POST if self.request.method == 'POST' else None
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['form'] = self.form
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
class EventLogout(EventViewMixin, View):
|
class EventLogout(EventViewMixin, View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
logout(request)
|
logout(request)
|
||||||
|
|||||||
Reference in New Issue
Block a user