Skip to content

Commit

Permalink
Make password reset emails store information in hardcache
Browse files Browse the repository at this point in the history
Password reset links now stored in the hardcache (they will exist
until the expiration, and never get evicted). Additionally,
if the password reset link has expired, the user will be
redirected to reddit.com/password?expired=true, with an error
message prompting them to try again.
  • Loading branch information
kemitche committed Jun 22, 2011
1 parent 7a0d21e commit e68b445
Show file tree
Hide file tree
Showing 5 changed files with 20 additions and 9 deletions.
4 changes: 2 additions & 2 deletions r2/r2/controllers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1665,12 +1665,12 @@ def POST_password(self, form, jquery, user):
form.set_html(".status", _("try again tomorrow"))


@validatedForm(cache_evt = VCacheKey('reset', ('key',)),
@validatedForm(cache_evt = VHardCacheKey('email-reset', ('key',)),
password = VPassword(['passwd', 'passwd2']))
def POST_resetpassword(self, form, jquery, cache_evt, password):
if form.has_errors('name', errors.EXPIRED):
cache_evt.clear()
form.redirect('/password')
form.redirect('/password?expired=true')
elif form.has_errors('passwd', errors.BAD_PASSWORD):
pass
elif form.has_errors('passwd2', errors.BAD_PASSWORD_MATCH):
Expand Down
6 changes: 3 additions & 3 deletions r2/r2/controllers/front.py
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,7 @@ def GET_verify_email(self, cache_evt, key, dest):
Award.give_if_needed("verified_email", c.user)
return self.redirect(dest)

@validate(cache_evt = VCacheKey('reset', ('key',)),
@validate(cache_evt = VHardCacheKey('email-reset', ('key',)),
key = nop('key'))
def GET_resetpassword(self, cache_evt, key):
"""page hit once a user has been sent a password reset email
Expand All @@ -829,10 +829,10 @@ def GET_resetpassword(self, cache_evt, key):

done = False
if not key and request.referer:
referer_path = request.referer.split(g.domain)[-1]
referer_path = request.referer.split(g.domain)[-1]
done = referer_path.startswith(request.fullpath)
elif not getattr(cache_evt, "user", None):
return self.abort404()
return self.redirect("/password?expired=true")
return BoringPage(_("reset password"),
content=ResetPassword(key=key, done=done)).render()

Expand Down
8 changes: 7 additions & 1 deletion r2/r2/controllers/validator/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -1154,13 +1154,14 @@ def clear(self):

class VCacheKey(Validator):
def __init__(self, cache_prefix, param, *a, **kw):
self.cache = g.cache
self.cache_prefix = cache_prefix
Validator.__init__(self, param, *a, **kw)

def run(self, key):
c_user = CachedUser(self.cache_prefix, None, key)
if key:
uid = g.cache.get(str(self.cache_prefix + "_" + key))
uid = self.cache.get(str(self.cache_prefix + "_" + key))
if uid:
try:
c_user.user = Account._byID(uid, data = True)
Expand All @@ -1169,6 +1170,11 @@ def run(self, key):
return c_user
self.set_error(errors.EXPIRED)

class VHardCacheKey(VCacheKey):
def __init__(self, cache_prefix, param, *a, **kw):
VCacheKey.__init__(self, cache_prefix, param, *a, **kw)
self.cache = g.hardcache

class VOneOf(Validator):
def __init__(self, param, options = (), *a, **kw):
Validator.__init__(self, param, *a, **kw)
Expand Down
6 changes: 3 additions & 3 deletions r2/r2/lib/emailer.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,20 @@ def password_email(user):
"""
from r2.lib.pages import PasswordReset

reset_count_key = "reset_count_%s" % user._id
reset_count_key = "email-reset_count_%s" % user._id
g.cache.add(reset_count_key, 0, time=3600 * 12)
if g.cache.incr(reset_count_key) > 3:
return False

reset_count_global = "reset_count_global"
reset_count_global = "email-reset_count_global"
g.cache.add(reset_count_global, 0, time=3600)
if g.cache.incr(reset_count_global) > 1000:
raise ValueError("Somebody's beating the hell out of the password reset box")

key = passhash(randstr(64, reallyrandom = True), user.email)
passlink = 'http://' + g.domain + '/resetpassword/' + key
g.log.info("Generated password reset link: " + passlink)
g.cache.set("reset_%s" % key, user._id, time=1800)
g.hardcache.set("email-reset_%s" % key, user._id, time=3600 * 12)
_system_email(user.email,
PasswordReset(user=user,
passlink=passlink).render(style='email'),
Expand Down
5 changes: 5 additions & 0 deletions r2/r2/templates/password.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
onsubmit="return post_form(this, 'password');">
<h1>${_("what's my password?")}</h1>
<p>${_("enter your user name below to receive your login information")}</p>
%if request.params.get('expired'):
<span class="error">
${_("password reset link expired. please try again")}
</span>
%endif

<div class="spacer">
<%utils:round_field title="${_('username')}">
Expand Down

0 comments on commit e68b445

Please sign in to comment.