aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mediagoblin/auth/forms.py32
-rw-r--r--mediagoblin/auth/lib.py40
-rw-r--r--mediagoblin/auth/routing.py18
-rw-r--r--mediagoblin/auth/views.py133
-rw-r--r--mediagoblin/db/migrations.py15
-rw-r--r--mediagoblin/db/models.py2
-rw-r--r--mediagoblin/templates/mediagoblin/auth/change_fp.html37
-rw-r--r--mediagoblin/templates/mediagoblin/auth/forgot_password.html37
-rw-r--r--mediagoblin/templates/mediagoblin/auth/fp_changed_success.html27
-rw-r--r--mediagoblin/templates/mediagoblin/auth/fp_email_sent.html28
-rw-r--r--mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt30
-rw-r--r--mediagoblin/templates/mediagoblin/auth/login.html6
-rw-r--r--mediagoblin/tests/test_auth.py88
13 files changed, 482 insertions, 11 deletions
diff --git a/mediagoblin/auth/forms.py b/mediagoblin/auth/forms.py
index 1dfaf095..6339b4a3 100644
--- a/mediagoblin/auth/forms.py
+++ b/mediagoblin/auth/forms.py
@@ -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()])
+
diff --git a/mediagoblin/auth/lib.py b/mediagoblin/auth/lib.py
index 89cfb6ff..d7d351a5 100644
--- a/mediagoblin/auth/lib.py
+++ b/mediagoblin/auth/lib.py
@@ -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)
+
diff --git a/mediagoblin/auth/routing.py b/mediagoblin/auth/routing.py
index edd21be7..912d89fa 100644
--- a/mediagoblin/auth/routing.py
+++ b/mediagoblin/auth/routing.py
@@ -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')]
diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py
index 1b942280..f67f0588 100644
--- a/mediagoblin/auth/views.py
+++ b/mediagoblin/auth/views.py
@@ -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
diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py
index b07681a6..755f49c5 100644
--- a/mediagoblin/db/migrations.py
+++ b/mediagoblin/db/migrations.py
@@ -92,3 +92,18 @@ def mediaentry_add_fail_error_and_metadata(database):
{'fail_metadata': {'$exists': False}},
{'$set': {'fail_metadata': {}}},
multi=True)
+
+
+@RegisterMigration(6)
+def user_add_forgot_password_token_and_expires(database):
+ """
+ Add token and expiration fields to help recover forgotten passwords
+ """
+ database['users'].update(
+ {'fp_verification_key': {'$exists': False}},
+ {'$set': {'fp_verification_key': None}},
+ multi=True)
+ database['users'].update(
+ {'fp_token_expire': {'$exists': False}},
+ {'$set': {'fp_token_expire': None}},
+ multi=True)
diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py
index 792a515e..bbddada6 100644
--- a/mediagoblin/db/models.py
+++ b/mediagoblin/db/models.py
@@ -78,6 +78,8 @@ class User(Document):
'url' : unicode,
'bio' : unicode, # May contain markdown
'bio_html': unicode, # May contain plaintext, or HTML
+ 'fp_verification_key': unicode, # forgotten password verification key
+ 'fp_token_expire': datetime.datetime
}
required_fields = ['username', 'created', 'pw_hash', 'email']
diff --git a/mediagoblin/templates/mediagoblin/auth/change_fp.html b/mediagoblin/templates/mediagoblin/auth/change_fp.html
new file mode 100644
index 00000000..4be7e065
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/auth/change_fp.html
@@ -0,0 +1,37 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011 Free Software Foundation, Inc
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_content %}
+
+ <form action="{{ request.urlgen('mediagoblin.auth.verify_forgot_password') }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="grid_6 prefix_1 suffix_1 form_box">
+ <h1>{% trans %}Enter your new password{% endtrans %}</h1>
+
+ {{ wtforms_util.render_divs(cp_form) }}
+ <div class="form_submit_buttons">
+ <input type="submit" value="submit" class="button"/>
+ </div>
+
+ </div>
+ </form>
+{% endblock %}
+
diff --git a/mediagoblin/templates/mediagoblin/auth/forgot_password.html b/mediagoblin/templates/mediagoblin/auth/forgot_password.html
new file mode 100644
index 00000000..23fa9eb5
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/auth/forgot_password.html
@@ -0,0 +1,37 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011 Free Software Foundation, Inc
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_content %}
+
+ <form action="{{ request.urlgen('mediagoblin.auth.forgot_password') }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="grid_6 prefix_1 suffix_1 form_box">
+ <h1>{% trans %}Enter your username or email{% endtrans %}</h1>
+
+ {{ wtforms_util.render_divs(fp_form) }}
+ <div class="form_submit_buttons">
+ <input type="submit" value="submit" class="button"/>
+ </div>
+
+ </div>
+ </form>
+{% endblock %}
+
diff --git a/mediagoblin/templates/mediagoblin/auth/fp_changed_success.html b/mediagoblin/templates/mediagoblin/auth/fp_changed_success.html
new file mode 100644
index 00000000..d6633ec6
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/auth/fp_changed_success.html
@@ -0,0 +1,27 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011 Free Software Foundation, Inc
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% block mediagoblin_content %}
+ <p>
+ {% trans %}
+ Your password has been changed. Try to log in now.
+ {% endtrans %}
+ </p>
+{% endblock %}
+
diff --git a/mediagoblin/templates/mediagoblin/auth/fp_email_sent.html b/mediagoblin/templates/mediagoblin/auth/fp_email_sent.html
new file mode 100644
index 00000000..bc79b970
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/auth/fp_email_sent.html
@@ -0,0 +1,28 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011 Free Software Foundation, Inc
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% block mediagoblin_content %}
+ <p>
+ {% trans %}
+ Check your inbox. We sent an email with a URL for changing your password.
+ {% endtrans %}
+ </p>
+
+{% endblock %}
+
diff --git a/mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt b/mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt
new file mode 100644
index 00000000..fb5e1674
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt
@@ -0,0 +1,30 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011 Free Software Foundation, Inc
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% trans username=username, verification_url=verification_url|safe -%}
+Hi {{ username }},
+
+to change your GNU MediaGoblin password, open the following URL in
+your web browser:
+
+{{ verification_url }}
+
+If you think this is an error, just ignore this email and continue being
+a happy goblin!
+{%- endtrans %}
+
diff --git a/mediagoblin/templates/mediagoblin/auth/login.html b/mediagoblin/templates/mediagoblin/auth/login.html
index 958cf9ea..3926a1df 100644
--- a/mediagoblin/templates/mediagoblin/auth/login.html
+++ b/mediagoblin/templates/mediagoblin/auth/login.html
@@ -44,6 +44,12 @@
<a href="{{ request.urlgen('mediagoblin.auth.register') }}">
{%- trans %}Create one here!{% endtrans %}</a>
</p>
+ <p>
+ {% trans %}Forgot your password?{% endtrans %}
+ <br />
+ <a href="{{ request.urlgen('mediagoblin.auth.forgot_password') }}">
+ {%- trans %}Change it!{% endtrans %}</a>
+ </p>
{% endif %}
</div>
</form>
diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py
index eed418c6..fbbe1613 100644
--- a/mediagoblin/tests/test_auth.py
+++ b/mediagoblin/tests/test_auth.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urlparse
+import datetime
from nose.tools import assert_equal
@@ -237,6 +238,93 @@ def test_register_views(test_app):
## TODO: Also check for double instances of an email address?
+ ### Oops, forgot the password
+ # -------------------
+ util.clear_test_template_context()
+ response = test_app.post(
+ '/auth/forgot_password/',
+ {'username': 'happygirl'})
+ response.follow()
+
+ ## Did we redirect to the proper page? Use the right template?
+ assert_equal(
+ urlparse.urlsplit(response.location)[2],
+ '/auth/forgot_password/email_sent/')
+ assert util.TEMPLATE_TEST_CONTEXT.has_key(
+ 'mediagoblin/auth/fp_email_sent.html')
+
+ ## Make sure link to change password is sent by email
+ assert len(util.EMAIL_TEST_INBOX) == 1
+ message = util.EMAIL_TEST_INBOX.pop()
+ assert message['To'] == 'happygrrl@example.org'
+ email_context = util.TEMPLATE_TEST_CONTEXT[
+ 'mediagoblin/auth/fp_verification_email.txt']
+ #TODO - change the name of verification_url to something forgot-password-ish
+ assert email_context['verification_url'] in message.get_payload(decode=True)
+
+ path = urlparse.urlsplit(email_context['verification_url'])[2]
+ get_params = urlparse.urlsplit(email_context['verification_url'])[3]
+ assert path == u'/auth/forgot_password/verify/'
+ parsed_get_params = urlparse.parse_qs(get_params)
+
+ # user should have matching parameters
+ new_user = mg_globals.database.User.find_one({'username': 'happygirl'})
+ assert parsed_get_params['userid'] == [unicode(new_user['_id'])]
+ assert parsed_get_params['token'] == [new_user['fp_verification_key']]
+
+ ### The forgotten password token should be set to expire in ~ 10 days
+ # A few ticks have expired so there are only 9 full days left...
+ assert (new_user['fp_token_expire'] - datetime.datetime.now()).days == 9
+
+ ## Try using a bs password-changing verification key, shouldn't work
+ util.clear_test_template_context()
+ response = test_app.get(
+ "/auth/forgot_password/verify/?userid=%s&token=total_bs" % unicode(
+ new_user['_id']), status=400)
+ assert response.status == '400 Bad Request'
+
+ ## Try using an expired token to change password, shouldn't work
+ util.clear_test_template_context()
+ real_token_expiration = new_user['fp_token_expire']
+ new_user['fp_token_expire'] = datetime.datetime.now()
+ new_user.save()
+ response = test_app.get("%s?%s" % (path, get_params), status=400)
+ assert response.status == '400 Bad Request'
+ new_user['fp_token_expire'] = real_token_expiration
+ new_user.save()
+
+ ## Verify step 1 of password-change works -- can see form to change password
+ util.clear_test_template_context()
+ response = test_app.get("%s?%s" % (path, get_params))
+ assert util.TEMPLATE_TEST_CONTEXT.has_key('mediagoblin/auth/change_fp.html')
+
+ ## Verify step 2.1 of password-change works -- report success to user
+ util.clear_test_template_context()
+ response = test_app.post(
+ '/auth/forgot_password/verify/', {
+ 'userid': parsed_get_params['userid'],
+ 'password': 'iamveryveryhappy',
+ 'confirm_password': 'iamveryveryhappy',
+ 'token': parsed_get_params['token']})
+ response.follow()
+ assert util.TEMPLATE_TEST_CONTEXT.has_key(
+ 'mediagoblin/auth/fp_changed_success.html')
+
+ ## Verify step 2.2 of password-change works -- login w/ new password success
+ util.clear_test_template_context()
+ response = test_app.post(
+ '/auth/login/', {
+ 'username': u'happygirl',
+ 'password': 'iamveryveryhappy'})
+
+ # User should be redirected
+ response.follow()
+ assert_equal(
+ urlparse.urlsplit(response.location)[2],
+ '/')
+ assert util.TEMPLATE_TEST_CONTEXT.has_key(
+ 'mediagoblin/root.html')
+
@setup_fresh_app
def test_authentication_views(test_app):