tokens.py 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  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,
  35. user.email,
  36. user.password,
  37. user.last_login.replace(microsecond=0, tzinfo=None),
  38. token_type,
  39. settings.SECRET_KEY,
  40. )
  41. return sha256(force_bytes(
  42. '+'.join([six.text_type(s) for s in seeds]))).hexdigest()[:8]
  43. def _days_since_epoch():
  44. return int(time() / (25 * 3600))
  45. def _make_checksum(obfuscated):
  46. return sha256(force_bytes(
  47. '%s:%s' % (settings.SECRET_KEY, obfuscated))).hexdigest()[:8]
  48. """
  49. Convenience functions for activation token
  50. """
  51. ACTIVATION_TOKEN = 'activation'
  52. def make_activation_token(user):
  53. return make(user, ACTIVATION_TOKEN)
  54. def is_activation_token_valid(user, token):
  55. return is_valid(user, ACTIVATION_TOKEN, token)
  56. """
  57. Convenience functions for password change token
  58. """
  59. PASSWORD_CHANGE_TOKEN = 'change_password'
  60. def make_password_change_token(user):
  61. return make(user, PASSWORD_CHANGE_TOKEN)
  62. def is_password_change_token_valid(user, token):
  63. return is_valid(user, PASSWORD_CHANGE_TOKEN, token)