mirror of
https://github.com/django/django.git
synced 2025-10-27 07:36:08 +00:00
Fixed #28622 -- Allowed specifying password reset link expiration in seconds and deprecated PASSWORD_RESET_TIMEOUT_DAYS.
This commit is contained in:
committed by
Mariusz Felisiak
parent
0719edcd5f
commit
226ebb1729
@@ -1,4 +1,4 @@
|
||||
from datetime import date
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.crypto import constant_time_compare, salted_hmac
|
||||
@@ -18,7 +18,7 @@ class PasswordResetTokenGenerator:
|
||||
Return a token that can be used once to do a password reset
|
||||
for the given user.
|
||||
"""
|
||||
return self._make_token_with_timestamp(user, self._num_days(self._today()))
|
||||
return self._make_token_with_timestamp(user, self._num_seconds(self._now()))
|
||||
|
||||
def check_token(self, user, token):
|
||||
"""
|
||||
@@ -41,19 +41,15 @@ class PasswordResetTokenGenerator:
|
||||
if not constant_time_compare(self._make_token_with_timestamp(user, ts), token):
|
||||
return False
|
||||
|
||||
# Check the timestamp is within limit. Timestamps are rounded to
|
||||
# midnight (server time) providing a resolution of only 1 day. If a
|
||||
# link is generated 5 minutes before midnight and used 6 minutes later,
|
||||
# that counts as 1 day. Therefore, PASSWORD_RESET_TIMEOUT_DAYS = 1 means
|
||||
# "at least 1 day, could be up to 2."
|
||||
if (self._num_days(self._today()) - ts) > settings.PASSWORD_RESET_TIMEOUT_DAYS:
|
||||
# Check the timestamp is within limit.
|
||||
if (self._num_seconds(self._now()) - ts) > settings.PASSWORD_RESET_TIMEOUT:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _make_token_with_timestamp(self, user, timestamp):
|
||||
# timestamp is number of days since 2001-1-1. Converted to
|
||||
# base 36, this gives us a 3 digit string until about 2121
|
||||
# timestamp is number of seconds since 2001-1-1. Converted to base 36,
|
||||
# this gives us a 6 digit string until about 2069.
|
||||
ts_b36 = int_to_base36(timestamp)
|
||||
hash_string = salted_hmac(
|
||||
self.key_salt,
|
||||
@@ -71,7 +67,7 @@ class PasswordResetTokenGenerator:
|
||||
same password is chosen, due to password salting).
|
||||
2. The last_login field will usually be updated very shortly after
|
||||
a password reset.
|
||||
Failing those things, settings.PASSWORD_RESET_TIMEOUT_DAYS eventually
|
||||
Failing those things, settings.PASSWORD_RESET_TIMEOUT eventually
|
||||
invalidates the token.
|
||||
|
||||
Running this data through salted_hmac() prevents password cracking
|
||||
@@ -82,12 +78,12 @@ class PasswordResetTokenGenerator:
|
||||
login_timestamp = '' if user.last_login is None else user.last_login.replace(microsecond=0, tzinfo=None)
|
||||
return str(user.pk) + user.password + str(login_timestamp) + str(timestamp)
|
||||
|
||||
def _num_days(self, dt):
|
||||
return (dt - date(2001, 1, 1)).days
|
||||
def _num_seconds(self, dt):
|
||||
return int((dt - datetime(2001, 1, 1)).total_seconds())
|
||||
|
||||
def _today(self):
|
||||
def _now(self):
|
||||
# Used for mocking in tests
|
||||
return date.today()
|
||||
return datetime.now()
|
||||
|
||||
|
||||
default_token_generator = PasswordResetTokenGenerator()
|
||||
|
||||
Reference in New Issue
Block a user