@@ -24,3 +24,8 @@ class EmailNotProvided(Exception): class UserAlreadyExists(Exception): def __str__(self): return _('User already exists') + + +class DomainNotFound(Exception): + def __str__(self): + return _('Domain not found') @@ -1,10 +1,13 @@ import logging +import dns.resolver + from django.conf import settings from django.core.mail import EmailMessage, send_mail from django.utils.html import format_html, strip_tags from django.utils.translation import gettext_lazy as _ +from authentication.exceptions.user import DomainNotFound from authentication.models import BusinessAccount, BusinessUserHost from authentication.models.user import CustomUserModel from authentication.services.email_token_service import EmailTokenService @@ -29,6 +32,8 @@ class EmailService: def send_email(self, subject: str, message: str, user_email: str): try: + email_domain = user_email.split('@')[-1] + dns.resolver.resolve(email_domain, 'MX') send_mail( subject=subject, message=strip_tags(message), @@ -37,6 +42,8 @@ class EmailService: recipient_list=(user_email,), auth_password=settings.EMAIL_HOST_PASSWORD, ) + except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN, dns.exception.Timeout): + raise DomainNotFound except Exception as exc: logger.exception(exc) raise Exception(_('Error occured when proceed email sending')) @@ -83,6 +83,10 @@ class UserService: ReferralAccountService.create_invite(referer_account=referer.referral_account, invitee=user) except CustomUserModel.DoesNotExist: pass + + email_domain = user.email.split('@')[-1] + if email_domain not in settings.VALID_EMAIL_DOMAINS: + send_corporate_offer_task.apply_async(args=[user.email], countdown=5) return user def create_user_telegram(self, request: Request) -> TelegramUser: @@ -25,7 +25,7 @@ from authentication.exceptions import BaseAlready from authentication.exceptions.business_host_exceptions.not_allowed_ip import ( NotAllowedIP, ) -from authentication.exceptions.user import EmailNotConfirmed, UserAlreadyExists, WrongEmail, WrongPassword +from authentication.exceptions.user import EmailNotConfirmed, UserAlreadyExists, WrongEmail, WrongPassword, DomainNotFound from authentication.models.business_group import BusinessGroup from authentication.models.business_host import BusinessUserHost from authentication.models.choices import AccountPrivileges, InvitationStatus @@ -206,6 +206,8 @@ class UserAPIView(APIView): return Response(status=status.HTTP_201_CREATED) except (UserAlreadyExists, ValidationError) as exc: return Response({'detail': f'{exc}'}, status=status.HTTP_400_BAD_REQUEST) + except DomainNotFound: + return Response({'detail': _('Email not found')}, status=status.HTTP_400_BAD_REQUEST) except Exception as exc: logger.exception(exc) return Response({'detail': _('Unexpected error')}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-04-21 13:53+0300\n" +"POT-Creation-Date: 2025-04-21 16:56+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -88,6 +88,10 @@ msgstr "Пользователь не может быть зарегистрир msgid "User already exists" msgstr "Пользователь уже существует" +#: authentication/exceptions/user.py:31 +msgid "Domain not found" +msgstr "Домен не найден" + #: authentication/models/business_account.py:16 payments/models/promocode.py:72 #: tools/public_api/models.py:33 msgid "Owner" @@ -463,11 +467,11 @@ msgstr "" msgid "No business account by this uid at your company" msgstr "Такого аккаунта нет в вашей компании" -#: authentication/services/email_service.py:38 +#: authentication/services/email_service.py:45 msgid "Error occured when proceed email sending" msgstr "Случилась ошибка во время отправки email" -#: authentication/services/email_service.py:115 +#: authentication/services/email_service.py:122 msgid "Regular users cannot send introductory letters" msgstr "Обычные пользователи не могут отсылать письма" @@ -503,10 +507,14 @@ msgstr "Пароли не совпадают" msgid "Current password is wrong" msgstr "Текущий пароль неверен" -#: authentication/views.py:105 authentication/views.py:210 +#: authentication/views.py:107 authentication/views.py:214 msgid "Unexpected error" msgstr "Непредвиденная ошибка" +#: authentication/views.py:211 +msgid "Email not found" +msgstr "Email не найден" + #: backend/urls.py:30 msgid "Requested object does not exists" msgstr "" @@ -1396,6 +1396,27 @@ compatible-mypy = ["django-stubs[compatible-mypy]", "mypy (>=1.7.0,<1.8.0)"] coreapi = ["coreapi (>=2.0.0)"] markdown = ["types-Markdown (>=0.1.5)"] +[[package]] +name = "dnspython" +version = "2.7.0" +description = "DNS toolkit" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=43)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=1.0.0)"] +idna = ["idna (>=3.7)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + [[package]] name = "docx2txt" version = "0.8" @@ -3846,6 +3867,7 @@ files = [ {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}, {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}, {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"}, @@ -5903,4 +5925,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"] [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "11aef76704403641d1d4f24a0e6525cb38c7fcf331df77983fd1da009dd8116d" +content-hash = "68bf294fcac8c0c497e4825caae12a59ec7cfe58eac8f82b8889dfd6199f3916" @@ -63,6 +63,7 @@ pypandoc = "^1.15" faiss-cpu = "^1.10.0" replicate = "^1.0.4" ruff = "^0.9.9" +dnspython = "^2.7.0" [tool.poetry.group.test.dependencies]