Adding fotgot password functionality

This commit is contained in:
Alejandro Villanueva
2011-07-21 11:55:41 -05:00
committed by Caleb Forbes Davis V
parent ad56a4826b
commit 25ba955e20
12 changed files with 346 additions and 7 deletions

View File

@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import wtforms
import re
from mediagoblin.util import fake_ugettext_passthrough as _
@@ -49,3 +50,34 @@ class LoginForm(wtforms.Form):
password = wtforms.PasswordField(
_('Password'),
[wtforms.validators.Required()])
class ForgotPassForm(wtforms.Form):
username = wtforms.TextField(
'Username or email',
[wtforms.validators.Required()])
def validate_username(form,field):
if not (re.match(r'^\w+$',field.data) or
re.match(r'^.+@[^.].*\.[a-z]{2,10}$',field.data, re.IGNORECASE)):
raise wtforms.ValidationError(u'Incorrect input')
class ChangePassForm(wtforms.Form):
password = wtforms.PasswordField(
'Password',
[wtforms.validators.Required(),
wtforms.validators.Length(min=6, max=30),
wtforms.validators.EqualTo(
'confirm_password',
'Passwords must match.')])
confirm_password = wtforms.PasswordField(
'Confirm password',
[wtforms.validators.Required()])
userid = wtforms.HiddenField(
'',
[wtforms.validators.Required()])
token = wtforms.HiddenField(
'',
[wtforms.validators.Required()])

View File

@@ -47,7 +47,7 @@ def bcrypt_check_password(raw_pass, stored_hash, extra_salt=None):
# number (thx to zooko on this advice, which I hopefully
# incorporated right.)
#
# See also:
# See also:
rand_salt = bcrypt.gensalt(5)
randplus_stored_hash = bcrypt.hashpw(stored_hash, rand_salt)
randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt)
@@ -99,7 +99,7 @@ def send_verification_email(user, request):
Args:
- user: a user object
- request: the request
- request: the request
"""
rendered_email = render_template(
request, 'mediagoblin/auth/verification_email.txt',
@@ -116,8 +116,38 @@ def send_verification_email(user, request):
[user['email']],
# TODO
# Due to the distributed nature of GNU MediaGoblin, we should
# find a way to send some additional information about the
# specific GNU MediaGoblin instance in the subject line. For
# example "GNU MediaGoblin @ Wandborg - [...]".
# find a way to send some additional information about the
# specific GNU MediaGoblin instance in the subject line. For
# example "GNU MediaGoblin @ Wandborg - [...]".
'GNU MediaGoblin - Verify your email!',
rendered_email)
EMAIL_FP_VERIFICATION_TEMPLATE = (
u"http://{host}{uri}?"
u"userid={userid}&token={fp_verification_key}")
def send_fp_verification_email(user,request):
"""
Send the verification email to users to change their password.
Args:
- user: a user object
- request: the request
"""
rendered_email = render_template(
request, 'mediagoblin/auth/fp_verification_email.txt',
{'username': user['username'],
'verification_url': EMAIL_FP_VERIFICATION_TEMPLATE.format(
host=request.host,
uri=request.urlgen('mediagoblin.auth.verify_forgot_password'),
userid=unicode(user['_id']),
fp_verification_key=user['fp_verification_key'])})
# TODO: There is no error handling in place
send_email(
mg_globals.email_sender_address,
[user['email']],
'GNU MediaGoblin - Change forgotten password!',
rendered_email)

View File

@@ -30,4 +30,16 @@ auth_routes = [
Route('mediagoblin.auth.resend_verification_success',
'/resend_verification_success/',
template='mediagoblin/auth/resent_verification_email.html',
controller='mediagoblin.views:simple_template_render'),
Route('mediagoblin.auth.forgot_password', '/forgotpass/',
controller='mediagoblin.auth.views:forgot_password'),
Route('mediagoblin.auth.verify_forgot_password', '/verifyforgotpass/',
controller='mediagoblin.auth.views:verify_forgot_password'),
Route('mediagoblin.auth.fp_changed_success',
'/fp_changed_success/',
template='mediagoblin/auth/fp_changed_success.html',
controller='mediagoblin.views:simple_template_render'),
Route('mediagoblin.auth.fp_email_sent',
'/fp_email_sent/',
template='mediagoblin/auth/fp_email_sent.html',
controller='mediagoblin.views:simple_template_render')]

View File

@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import uuid
import datetime
from webob import exc
@@ -22,10 +23,11 @@ from mediagoblin import messages
from mediagoblin import mg_globals
from mediagoblin.util import render_to_response, redirect, render_404
from mediagoblin.util import pass_to_ugettext as _
from mediagoblin.db.util import ObjectId
from mediagoblin.db.util import ObjectId, InvalidId
from mediagoblin.auth import lib as auth_lib
from mediagoblin.auth import forms as auth_forms
from mediagoblin.auth.lib import send_verification_email
from mediagoblin.auth.lib import send_verification_email, \
send_fp_verification_email
def register(request):
@@ -187,3 +189,93 @@ def resend_activation(request):
return redirect(
request, 'mediagoblin.user_pages.user_home',
user=request.user['username'])
def forgot_password(request):
"""
Forgot password view
Sends an email whit an url to renew forgoten password
"""
fp_form = auth_forms.ForgotPassForm(request.POST)
if request.method == 'POST' and fp_form.validate():
user = request.db.User.one(
{'$or': [{'username': request.POST['username']},
{'email': request.POST['username']}]})
if not user:
fp_form.username.errors.append(
u"Sorry, the username doesn't exists")
else:
user['fp_verification_key'] = unicode(uuid.uuid4())
user['fp_token_expire'] = datetime.datetime.now() + \
datetime.timedelta(days=10)
user.save()
send_fp_verification_email(user, request)
return redirect(request, 'mediagoblin.auth.fp_email_sent')
return render_to_response(
request,
'mediagoblin/auth/forgot_password.html',
{'fp_form': fp_form})
def verify_forgot_password(request):
if request.method == 'GET':
# If we don't have userid and token parameters, we can't do anything;404
if (not request.GET.has_key('userid') or
not request.GET.has_key('token')):
return exc.HTTPNotFound('You must provide userid and token')
# check if it's a valid Id
try:
user = request.db.User.find_one(
{'_id': ObjectId(unicode(request.GET['userid']))})
except InvalidId:
return exc.HTTPNotFound('Invalid id')
# check if we have a real user and correct token
if (user and
user['fp_verification_key'] == unicode(request.GET['token'])):
cp_form = auth_forms.ChangePassForm(request.GET)
return render_to_response(
request,
'mediagoblin/auth/change_fp.html',
{'cp_form': cp_form})
# in case there is a valid id but no user whit that id in the db
else:
return exc.HTTPNotFound('User not found')
if request.method == 'POST':
# verification doing here to prevent POST values modification
try:
user = request.db.User.find_one(
{'_id': ObjectId(unicode(request.POST['userid']))})
except InvalidId:
return exc.HTTPNotFound('Invalid id')
cp_form = auth_forms.ChangePassForm(request.POST)
# verification doing here to prevent POST values modification
# if token and id are correct they are able to change their password
if (user and
user['fp_verification_key'] == unicode(request.POST['token'])):
if cp_form.validate():
user['pw_hash'] = auth_lib.bcrypt_gen_password_hash(
request.POST['password'])
user['fp_verification_key'] = None
user.save()
return redirect(request,
'mediagoblin.auth.fp_changed_success')
else:
return render_to_response(
request,
'mediagoblin/auth/change_fp.html',
{'cp_form': cp_form})
else:
return exc.HTTPNotFound('User not found')