Merge branch 'gullydwarf-cfdv-f357_lost_password_functionality'

Conflicts:
	mediagoblin/auth/routing.py
This commit is contained in:
Christopher Allan Webber
2011-09-08 08:12:43 -05:00
13 changed files with 482 additions and 11 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 _
@@ -51,3 +52,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.app_config['email_sender_address'],
[user['email']],
'GNU MediaGoblin - Change forgotten password!',
rendered_email)

View File

@@ -26,4 +26,20 @@ auth_routes = [
Route('mediagoblin.auth.verify_email', '/verify_email/',
controller='mediagoblin.auth.views:verify_email'),
Route('mediagoblin.auth.resend_verification', '/resend_verification/',
controller='mediagoblin.auth.views:resend_activation')]
controller='mediagoblin.auth.views:resend_activation'),
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', '/forgot_password/',
controller='mediagoblin.auth.views:forgot_password'),
Route('mediagoblin.auth.verify_forgot_password', '/forgot_password/verify/',
controller='mediagoblin.auth.views:verify_forgot_password'),
Route('mediagoblin.auth.fp_changed_success',
'/forgot_password/changed_success/',
template='mediagoblin/auth/fp_changed_success.html',
controller='mediagoblin.views:simple_template_render'),
Route('mediagoblin.auth.fp_email_sent',
'/forgot_password/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):
@@ -151,9 +153,12 @@ def verify_email(request):
{'_id': ObjectId(unicode(request.GET['userid']))})
if user and user['verification_key'] == unicode(request.GET['token']):
user['status'] = u'active'
user['email_verified'] = True
user[u'status'] = u'active'
user[u'email_verified'] = True
user[u'verification_key'] = None
user.save()
messages.add_message(
request,
messages.SUCCESS,
@@ -176,7 +181,7 @@ def resend_activation(request):
Resend the activation email.
"""
request.user['verification_key'] = unicode(uuid.uuid4())
request.user[u'verification_key'] = unicode(uuid.uuid4())
request.user.save()
send_verification_email(request.user, request)
@@ -188,3 +193,121 @@ 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():
# '$or' not available till mongodb 1.5.3
user = request.db.User.find_one(
{'username': request.POST['username']})
if not user:
user = request.db.User.find_one(
{'email': request.POST['username']})
if user:
if user['email_verified'] and user['status'] == 'active':
user[u'fp_verification_key'] = unicode(uuid.uuid4())
user[u'fp_token_expire'] = datetime.datetime.now() + \
datetime.timedelta(days=10)
user.save()
send_fp_verification_email(user, request)
else:
# special case... we can't send the email because the
# username is inactive / hasn't verified their email
messages.add_message(
request,
messages.WARNING,
_("Could not send password recovery email as "
"your username is inactive or your account's "
"email address has not been verified."))
return redirect(
request, 'mediagoblin.user_pages.user_home',
user=user['username'])
# do not reveal whether or not there is a matching user, just move along
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):
"""
Check the forgot-password verification and possibly let the user
change their password because of it.
"""
# get form data variables, and specifically check for presence of token
formdata = _process_for_token(request)
if not formdata['has_userid_and_token']:
return render_404(request)
formdata_token = formdata['vars']['token']
formdata_userid = formdata['vars']['userid']
formdata_vars = formdata['vars']
# check if it's a valid Id
try:
user = request.db.User.find_one(
{'_id': ObjectId(unicode(formdata_userid))})
except InvalidId:
return render_404(request)
# check if we have a real user and correct token
if ((user and user['fp_verification_key'] and
user['fp_verification_key'] == unicode(formdata_token) and
datetime.datetime.now() < user['fp_token_expire']
and user['email_verified'] and user['status'] == 'active')):
cp_form = auth_forms.ChangePassForm(formdata_vars)
if request.method == 'POST' and cp_form.validate():
user[u'pw_hash'] = auth_lib.bcrypt_gen_password_hash(
request.POST['password'])
user[u'fp_verification_key'] = None
user[u'fp_token_expire'] = 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})
# in case there is a valid id but no user whit that id in the db
# or the token expired
else:
return render_404(request)
def _process_for_token(request):
"""
Checks for tokens in formdata without prior knowledge of request method
For now, returns whether the userid and token formdata variables exist, and
the formdata variables in a hash. Perhaps an object is warranted?
"""
# retrieve the formdata variables
if request.method == 'GET':
formdata_vars = request.GET
else:
formdata_vars = request.POST
formdata = {
'vars': formdata_vars,
'has_userid_and_token':
formdata_vars.has_key('userid') and formdata_vars.has_key('token')}
return formdata