strategy object used to generate and check tokens for the password reset mechanism return a token that can be used once to do a password reset for the given user check that a password reset token is correct for a given user parse the token check that the timestampuid has not been tampered with check the timestamp is within limit timestamp is number of seconds since 200111 converted to base 36 this gives us a 6 digit string until about 2069 hash the user s primary key email if available and some user state that s sure to change after a password reset to produce a token that is invalidated when it s used 1 the password field will change upon a password reset even if the same password is chosen due to password salting 2 the lastlogin field will usually be updated very shortly after a password reset failing those things settings passwordresettimeout eventually invalidates the token running this data through saltedhmac prevents password cracking attempts using the reset token provided the secret isn t compromised truncate microseconds so that tokens are consistent even if the database doesn t support microseconds used for mocking in tests strategy object used to generate and check tokens for the password reset mechanism return a token that can be used once to do a password reset for the given user check that a password reset token is correct for a given user parse the token check that the timestamp uid has not been tampered with check the timestamp is within limit timestamp is number of seconds since 2001 1 1 converted to base 36 this gives us a 6 digit string until about 2069 limit to shorten the url hash the user s primary key email if available and some user state that s sure to change after a password reset to produce a token that is invalidated when it s used 1 the password field will change upon a password reset even if the 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 eventually invalidates the token running this data through salted_hmac prevents password cracking attempts using the reset token provided the secret isn t compromised truncate microseconds so that tokens are consistent even if the database doesn t support microseconds used for mocking in tests
from datetime import datetime

from django.conf import settings
from django.utils.crypto import constant_time_compare, salted_hmac
from django.utils.http import base36_to_int, int_to_base36


class PasswordResetTokenGenerator:
    

    key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"
    algorithm = None
    _secret = None
    _secret_fallbacks = None

    def __init__(self):
        self.algorithm = self.algorithm or "sha256"

    def _get_secret(self):
        return self._secret or settings.SECRET_KEY

    def _set_secret(self, secret):
        self._secret = secret

    secret = property(_get_secret, _set_secret)

    def _get_fallbacks(self):
        if self._secret_fallbacks is None:
            return settings.SECRET_KEY_FALLBACKS
        return self._secret_fallbacks

    def _set_fallbacks(self, fallbacks):
        self._secret_fallbacks = fallbacks

    secret_fallbacks = property(_get_fallbacks, _set_fallbacks)

    def make_token(self, user):
        
        return self._make_token_with_timestamp(
            user,
            self._num_seconds(self._now()),
            self.secret,
        )

    def check_token(self, user, token):
        
        if not (user and token):
            return False
        
        try:
            ts_b36, _ = token.split("-")
        except ValueError:
            return False

        try:
            ts = base36_to_int(ts_b36)
        except ValueError:
            return False

        
        for secret in [self.secret, *self.secret_fallbacks]:
            if constant_time_compare(
                self._make_token_with_timestamp(user, ts, secret),
                token,
            ):
                break
        else:
            return False

        
        if (self._num_seconds(self._now()) - ts) > settings.PASSWORD_RESET_TIMEOUT:
            return False

        return True

    def _make_token_with_timestamp(self, user, timestamp, secret):
        
        
        ts_b36 = int_to_base36(timestamp)
        hash_string = salted_hmac(
            self.key_salt,
            self._make_hash_value(user, timestamp),
            secret=secret,
            algorithm=self.algorithm,
        ).hexdigest()[
            ::2
        ]  
        return "%s-%s" % (ts_b36, hash_string)

    def _make_hash_value(self, user, timestamp):
        
        
        
        login_timestamp = (
            ""
            if user.last_login is None
            else user.last_login.replace(microsecond=0, tzinfo=None)
        )
        email_field = user.get_email_field_name()
        email = getattr(user, email_field, "") or ""
        return f"{user.pk}{user.password}{login_timestamp}{timestamp}{email}"

    def _num_seconds(self, dt):
        return int((dt - datetime(2001, 1, 1)).total_seconds())

    def _now(self):
        
        return datetime.now()


default_token_generator = PasswordResetTokenGenerator()