tokens.py 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import base64
  2. from hashlib import sha256
  3. from time import time
  4. from django.conf import settings
  5. from django.utils import six
  6. from django.utils.encoding import force_bytes
  7. """
  8. Token creation
  9. Token is base encoded string containing three values:
  10. - days since unix epoch (so we can validate token expiration)
  11. - hash unique for current state of user model
  12. - token checksum for discovering manipulations
  13. """
  14. def make(user, token_type):
  15. user_hash = _make_hash(user, token_type)
  16. creation_day = _days_since_epoch()
  17. obfuscated = base64.b64encode(force_bytes('%s%s' % (user_hash, creation_day))).decode()
  18. obfuscated = obfuscated.rstrip('=')
  19. checksum = _make_checksum(obfuscated)
  20. return '%s%s' % (checksum, obfuscated)
  21. def is_valid(user, token_type, token):
  22. checksum = token[:8]
  23. obfuscated = token[8:]
  24. if checksum != _make_checksum(obfuscated):
  25. return False
  26. unobfuscated = base64.b64decode(obfuscated + '=' * (-len(obfuscated) % 4)).decode()
  27. user_hash = unobfuscated[:8]
  28. if user_hash != _make_hash(user, token_type):
  29. return False
  30. creation_day = int(unobfuscated[8:])
  31. return creation_day + 5 >= _days_since_epoch()
  32. def _make_hash(user, token_type):
  33. seeds = (
  34. user.pk, user.email, user.password, user.last_login.replace(microsecond=0, tzinfo=None),
  35. token_type, settings.SECRET_KEY,
  36. )
  37. return sha256(force_bytes('+'.join([six.text_type(s) for s in seeds]))).hexdigest()[:8]
  38. def _days_since_epoch():
  39. return int(time() / (25 * 3600))
  40. def _make_checksum(obfuscated):
  41. return sha256(force_bytes('%s:%s' % (settings.SECRET_KEY, obfuscated))).hexdigest()[:8]
  42. """
  43. Convenience functions for activation token
  44. """
  45. ACTIVATION_TOKEN = 'activation'
  46. def make_activation_token(user):
  47. return make(user, ACTIVATION_TOKEN)
  48. def is_activation_token_valid(user, token):
  49. return is_valid(user, ACTIVATION_TOKEN, token)
  50. """
  51. Convenience functions for password change token
  52. """
  53. PASSWORD_CHANGE_TOKEN = 'change_password'
  54. def make_password_change_token(user):
  55. return make(user, PASSWORD_CHANGE_TOKEN)
  56. def is_password_change_token_valid(user, token):
  57. return is_valid(user, PASSWORD_CHANGE_TOKEN, token)