diff --git a/app/app/admin_model.py b/app/app/admin_model.py
index 565e4b6..a97d41e 100644
--- a/app/app/admin_model.py
+++ b/app/app/admin_model.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
from typing import Optional
import arrow
@@ -30,6 +31,8 @@ from app.models import (
Newsletter,
PADDLE_SUBSCRIPTION_GRACE_DAYS,
Mailbox,
+ DeletedAlias,
+ DomainDeletedAlias,
)
from app.newsletter_utils import send_newsletter_to_user, send_newsletter_to_address
@@ -729,6 +732,67 @@ class InvalidMailboxDomainAdmin(SLModelView):
can_delete = True
+class EmailSearchResult:
+ no_match: bool = True
+ alias: Optional[Alias] = None
+ mailbox: Optional[Mailbox] = None
+ deleted_alias: Optional[DeletedAlias] = None
+ deleted_custom_alias: Optional[DomainDeletedAlias] = None
+ user: Optional[User] = None
+
+ @staticmethod
+ def from_email(email: str) -> EmailSearchResult:
+ output = EmailSearchResult()
+ alias = Alias.get_by(email=email)
+ if alias:
+ output.alias = alias
+ output.no_match = False
+ return output
+ user = User.get_by(email=email)
+ if user:
+ output.user = user
+ output.no_match = False
+ return output
+ mailbox = Mailbox.get_by(email=email)
+ if mailbox:
+ output.mailbox = mailbox
+ output.no_match = False
+ return output
+ deleted_alias = DeletedAlias.get_by(email=email)
+ if deleted_alias:
+ output.deleted_alias = deleted_alias
+ output.no_match = False
+ return output
+ domain_deleted_alias = DomainDeletedAlias.get_by(email=email)
+ if domain_deleted_alias:
+ output.domain_deleted_alias = domain_deleted_alias
+ output.no_match = False
+ return output
+
+
+class EmailSearchHelpers:
+ @staticmethod
+ def mailbox_list(user: User) -> list[Mailbox]:
+ return (
+ Mailbox.filter_by(user_id=user.id)
+ .order_by(Mailbox.id.asc())
+ .limit(10)
+ .all()
+ )
+
+ @staticmethod
+ def mailbox_count(user: User) -> int:
+ return Mailbox.filter_by(user_id=user.id).order_by(Mailbox.id.asc()).count()
+
+ @staticmethod
+ def alias_list(user: User) -> list[Alias]:
+ return Alias.filter_by(user_id=user.id).order_by(Alias.id.asc()).limit(10).all()
+
+ @staticmethod
+ def alias_count(user: User) -> int:
+ return Alias.filter_by(user_id=user.id).count()
+
+
class EmailSearchAdmin(BaseView):
def is_accessible(self):
return current_user.is_authenticated and current_user.is_admin
@@ -740,25 +804,16 @@ class EmailSearchAdmin(BaseView):
@expose("/", methods=["GET", "POST"])
def index(self):
- alias = None
- user = None
- mailbox = None
- no_match = False
- email = None
+ search = EmailSearchResult()
+ email = ""
if request.form and request.form["email"]:
email = request.form["email"]
- alias = Alias.get_by(email=email)
- user = User.get_by(email=email)
- mailbox = Mailbox.get_by(email=email)
- if not alias and not user and not mailbox:
- no_match = True
+ email = email.strip()
+ search = EmailSearchResult.from_email(email)
return self.render(
- "admin/alias_search.html",
+ "admin/email_search.html",
email=email,
- no_match=no_match,
- alias=alias,
- mailbox=mailbox,
- user=user,
- user_aliases=lambda user_id: Alias.filter_by(user_id=user_id).all(),
+ data=search,
+ helper=EmailSearchHelpers,
)
diff --git a/app/app/alias_utils.py b/app/app/alias_utils.py
index e0aeb6a..f4ff0d3 100644
--- a/app/app/alias_utils.py
+++ b/app/app/alias_utils.py
@@ -63,12 +63,16 @@ def get_user_if_alias_would_auto_create(
# Prevent addresses with unicode characters (🤯) in them for now.
validate_email(address, check_deliverability=False, allow_smtputf8=False)
except EmailNotValidError:
+ LOG.i(f"Not creating alias for {address} because email is invalid")
return None
domain_and_rule = check_if_alias_can_be_auto_created_for_custom_domain(
address, notify_user=notify_user
)
if DomainDeletedAlias.get_by(email=address):
+ LOG.i(
+ f"Not creating alias for {address} because it was previously deleted for this domain"
+ )
return None
if domain_and_rule:
return domain_and_rule[0].user
@@ -93,6 +97,9 @@ def check_if_alias_can_be_auto_created_for_custom_domain(
custom_domain: CustomDomain = CustomDomain.get_by(domain=alias_domain)
if not custom_domain:
+ LOG.i(
+ f"Cannot auto-create custom domain alias for {address} because there's no custom domain for {alias_domain}"
+ )
return None
user: User = custom_domain.user
@@ -108,6 +115,9 @@ def check_if_alias_can_be_auto_created_for_custom_domain(
if not custom_domain.catch_all:
if len(custom_domain.auto_create_rules) == 0:
+ LOG.i(
+ f"Cannot create alias {address} for domain {custom_domain} because it has no catch-all and no rules"
+ )
return None
local = get_email_local_part(address)
@@ -121,7 +131,7 @@ def check_if_alias_can_be_auto_created_for_custom_domain(
)
return custom_domain, rule
else: # no rule passes
- LOG.d("no rule passed to create %s", local)
+ LOG.d(f"No rule matches auto-create {address} for domain {custom_domain}")
return None
LOG.d("Create alias via catchall")
@@ -148,6 +158,7 @@ def check_if_alias_can_be_auto_created_for_a_directory(
sep = "#"
else:
# if there's no directory separator in the alias, no way to auto-create it
+ LOG.info(f"Cannot auto-create {address} since it has no directory separator")
return None
directory_name = address[: address.find(sep)]
@@ -155,6 +166,9 @@ def check_if_alias_can_be_auto_created_for_a_directory(
directory = Directory.get_by(name=directory_name)
if not directory:
+ LOG.info(
+ f"Cannot auto-create {address} because there is no directory for {directory_name}"
+ )
return None
user: User = directory.user
@@ -163,12 +177,17 @@ def check_if_alias_can_be_auto_created_for_a_directory(
return None
if not user.can_create_new_alias():
- LOG.d(f"{user} can't create new directory alias {address}")
+ LOG.d(
+ f"{user} can't create new directory alias {address} because user cannot create aliases"
+ )
if notify_user:
send_cannot_create_directory_alias(user, address, directory_name)
return None
if directory.disabled:
+ LOG.d(
+ f"{user} can't create new directory alias {address} bcause directory is disabled"
+ )
if notify_user:
send_cannot_create_directory_alias_disabled(user, address, directory_name)
return None
diff --git a/app/app/email_utils.py b/app/app/email_utils.py
index 6048a99..5ff34d0 100644
--- a/app/app/email_utils.py
+++ b/app/app/email_utils.py
@@ -548,7 +548,9 @@ def can_create_directory_for_address(email_address: str) -> bool:
for domain in config.ALIAS_DOMAINS:
if email_address.endswith("@" + domain):
return True
-
+ LOG.i(
+ f"Cannot create address in directory for {email_address} since it does not belong to a valid directory domain"
+ )
return False
diff --git a/app/scripts/reset_local_db.sh b/app/scripts/reset_local_db.sh
index 42dae95..422c2a8 100755
--- a/app/scripts/reset_local_db.sh
+++ b/app/scripts/reset_local_db.sh
@@ -3,5 +3,5 @@
export DB_URI=postgresql://myuser:mypassword@localhost:15432/simplelogin
echo 'drop schema public cascade; create schema public;' | psql $DB_URI
-rye run alembic upgrade head
-rye run flask dummy-data
+poetry run alembic upgrade head
+poetry run flask dummy-data
diff --git a/app/scripts/reset_test_db.sh b/app/scripts/reset_test_db.sh
index 234cef3..2546601 100755
--- a/app/scripts/reset_test_db.sh
+++ b/app/scripts/reset_test_db.sh
@@ -3,4 +3,4 @@
export DB_URI=postgresql://myuser:mypassword@localhost:15432/test
echo 'drop schema public cascade; create schema public;' | psql $DB_URI
-rye run alembic upgrade head
+poetry run alembic upgrade head
diff --git a/app/templates/admin/alias_search.html b/app/templates/admin/alias_search.html
deleted file mode 100644
index 159d733..0000000
--- a/app/templates/admin/alias_search.html
+++ /dev/null
@@ -1,300 +0,0 @@
-{% extends 'admin/master.html' %}
-
-{% block body %}
-
-
- {% if no_match %}
-
-
- No user, alias or mailbox found for {{ email }}
-
- {% endif %}
- {% if alias %}
-
-
-
Alias {{ alias.email }} found
-
- -
- Alias id
-
- -
- {{ alias.id }}
-
- -
- Email
-
- -
- {{ alias.email }}
-
- -
- Created at
-
- -
- {{ alias.created_at }}
-
- -
- User
-
- -
-
- -
- User id
-
- -
- {{ alias.user.id }}
-
- -
- Email
-
- -
- {{ alias.user.email }}
-
- -
- Premium
-
- -
- {{ alias.user.is_premium() }}
-
- -
- Disabled
-
- -
- {{ alias.user.disabled }}
-
- -
- Crated At
-
- -
- {{ alias.user.created_at }}
-
- -
- Mailboxes
-
- -
- {% for mailbox in alias.mailboxes %}
-
-
- -
- Mailbox id
-
- -
- {{ mailbox.id }}
-
- -
- Email
-
- -
- {{ mailbox.email }}
-
- -
- Verified
-
- -
- {{ mailbox.verified }}
-
- -
- Created At
-
- -
- {{ mailbox.created_at }}
-
-
- {% endfor %}
-
-
-
-
-
- {% endif %}
- {% if user %}
-
-
-
User {{ user.email }} found
-
- -
- User id
-
- -
- {{ user.id }}
-
- -
- Email
-
- -
- {{ user.email }}
-
- {% if user.is_paid() %}
-
- -
- Paid
-
- -
- Yes
-
- -
- Subscription
-
- -
- {{ user.get_active_subscription() }}
-
- {% else %}
- -
- Paid
-
- -
- No
-
- {% endif %}
- -
- Created at
-
- -
- {{ user.created_at }}
-
- -
- Mailboxes
-
- -
- {% for mailbox in user.mailboxes() %}
-
-
- -
- Mailbox id
-
- -
- {{ mailbox.id }}
-
- -
- Email
-
- -
- {{ mailbox.email }}
-
- -
- Verified
-
- -
- {{ mailbox.verified }}
-
- -
- Created At
-
- -
- {{ mailbox.created_at }}
-
-
- {% endfor %}
-
- -
- Aliases
-
- -
- {% for mailbox in user_aliases(user.id) %}
-
-
- -
- Mailbox id
-
- -
- {{ mailbox.id }}
-
- -
- Email
-
- -
- {{ mailbox.email }}
-
- -
- Verified
-
- -
- {{ mailbox.verified }}
-
- -
- Created At
-
- -
- {{ mailbox.created_at }}
-
-
- {% endfor %}
-
-
-
- {% endif %}
- {% if mailbox %}
-
-
-
Mailbox {{ mailbox.email }} found
-
- -
- Mailbox id
-
- -
- {{ mailbox.id }}
-
- -
- Email
-
- -
- {{ mailbox.email }}
-
- -
- Created at
-
- -
- {{ mailbox.created_at }}
-
- -
- User
-
- -
-
- -
- User id
-
- -
- {{ mailbox.user.id }}
-
- -
- Email
-
- -
- {{ mailbox.user.email }}
-
- -
- Premium
-
- -
- {{ mailbox.user.is_premium() }}
-
- -
- Disabled
-
- -
- {{ mailbox.user.disabled }}
-
- -
- Crated At
-
- -
- {{ mailbox.user.created_at }}
-
-
-
-
-
- {% endif %}
-{% endblock %}
diff --git a/app/templates/admin/email_search.html b/app/templates/admin/email_search.html
new file mode 100644
index 0000000..537d03c
--- /dev/null
+++ b/app/templates/admin/email_search.html
@@ -0,0 +1,251 @@
+{% extends 'admin/master.html' %}
+
+{% macro show_user(user) -%}
+ User {{ user.email }} with ID {{ user.id }}.
+
+
+
+ User ID |
+ Email |
+ Paid |
+ Subscription |
+ Created At |
+
+
+
+
+ {{ user.id }} |
+ {{ user.email }} |
+ {{ "yes" if user.is_paid() else No }} |
+ {{ user.get_active_subscription() }} |
+ {{ user.created_at }} |
+
+
+
+{%- endmacro %}
+{% macro list_mailboxes(mbox_count, mboxes) %}
+
+ {{ mbox_count }} Mailboxes found.
+ {% if mbox_count>10 %}Showing only the first 10.{% endif %}
+
+
+
+
+ Mailbox ID |
+ Email |
+ Verified |
+ Created At |
+
+
+
+ {% for mailbox in mboxes %}
+
+
+ {{ mailbox.id }} |
+ {{ mailbox.email }} |
+ {{ "Yes" if mailbox.verified else "No" }} |
+ {{ mailbox.created_at }} |
+
+ {% endfor %}
+
+
+{% endmacro %}
+{% macro list_alias(alias_count, aliases) %}
+
+ {{ alias_count }} Aliases found.
+ {% if alias_count>10 %}Showing only the first 10.{% endif %}
+
+
+
+
+ Alias ID |
+ Email |
+ Verified |
+ Created At |
+
+
+
+ {% for alias in aliases %}
+
+
+ {{ alias.id }} |
+ {{ alias.email }} |
+ {{ "Yes" if alias.verified else "No" }} |
+
+ {{ alias.created_at }}
+ |
+
+ {% endfor %}
+
+
+{% endmacro %}
+{% macro show_deleted_alias(deleted_alias) -%}
+
+ Deleted Alias {{ deleted_alias.email }} with ID {{ deleted_alias.id }}.
+
+
+
+
+
+ Deleted Alias ID
+ |
+
+ Email
+ |
+
+ Deleted At
+ |
+
+ Reason
+ |
+
+
+
+
+
+ {{ deleted_alias.id }}
+ |
+
+ {{ deleted_alias.email }}
+ |
+
+ {{ deleted_alias.created_at }}
+ |
+
+ {{ deleted_alias.reason }}
+ |
+
+
+
+{%- endmacro %}
+{% macro show_domain_deleted_alias(dom_deleted_alias) -%}
+
+ Domain Deleted Alias {{ dom_deleted_alias.email }} with ID {{ dom_deleted_alias.id }} for domain {{ dom_deleted_alias.domain.domain }}
+
+
+
+
+
+ Deleted Alias ID
+ |
+
+ Email
+ |
+
+ Domain
+ |
+
+ Domain ID
+ |
+
+ Domain owner user ID
+ |
+
+ Domain owner user email
+ |
+
+ Deleted At
+ |
+
+
+
+
+
+ {{ dom_deleted_alias.id }}
+ |
+
+ {{ dom_deleted_alias.email }}
+ |
+
+ {{ dom_deleted_alias.domain.domain }}
+ |
+
+ {{ dom_deleted_alias.domain.id }}
+ |
+
+ {{ dom_deleted_alias.domain.user_id }}
+ |
+
+ {{ dom_deleted_alias.created_at }}
+ |
+
+
+
+ {{ show_user(data.domain_deleted_alias.domain.user) }}
+{%- endmacro %}
+{% block body %}
+
+
+ {% if no_match %}
+
+
+ No user, alias or mailbox found for {{ email }}
+
+ {% endif %}
+ {% if data.alias %}
+
+
+
+ Found Alias {{ data.alias.email }}
+
+ {{ list_alias(1,[data.alias]) }}
+ {{ show_user(data.alias.user) }}
+ {{ list_mailboxes(helper.mailbox_count(data.alias.user), helper.mailbox_list(data.alias.user) ) }}
+
+ {% endif %}
+ {% if data.user %}
+
+
+
+ Found User {{ data.user.email }}
+
+ {{ show_user(data.user) }}
+ {{ list_mailboxes(helper.mailbox_count(data.user), helper.mailbox_list(data.user) ) }}
+ {{ list_alias(helper.alias_count(data.user),helper.alias_list(data.user)) }}
+
+ {% endif %}
+ {% if data.mailbox %}
+
+
+
+ Found Mailbox {{ data.mailbox.email }}
+
+ {{ list_mailboxes(1, [data.mailbox] ) }}
+ {{ show_user(data.mailbox.user) }}
+
+ {% endif %}
+ {% if data.deleted_alias %}
+
+
+
+ Found DeletedAlias {{ data.deleted_alias.email }}
+
+ {{ show_deleted_alias(data.deleted_alias) }}
+
+ {% endif %}
+ {% if data.domain_deleted_alias %}
+
+
+
+ Found DomainDeletedAlias {{ data.domain_deleted_alias.email }}
+
+ {{ show_domain_deleted_alias(data.domain_deleted_alias) }}
+
+ {% endif %}
+{% endblock %}