diff options
author | Marian Sigler <m@qjym.de> | 2012-09-26 03:47:57 +0200 |
---|---|---|
committer | Marian Sigler <m@qjym.de> | 2012-09-26 03:47:57 +0200 |
commit | bf5d684c05a26787de0de80b0894b9d2d031c6ad (patch) | |
tree | 384a98a46061d04cc5064059f89215886b208dbc | |
parent | 1d149b55d57632c41bb3d0d60805f3552cde3a22 (diff) | |
download | web-bf5d684c05a26787de0de80b0894b9d2d031c6ad.tar.gz web-bf5d684c05a26787de0de80b0894b9d2d031c6ad.tar.bz2 web-bf5d684c05a26787de0de80b0894b9d2d031c6ad.zip |
Implement password recovery functionality.
-rw-r--r-- | app.py | 52 | ||||
-rw-r--r-- | forms.py | 25 | ||||
-rw-r--r-- | templates/index.html | 7 | ||||
-rw-r--r-- | templates/lost_password.html | 16 | ||||
-rw-r--r-- | templates/lost_password_complete.html | 19 | ||||
-rw-r--r-- | templates/mail/lost_password.txt | 11 | ||||
-rw-r--r-- | templates/register.html | 2 | ||||
-rw-r--r-- | templates/register_complete.html | 2 | ||||
-rw-r--r-- | templates/settings.html | 2 | ||||
-rw-r--r-- | utils.py | 1 |
10 files changed, 124 insertions, 13 deletions
@@ -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__': @@ -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) }} @@ -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') |