summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarian Sigler <m@qjym.de>2012-09-26 03:47:57 +0200
committerMarian Sigler <m@qjym.de>2012-09-26 03:47:57 +0200
commitbf5d684c05a26787de0de80b0894b9d2d031c6ad (patch)
tree384a98a46061d04cc5064059f89215886b208dbc
parent1d149b55d57632c41bb3d0d60805f3552cde3a22 (diff)
downloadweb-bf5d684c05a26787de0de80b0894b9d2d031c6ad.tar.gz
web-bf5d684c05a26787de0de80b0894b9d2d031c6ad.tar.bz2
web-bf5d684c05a26787de0de80b0894b9d2d031c6ad.zip
Implement password recovery functionality.
-rw-r--r--app.py52
-rw-r--r--forms.py25
-rw-r--r--templates/index.html7
-rw-r--r--templates/lost_password.html16
-rw-r--r--templates/lost_password_complete.html19
-rw-r--r--templates/mail/lost_password.txt11
-rw-r--r--templates/register.html2
-rw-r--r--templates/register_complete.html2
-rw-r--r--templates/settings.html2
-rw-r--r--utils.py1
10 files changed, 124 insertions, 13 deletions
diff --git a/app.py b/app.py
index 8eb8ece..518be05 100644
--- a/app.py
+++ b/app.py
@@ -104,6 +104,55 @@ def register_complete(token):
}
+@app.route('/lost_password', methods=['GET', 'POST'])
+@templated('lost_password.html')
+@logout_required
+def lost_password():
+ form = LostPasswordForm(request.form)
+ if request.method == 'POST' and form.validate():
+ #TODO: make the link only usable once (e.g include a hash of the old pw)
+ # atm the only thing we do is make the link valid for only little time
+ confirm_token = make_confirmation('lost_password', (form.username.data,))
+ confirm_link = url_for('lost_password_complete', token=confirm_token, _external=True)
+
+ body = render_template('mail/lost_password.txt', username=form.username.data,
+ link=confirm_link)
+
+ send_mail(form.user.mail, u'Passwort vergessen', body,
+ sender=app.config.get('MAIL_CONFIRM_SENDER'))
+
+ flash(u'Wir haben dir eine E-Mail mit einem Link zum Passwort ändern '
+ u'geschickt. Bitte folge den Anweisungen in der E-Mail.', 'success')
+
+ return redirect(url_for('index'))
+
+ return {'form': form}
+
+
+@app.route('/lost_password/<token>', methods=['GET', 'POST'])
+@templated('lost_password_complete.html')
+@logout_required
+def lost_password_complete(token):
+ username, = http_verify_confirmation('lost_password', token.encode('ascii'), timeout=4*60*60)
+
+ form = RegisterCompleteForm(request.form)
+ if request.method == 'POST' and form.validate():
+ user = g.ldap.get_by_uid(username)
+ user.change_password(form.password.data)
+ g.ldap.update(user, as_admin=True)
+
+ session['username'] = username
+ session['password'] = encrypt_password(form.password.data)
+ flash(u'Passwort geändert.', 'success')
+
+ return redirect(url_for('settings'))
+
+ return {
+ 'form': form,
+ 'token': token,
+ 'username': username,
+ }
+
@app.route('/settings', methods=['GET', 'POST'])
@templated('settings.html')
@@ -186,7 +235,8 @@ def debug():
# we need the app to exist before initializing the forms
-from forms import RegisterForm, RegisterCompleteForm, LoginForm, SettingsForm
+from forms import RegisterForm, RegisterCompleteForm, LoginForm, SettingsForm,\
+ LostPasswordForm
if __name__ == '__main__':
diff --git a/forms.py b/forms.py
index c5728d5..ff54449 100644
--- a/forms.py
+++ b/forms.py
@@ -1,14 +1,17 @@
# -*- coding: utf-8 -*-
-from account import SERVICES
-from flask.ext.wtf import Form, validators, TextField, PasswordField
+from account import SERVICES, NoSuchUserError
+from flask import g
+from flask.ext.wtf import Form, validators, TextField, PasswordField,\
+ ValidationError
+from functools import partial
from utils import _username_re
-username = TextField('Benutzername', [validators.Regexp(_username_re, message=u'Benutzername darf nur aus a-z bestehen (2-16 Zeichen)')])
+username = partial(TextField, 'Benutzername', [validators.Regexp(_username_re, message=u'Benutzername darf nur aus a-z bestehen (2-16 Zeichen)')])
class RegisterForm(Form):
- username = username
+ username = username()
mail = TextField('E-Mail-Adresse', [validators.Email(), validators.Length(min=6, max=50)])
@@ -16,13 +19,24 @@ class RegisterCompleteForm(Form):
password = PasswordField('Passwort', [validators.Required(),
validators.EqualTo('password_confirm', message=u'Passwörter stimmen nicht überein')])
password_confirm = PasswordField(u'Passwort bestätigen')
+ # n.b. this form is also used in lost_password_complete
class LoginForm(Form):
- username = username
+ username = username()
password = PasswordField('Passwort', [validators.Required()])
+class LostPasswordForm(Form):
+ username = username()
+
+ def validate_username(form, field):
+ try:
+ form.user = g.ldap.get_by_uid(field.data)
+ except NoSuchUserError:
+ raise ValidationError(u'Es gibt keinen Benutzer mit diesem Namen.')
+
+
class SettingsForm(Form):
old_password = PasswordField('Bisheriges Passwort',
[validators.Required(u'Bitte gib dein (altes) Passwort an, um deine Daten zu ändern.')])
@@ -47,4 +61,3 @@ for service in SERVICES:
]))
setattr(SettingsForm, 'password_confirm_%s' % service.id,
PasswordField(u'Passwort für %s (Bestätigung)' % service.name))
-
diff --git a/templates/index.html b/templates/index.html
index abcbb1d..cbbaa44 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -5,9 +5,12 @@
{%- if session.username %}
<p>Hallo {{ session.username }}. <a href="{{ url_for('settings') }}">Einstellungen</a></p>
{%- else %}
-<p><a href="/register">Account erstellen</a></p>
+<p>
+ <a href="/register">Account erstellen</a> |
+ <a href="/lost_password">Passwort vergessen</a>
+</p>
<form action="{{ url_for('index') }}" method="post" class="form-horizontal">
- {{ render_field(form.username) }}
+ {{ render_field(form.username, autofocus="autofocus") }}
{{ render_field(form.password) }}
{{ form.csrf_token }}
<div class="form-actions"><input type="submit" value="Login" /></div>
diff --git a/templates/lost_password.html b/templates/lost_password.html
new file mode 100644
index 0000000..391af0d
--- /dev/null
+++ b/templates/lost_password.html
@@ -0,0 +1,16 @@
+{%- extends 'base.html' %}
+{%- from '_macros.html' import render_field %}
+{%- set title = 'Passwort vergessen' %}
+{%- set no_login_message = true %}
+{%- block content %}
+<form action="{{ url_for('lost_password') }}" method="post" class="form-horizontal">
+ <p>
+ Du hast dein Passwort vergessen? Kein Problem.
+ Gib einfach unten deinen Benutzernamen ein, und wir schicken dir einen Link,
+ mit dem du dir ein neues setzen kannst.
+ </p>
+ {{ render_field(form.username, autofocus="autofocus") }}
+ {{ form.csrf_token }}
+ <div class="form-actions"><input type="submit" value="Weiter" /></div>
+</form>
+{%- endblock %}
diff --git a/templates/lost_password_complete.html b/templates/lost_password_complete.html
new file mode 100644
index 0000000..828bd6d
--- /dev/null
+++ b/templates/lost_password_complete.html
@@ -0,0 +1,19 @@
+{%- extends 'base.html' %}
+{%- from '_macros.html' import render_field %}
+{%- set title = 'Passwort vergessen' %}
+{%- set no_login_message = true %}
+{%- block content %}
+<form action="{{ url_for('lost_password_complete', token=token) }}" method="post" class="form-horizontal">
+ <p>
+ Hier kannst du jetzt ein neues Passwort setzen.
+ </p>
+ <div class="control-group">
+ <div class="control-label">Benutzername</div>
+ <div class="controls"><input readonly="readonly" value="{{ username }}" /></div>
+ </div>
+ {{ render_field(form.password, autofocus="autofocus") }}
+ {{ render_field(form.password_confirm) }}
+ {{ form.csrf_token }}
+ <div class="form-actions"><input type="submit" value="Registrieren" /></div>
+</form>
+{%- endblock %}
diff --git a/templates/mail/lost_password.txt b/templates/mail/lost_password.txt
new file mode 100644
index 0000000..af51ae4
--- /dev/null
+++ b/templates/mail/lost_password.txt
@@ -0,0 +1,11 @@
+Hallo {{ username }},
+
+Jemand, vermutlich du, hat auf spline accounts einen Link zum Ändern
+deines Passworts angefordert.
+
+Hier kannst du dein Passwort ändern:
+ <{{ link }}>
+
+
+Wenn du diese Mail nicht angefordert hast, brauchst du nichts
+weiter zu tun. Dein altes Passwort bleibt weiter gültig.
diff --git a/templates/register.html b/templates/register.html
index ab785ea..d8ef800 100644
--- a/templates/register.html
+++ b/templates/register.html
@@ -4,7 +4,7 @@
{%- set no_login_message = true %}
{%- block content %}
<form action="{{ url_for('register') }}" method="post" class="form-horizontal">
- {{ render_field(form.username) }}
+ {{ render_field(form.username, autofocus="autofocus") }}
{{ render_field(form.mail) }}
{{ form.csrf_token }}
<div class="form-actions"><input type="submit" value="E-Mail-Adresse bestätigen" /></div>
diff --git a/templates/register_complete.html b/templates/register_complete.html
index 9320995..629f9c9 100644
--- a/templates/register_complete.html
+++ b/templates/register_complete.html
@@ -17,7 +17,7 @@
<div class="control-label">E-Mail-Adresse</div>
<div class="controls"><input readonly="readonly" value="{{ mail }}" /></div>
</div>
- {{ render_field(form.password) }}
+ {{ render_field(form.password, autofocus="autofocus") }}
{{ render_field(form.password_confirm) }}
{{ form.csrf_token }}
<div class="form-actions"><input type="submit" value="Registrieren" /></div>
diff --git a/templates/settings.html b/templates/settings.html
index 1d112d8..c672493 100644
--- a/templates/settings.html
+++ b/templates/settings.html
@@ -3,7 +3,7 @@
{%- set title = 'Einstellungen' %}
{%- block content %}
<form action="{{ url_for('settings') }}" method="post" class="form-horizontal">
- {{ render_field(form.old_password) }}
+ {{ render_field(form.old_password, autofocus="autofocus") }}
<h3>Globale Einstellungen ändern</h3>
{{ render_field(form.mail) }}
{{ render_field(form.password) }}
diff --git a/utils.py b/utils.py
index 6ab7ed4..42b3bf5 100644
--- a/utils.py
+++ b/utils.py
@@ -133,7 +133,6 @@ def verify_confirmation(realm, token, timeout=None):
if mac != hmac.new(key, token[20:], sha1).digest():
raise ConfirmationInvalid('MAC does not match')
- print '%d+%d=%d <> %d' % (tokentime, timeout, tokentime+timeout, time())
if timeout is not None and time() > tokentime + timeout:
raise ConfirmationTimeout('Token is too old')