This commit is contained in:
Iliyan Angelov
2025-09-14 23:24:25 +03:00
commit c67067a2a4
71311 changed files with 6800714 additions and 0 deletions

View File

@@ -0,0 +1,275 @@
import datetime
from django.contrib.auth import get_user_model
from django.core import signing
from django.db import models
from django.db.models import Index, Q
from django.db.models.constraints import UniqueConstraint
from django.db.models.functions import Upper
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from .. import app_settings as allauth_app_settings
from . import app_settings, signals
from .adapter import get_adapter
from .managers import EmailAddressManager, EmailConfirmationManager
class EmailAddress(models.Model):
user = models.ForeignKey(
allauth_app_settings.USER_MODEL,
verbose_name=_("user"),
on_delete=models.CASCADE,
)
email = models.EmailField(
max_length=app_settings.EMAIL_MAX_LENGTH,
verbose_name=_("email address"),
)
verified = models.BooleanField(verbose_name=_("verified"), default=False)
primary = models.BooleanField(verbose_name=_("primary"), default=False)
objects = EmailAddressManager()
class Meta:
verbose_name = _("email address")
verbose_name_plural = _("email addresses")
unique_together = [("user", "email")]
if app_settings.UNIQUE_EMAIL:
constraints = [
UniqueConstraint(
fields=["email"],
name="unique_verified_email",
condition=Q(verified=True),
)
]
indexes = [Index(Upper("email"), name="account_emailaddress_upper")]
def __str__(self):
return self.email
def can_set_verified(self):
if self.verified:
return True
conflict = False
if app_settings.UNIQUE_EMAIL:
conflict = (
EmailAddress.objects.exclude(pk=self.pk)
.filter(verified=True, email__iexact=self.email)
.exists()
)
return not conflict
def set_verified(self, commit=True):
if self.verified:
return True
if self.can_set_verified():
self.verified = True
if commit:
self.save(update_fields=["verified"])
return self.verified
def set_as_primary(self, conditional=False):
"""Marks the email address as primary. In case of `conditional`, it is
only marked as primary if there is no other primary email address set.
"""
from allauth.account.utils import user_email
old_primary = EmailAddress.objects.get_primary(self.user)
if old_primary:
if conditional:
return False
old_primary.primary = False
old_primary.save()
self.primary = True
self.save()
user_email(self.user, self.email, commit=True)
return True
def send_confirmation(self, request=None, signup=False):
if app_settings.EMAIL_CONFIRMATION_HMAC:
confirmation = EmailConfirmationHMAC(self)
else:
confirmation = EmailConfirmation.create(self)
confirmation.send(request, signup=signup)
return confirmation
def remove(self):
from allauth.account.utils import user_email
self.delete()
if user_email(self.user) == self.email:
alt = (
EmailAddress.objects.filter(user=self.user)
.order_by("-verified")
.first()
)
alt_email = ""
if alt:
alt_email = alt.email
user_email(self.user, alt_email, commit=True)
class EmailConfirmationMixin:
def confirm(self, request):
email_address = self.email_address
if not email_address.verified:
confirmed = get_adapter().confirm_email(request, email_address)
if confirmed:
signals.email_confirmed.send(
sender=self.__class__,
request=request,
email_address=email_address,
)
return email_address
def send(self, request=None, signup=False):
get_adapter().send_confirmation_mail(request, self, signup)
signals.email_confirmation_sent.send(
sender=self.__class__,
request=request,
confirmation=self,
signup=signup,
)
class EmailConfirmation(EmailConfirmationMixin, models.Model):
email_address = models.ForeignKey(
EmailAddress,
verbose_name=_("email address"),
on_delete=models.CASCADE,
)
created = models.DateTimeField(verbose_name=_("created"), default=timezone.now)
sent = models.DateTimeField(verbose_name=_("sent"), null=True)
key = models.CharField(verbose_name=_("key"), max_length=64, unique=True)
objects = EmailConfirmationManager()
class Meta:
verbose_name = _("email confirmation")
verbose_name_plural = _("email confirmations")
def __str__(self):
return "confirmation for %s" % self.email_address
@classmethod
def create(cls, email_address):
key = get_adapter().generate_emailconfirmation_key(email_address.email)
return cls._default_manager.create(email_address=email_address, key=key)
def key_expired(self):
expiration_date = self.sent + datetime.timedelta(
days=app_settings.EMAIL_CONFIRMATION_EXPIRE_DAYS
)
return expiration_date <= timezone.now()
key_expired.boolean = True
def confirm(self, request):
if not self.key_expired():
return super().confirm(request)
def send(self, request=None, signup=False):
super().send(request=request, signup=signup)
self.sent = timezone.now()
self.save()
class EmailConfirmationHMAC(EmailConfirmationMixin, object):
def __init__(self, email_address):
self.email_address = email_address
@property
def key(self):
return signing.dumps(obj=self.email_address.pk, salt=app_settings.SALT)
@classmethod
def from_key(cls, key):
try:
max_age = 60 * 60 * 24 * app_settings.EMAIL_CONFIRMATION_EXPIRE_DAYS
pk = signing.loads(key, max_age=max_age, salt=app_settings.SALT)
ret = EmailConfirmationHMAC(EmailAddress.objects.get(pk=pk, verified=False))
except (
signing.SignatureExpired,
signing.BadSignature,
EmailAddress.DoesNotExist,
):
ret = None
return ret
class Login:
"""
Represents a user that is in the process of logging in.
"""
def __init__(
self,
user,
email_verification,
redirect_url=None,
signal_kwargs=None,
signup=False,
email=None,
state=None,
):
self.user = user
self.email_verification = email_verification
self.redirect_url = redirect_url
self.signal_kwargs = signal_kwargs
self.signup = signup
self.email = email
self.state = {} if state is None else state
def serialize(self):
from allauth.account.utils import user_pk_to_url_str
# :-( Knowledge of the `socialaccount` is entering the `account` app.
signal_kwargs = self.signal_kwargs
if signal_kwargs is not None:
sociallogin = signal_kwargs.get("sociallogin")
if sociallogin is not None:
signal_kwargs = signal_kwargs.copy()
signal_kwargs["sociallogin"] = sociallogin.serialize()
data = {
"user_pk": user_pk_to_url_str(self.user),
"email_verification": self.email_verification,
"signup": self.signup,
"redirect_url": self.redirect_url,
"email": self.email,
"signal_kwargs": signal_kwargs,
"state": self.state,
}
return data
@classmethod
def deserialize(cls, data):
from allauth.account.utils import url_str_to_user_pk
from allauth.socialaccount.models import SocialLogin
user = (
get_user_model()
.objects.filter(pk=url_str_to_user_pk(data["user_pk"]))
.first()
)
if user is None:
raise ValueError()
try:
# :-( Knowledge of the `socialaccount` is entering the `account` app.
signal_kwargs = data["signal_kwargs"]
if signal_kwargs is not None:
sociallogin = signal_kwargs.get("sociallogin")
if sociallogin is not None:
signal_kwargs = signal_kwargs.copy()
signal_kwargs["sociallogin"] = SocialLogin.deserialize(sociallogin)
return Login(
user=user,
email_verification=data["email_verification"],
redirect_url=data["redirect_url"],
signup=data["signup"],
signal_kwargs=signal_kwargs,
state=data["state"],
)
except KeyError:
raise ValueError()