1+ import hashlib
2+
13from django .conf import settings
24from django .contrib .auth .base_user import BaseUserManager
35from django .contrib .auth .models import AbstractUser
@@ -18,6 +20,7 @@ def _create_user(self, email, **extra_fields):
1820 """Create and save a user with the given email."""
1921 email = self .normalize_email (email )
2022 user = self .model (email = email , ** extra_fields )
23+ user .email_hash = hashlib .md5 (email .lower ().encode ()).hexdigest ()
2124 user .save (using = self ._db )
2225 return user
2326
@@ -50,9 +53,24 @@ class AbstractEmailUser(AbstractUser):
5053 username = None
5154 password = None
5255
53- email = CIEmailField (_ ("email address" ), unique = True , db_index = True )
56+ email = CIEmailField (
57+ _ ("email address" ), blank = True , null = True , unique = True , db_index = True
58+ )
5459 """Unique and case insensitive to serve as a better username."""
5560
61+ email_hash = models .TextField (db_index = True , editable = False )
62+ """
63+ A hash of the email address, to erase the email address without loosing the user.
64+
65+ GDPR may require us to erase the email address, yet we still want to be able to
66+ retain non-personal information for statistical or other legal purposes. We may
67+ also want to be able to authenticate users without ever storing the email address.
68+
69+ A hash, being non-reversible in nature, means that the personal information is
70+ not stored and you may only processes personal information during runtime for
71+ the explicit purpose of authenticating the user.
72+ """
73+
5674 session_salt = models .CharField (
5775 max_length = 12 ,
5876 editable = False ,
@@ -68,6 +86,19 @@ def has_usable_password(self):
6886 class Meta (AbstractUser .Meta ):
6987 abstract = True
7088
89+ def __init__ (self , * args , ** kwargs ):
90+ super ().__init__ (* args , ** kwargs )
91+ self ._email = self .email
92+
93+ def save (self , * args , ** kwargs ):
94+ if self .email and ((self ._email != self .email ) or not self .pk ):
95+ self .email_hash = hashlib .md5 (self .email .lower ().encode ()).digest ()
96+ try :
97+ kwargs ["update_fields" ] = {* kwargs .pop ("update_fields" ), "email_hash" }
98+ except KeyError :
99+ pass
100+ super ().save (* args , ** kwargs )
101+
71102 def _legacy_get_session_auth_hash (self ):
72103 # RemovedInDjango40Warning: pre-Django 3.1 hashes will be invalid.
73104 key_salt = "mailauth.contrib.user.models.EmailUserManager.get_session_auth_hash"
0 commit comments