aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin/auth
diff options
context:
space:
mode:
authorNathan Yergler <nathan@yergler.net>2011-10-01 12:08:58 -0700
committerNathan Yergler <nathan@yergler.net>2011-10-01 12:08:58 -0700
commit6bfbe0242653678c09258b7a642514d706153eac (patch)
treed3e0c7f94f566886a6b80ce98d8a003465f4368f /mediagoblin/auth
parent0a8a3fc1571100aba3bd3a3dec98f5e9e252780b (diff)
parent573aba86b58c2ab064d0d57ed0bbae6bdf9a2819 (diff)
downloadmediagoblin-6bfbe0242653678c09258b7a642514d706153eac.tar.lz
mediagoblin-6bfbe0242653678c09258b7a642514d706153eac.tar.xz
mediagoblin-6bfbe0242653678c09258b7a642514d706153eac.zip
Merge remote-tracking branch 'refs/remotes/upstream/master' into 569-application-middleware
Conflicts: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html
Diffstat (limited to 'mediagoblin/auth')
-rw-r--r--mediagoblin/auth/forms.py44
-rw-r--r--mediagoblin/auth/lib.py40
-rw-r--r--mediagoblin/auth/routing.py12
-rw-r--r--mediagoblin/auth/views.py133
4 files changed, 210 insertions, 19 deletions
diff --git a/mediagoblin/auth/forms.py b/mediagoblin/auth/forms.py
index daf7b993..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 _
@@ -24,18 +25,14 @@ class RegistrationForm(wtforms.Form):
_('Username'),
[wtforms.validators.Required(),
wtforms.validators.Length(min=3, max=30),
- wtforms.validators.Regexp(r'^\w+$')],
- description=_(
- u"This is the name other users will identify you with."))
+ wtforms.validators.Regexp(r'^\w+$')])
password = wtforms.PasswordField(
_('Password'),
[wtforms.validators.Required(),
wtforms.validators.Length(min=6, max=30),
wtforms.validators.EqualTo(
'confirm_password',
- _('Passwords must match.'))],
- description=_(
- u"Try to use a strong password!"))
+ _('Passwords must match.'))])
confirm_password = wtforms.PasswordField(
_('Confirm password'),
[wtforms.validators.Required()],
@@ -44,9 +41,7 @@ class RegistrationForm(wtforms.Form):
email = wtforms.TextField(
_('Email address'),
[wtforms.validators.Required(),
- wtforms.validators.Email()],
- description=_(
- u"Your email will never be published."))
+ wtforms.validators.Email()])
class LoginForm(wtforms.Form):
@@ -57,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 76c7ceed..912d89fa 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', '/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