From 0533f117a9ecadbe640281e9721a6e85c9ae1fec Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Tue, 23 Aug 2011 23:22:17 -0500 Subject: [PATCH 01/56] Basic beaker caching functionality added to the application. --- mediagoblin/app.py | 5 ++++- mediagoblin/config_spec.ini | 6 ++++++ mediagoblin/init/__init__.py | 16 ++++++++++++++++ mediagoblin/mg_globals.py | 3 +++ mediagoblin/tests/test_mgoblin_app.ini | 3 +++ 5 files changed, 32 insertions(+), 1 deletion(-) diff --git a/mediagoblin/app.py b/mediagoblin/app.py index 3030929d..113bcb8d 100644 --- a/mediagoblin/app.py +++ b/mediagoblin/app.py @@ -25,7 +25,7 @@ from mediagoblin.mg_globals import setup_globals from mediagoblin.init.celery import setup_celery_from_config from mediagoblin.init import (get_jinja_loader, get_staticdirector, setup_global_and_app_config, setup_workbench, setup_database, - setup_storage) + setup_storage, setup_beaker_cache) class MediaGoblinApp(object): @@ -71,6 +71,9 @@ class MediaGoblinApp(object): # set up staticdirector tool self.staticdirector = get_staticdirector(app_config) + # set up caching + self.cache = setup_beaker_cache() + # Setup celery, if appropriate if setup_celery and not app_config.get('celery_setup_elsewhere'): if os.environ.get('CELERY_ALWAYS_EAGER'): diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini index 11badc1f..3f99b497 100644 --- a/mediagoblin/config_spec.ini +++ b/mediagoblin/config_spec.ini @@ -46,6 +46,12 @@ base_url = string(default="/mgoblin_media/") base_dir = string(default="%(here)s/user_dev/media/queue") +[beaker.cache] +type = string(default="file") +data_dir = string(default="%(here)s/user_dev/beaker/cache/data") +lock_dir = string(default="%(here)s/user_dev/beaker/cache/lock") + + [celery] # known booleans celery_result_persistent = boolean() diff --git a/mediagoblin/init/__init__.py b/mediagoblin/init/__init__.py index 44f604b1..4fe5df35 100644 --- a/mediagoblin/init/__init__.py +++ b/mediagoblin/init/__init__.py @@ -14,7 +14,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from beaker.cache import CacheManager +from beaker.util import parse_cache_config_options import jinja2 + from mediagoblin import staticdirect from mediagoblin.init.config import ( read_mediagoblin_config, generate_validation_report) @@ -135,3 +138,16 @@ def setup_workbench(): workbench_manager = WorkbenchManager(app_config['workbench_path']) setup_globals(workbench_manager = workbench_manager) + + +def setup_beaker_cache(): + """ + Setup the Beaker Cache manager. + """ + cache_config = mg_globals.global_config['beaker.cache'] + cache_config = dict( + [(u'cache.%s' % key, value) + for key, value in cache_config.iteritems()]) + cache = CacheManager(**parse_cache_config_options(cache_config)) + setup_globals(cache=cache) + return cache diff --git a/mediagoblin/mg_globals.py b/mediagoblin/mg_globals.py index 80ff5ead..8df5606e 100644 --- a/mediagoblin/mg_globals.py +++ b/mediagoblin/mg_globals.py @@ -31,6 +31,9 @@ db_connection = None # mongokit.Connection database = None +# beaker's cache manager +cache = None + # should be the same as the public_store = None queue_store = None diff --git a/mediagoblin/tests/test_mgoblin_app.ini b/mediagoblin/tests/test_mgoblin_app.ini index 9d938b4f..986f793b 100644 --- a/mediagoblin/tests/test_mgoblin_app.ini +++ b/mediagoblin/tests/test_mgoblin_app.ini @@ -19,5 +19,8 @@ base_url = /mgoblin_media/ [storage:queuestore] base_dir = %(here)s/test_user_dev/media/queue +[beaker.cache] +enabled = false + [celery] celery_always_eager = true From 25ba955e20e9262f2599a21d234511b724569717 Mon Sep 17 00:00:00 2001 From: Alejandro Villanueva Date: Thu, 21 Jul 2011 11:55:41 -0500 Subject: [PATCH 02/56] Adding fotgot password functionality --- mediagoblin/auth/forms.py | 32 +++++++ mediagoblin/auth/lib.py | 40 +++++++- mediagoblin/auth/routing.py | 12 +++ mediagoblin/auth/views.py | 96 ++++++++++++++++++- mediagoblin/db/migrations.py | 15 +++ mediagoblin/db/models.py | 2 + .../templates/mediagoblin/auth/change_fp.html | 37 +++++++ .../mediagoblin/auth/forgot_password.html | 37 +++++++ .../mediagoblin/auth/fp_changed_success.html | 25 +++++ .../mediagoblin/auth/fp_email_sent.html | 26 +++++ .../auth/fp_verification_email.txt | 25 +++++ .../templates/mediagoblin/auth/login.html | 6 ++ 12 files changed, 346 insertions(+), 7 deletions(-) create mode 100644 mediagoblin/templates/mediagoblin/auth/change_fp.html create mode 100644 mediagoblin/templates/mediagoblin/auth/forgot_password.html create mode 100644 mediagoblin/templates/mediagoblin/auth/fp_changed_success.html create mode 100644 mediagoblin/templates/mediagoblin/auth/fp_email_sent.html create mode 100644 mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt diff --git a/mediagoblin/auth/forms.py b/mediagoblin/auth/forms.py index 917909c5..1be74aa6 100644 --- a/mediagoblin/auth/forms.py +++ b/mediagoblin/auth/forms.py @@ -15,6 +15,7 @@ # along with this program. If not, see . 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()]) + diff --git a/mediagoblin/auth/lib.py b/mediagoblin/auth/lib.py index 6d1aec49..df93b666 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.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 9547b3ea..14e87133 100644 --- a/mediagoblin/auth/routing.py +++ b/mediagoblin/auth/routing.py @@ -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')] diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py index 4c4a34fd..50276442 100644 --- a/mediagoblin/auth/views.py +++ b/mediagoblin/auth/views.py @@ -15,6 +15,7 @@ # along with this program. If not, see . 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') diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 5456b248..b0cb6965 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_token': {'$exists': False}}, + {'$set': {'fp_token': ''}}, + multi=True) + database['users'].update( + {'fp_token_expire': {'$exists': False}}, + {'$set': {'fp_token_expire': ''}}, + multi=True) diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index b6e52441..a626937d 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_token': 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..0a3c76f6 --- /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 . +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} + +
+ +
+{% 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..1708874f --- /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 . +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} + +
+ +
+{% 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..dfce1423 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/auth/fp_changed_success.html @@ -0,0 +1,25 @@ +{# +# 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 . +#} +{% extends "mediagoblin/base.html" %} + +{% block mediagoblin_content %} +

+ Your password have been changed. Now you can Login +

+{% 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..d7fad722 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/auth/fp_email_sent.html @@ -0,0 +1,26 @@ +{# +# 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 . +#} +{% extends "mediagoblin/base.html" %} + +{% block mediagoblin_content %} +

+ Please check your email. We send an email whit an url to change your password. +

+ +{% 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..1b2dbe2f --- /dev/null +++ b/mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt @@ -0,0 +1,25 @@ +{# +# 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 . +#} +Hi {{ username }}, + +to change your GNU MediaGoblin password, open the following URL in your web browser + +{{ verification_url|safe }} + +If you think this is an error, just ignore this email and continue being a happy goblin! + diff --git a/mediagoblin/templates/mediagoblin/auth/login.html b/mediagoblin/templates/mediagoblin/auth/login.html index afbecf20..75e6eed1 100644 --- a/mediagoblin/templates/mediagoblin/auth/login.html +++ b/mediagoblin/templates/mediagoblin/auth/login.html @@ -44,6 +44,12 @@ {%- trans %}Create one here!{% endtrans %}

+

+ {% trans %}Forgot your password?{% endtrans %} +
+ + {%- trans %}Send a reminder!{% endtrans %} +

{% endif %} From 65030735085782be067c8c97e288e9baf3dbdbf4 Mon Sep 17 00:00:00 2001 From: Caleb Forbes Davis V Date: Sun, 28 Aug 2011 21:13:07 -0500 Subject: [PATCH 03/56] oops, uses Alejandro's fp_verification_key. my bad. --- mediagoblin/db/migrations.py | 4 ++-- mediagoblin/db/models.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index b0cb6965..cf09e817 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -100,8 +100,8 @@ def user_add_forgot_password_token_and_expires(database): Add token and expiration fields to help recover forgotten passwords """ database['users'].update( - {'fp_token': {'$exists': False}}, - {'$set': {'fp_token': ''}}, + {'fp_verification_key': {'$exists': False}}, + {'$set': {'fp_verification_key': ''}}, multi=True) database['users'].update( {'fp_token_expire': {'$exists': False}}, diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index a626937d..ad989f81 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -78,7 +78,7 @@ class User(Document): 'url' : unicode, 'bio' : unicode, # May contain markdown 'bio_html': unicode, # May contain plaintext, or HTML - 'fp_token': unicode, # forgotten password verification key + 'fp_verification_key': unicode, # forgotten password verification key 'fp_token_expire': datetime.datetime } From f85909c061a6cff211cf27f46879ad096ae7852f Mon Sep 17 00:00:00 2001 From: Caleb Forbes Davis V Date: Sun, 28 Aug 2011 21:41:42 -0500 Subject: [PATCH 04/56] needed to access email_sender_address through mg_globals.app_config instead of mg_globals.email_sender_address. --- mediagoblin/auth/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediagoblin/auth/lib.py b/mediagoblin/auth/lib.py index df93b666..31dc4b7f 100644 --- a/mediagoblin/auth/lib.py +++ b/mediagoblin/auth/lib.py @@ -146,7 +146,7 @@ def send_fp_verification_email(user,request): # TODO: There is no error handling in place send_email( - mg_globals.email_sender_address, + mg_globals.app_config['email_sender_address'], [user['email']], 'GNU MediaGoblin - Change forgotten password!', rendered_email) From fac7b8c9b160db7c6c85f9677acfb7bc7650c23f Mon Sep 17 00:00:00 2001 From: Caleb Forbes Davis V Date: Sun, 28 Aug 2011 22:46:21 -0500 Subject: [PATCH 05/56] Changed phrasing, spelling, and added translations --- mediagoblin/templates/mediagoblin/auth/change_fp.html | 4 ++-- .../templates/mediagoblin/auth/forgot_password.html | 4 ++-- .../mediagoblin/auth/fp_changed_success.html | 4 +++- .../templates/mediagoblin/auth/fp_email_sent.html | 4 +++- .../mediagoblin/auth/fp_verification_email.txt | 11 ++++++++--- mediagoblin/templates/mediagoblin/auth/login.html | 2 +- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/mediagoblin/templates/mediagoblin/auth/change_fp.html b/mediagoblin/templates/mediagoblin/auth/change_fp.html index 0a3c76f6..4be7e065 100644 --- a/mediagoblin/templates/mediagoblin/auth/change_fp.html +++ b/mediagoblin/templates/mediagoblin/auth/change_fp.html @@ -23,8 +23,8 @@
-