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 %} - - - {% 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 IDEmailPaidSubscriptionCreated 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 %} +

+ + + + + + + + + + + {% for mailbox in mboxes %} + + + + + + + + {% endfor %} + +
Mailbox IDEmailVerifiedCreated At
{{ mailbox.id }}{{ mailbox.email }}{{ "Yes" if mailbox.verified else "No" }}{{ mailbox.created_at }}
+{% endmacro %} +{% macro list_alias(alias_count, aliases) %} +

+ {{ alias_count }} Aliases found. + {% if alias_count>10 %}Showing only the first 10.{% endif %} +

+ + + + + + + + + + + {% for alias in aliases %} + + + + + + + + {% endfor %} + +
Alias IDEmailVerifiedCreated At
{{ alias.id }}{{ alias.email }}{{ "Yes" if alias.verified else "No" }} + {{ alias.created_at }} +
+{% 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 %} + + + {% 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 %}