From dadc1da194850a18bf28edcfef2dd41a7e644b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Casaj=C3=BAs?= Date: Wed, 6 Aug 2025 10:13:29 +0200 Subject: [PATCH 1/6] Soft delete users --- app/api/views/user.py | 22 +++------------------ app/auth/views/login.py | 2 +- app/dashboard/views/delete_account.py | 28 ++++----------------------- app/user_utils.py | 20 +++++++++++++++++++ email_handler.py | 2 +- 5 files changed, 29 insertions(+), 45 deletions(-) create mode 100644 app/user_utils.py diff --git a/app/api/views/user.py b/app/api/views/user.py index 700792319..af6cb17c9 100644 --- a/app/api/views/user.py +++ b/app/api/views/user.py @@ -1,12 +1,9 @@ from flask import jsonify, g -from sqlalchemy_utils.types.arrow import arrow from app.api.base import api_bp, require_api_sudo, require_api_auth -from app.constants import JobType from app.extensions import limiter -from app.log import LOG -from app.models import Job, ApiToCookieToken -from app.user_audit_log_utils import emit_user_audit_log, UserAuditLogAction +from app.models import ApiToCookieToken +from app.user_utils import soft_delete_user @api_bp.route("/user", methods=["DELETE"]) @@ -14,21 +11,8 @@ def delete_user(): """ Delete the user. Requires sudo mode. - """ - # Schedule delete account job - emit_user_audit_log( - user=g.user, - action=UserAuditLogAction.UserMarkedForDeletion, - message=f"Marked user {g.user.id} ({g.user.email}) for deletion from API", - ) - LOG.w("schedule delete account job for %s", g.user) - Job.create( - name=JobType.DELETE_ACCOUNT.value, - payload={"user_id": g.user.id}, - run_at=arrow.now(), - commit=True, - ) + soft_delete_user(g.user, "API") return jsonify(ok=True) diff --git a/app/auth/views/login.py b/app/auth/views/login.py index ffa09a6a6..624c92f86 100644 --- a/app/auth/views/login.py +++ b/app/auth/views/login.py @@ -64,7 +64,7 @@ def login(): LoginEvent(LoginEvent.ActionType.disabled_login).send() elif user.delete_on is not None: flash( - f"Your account is scheduled to be deleted on {user.delete_on}", + "Email or password incorrect", "error", ) LoginEvent(LoginEvent.ActionType.scheduled_to_be_deleted).send() diff --git a/app/dashboard/views/delete_account.py b/app/dashboard/views/delete_account.py index b3ef1b4d6..c80f75a96 100644 --- a/app/dashboard/views/delete_account.py +++ b/app/dashboard/views/delete_account.py @@ -1,14 +1,11 @@ -import arrow from flask import flash, redirect, url_for, request, render_template from flask_login import login_required, current_user from flask_wtf import FlaskForm -from app.constants import JobType from app.dashboard.base import dashboard_bp from app.dashboard.views.enter_sudo import sudo_required -from app.log import LOG -from app.models import Subscription, Job -from app.user_audit_log_utils import emit_user_audit_log, UserAuditLogAction +from app.models import Subscription +from app.user_utils import soft_delete_user class DeleteDirForm(FlaskForm): @@ -32,25 +29,8 @@ def delete_account(): flash("Please cancel your current subscription first", "warning") return redirect(url_for("dashboard.setting")) - # Schedule delete account job - LOG.w("schedule delete account job for %s", current_user) - emit_user_audit_log( - user=current_user, - action=UserAuditLogAction.UserMarkedForDeletion, - message=f"User {current_user.id} ({current_user.email}) marked for deletion via webapp", - ) - Job.create( - name=JobType.DELETE_ACCOUNT.value, - payload={"user_id": current_user.id}, - run_at=arrow.now(), - commit=True, - ) - - flash( - "Your account deletion has been scheduled. " - "You'll receive an email when the deletion is finished", - "info", - ) + soft_delete_user(current_user, "webapp") + flash("Your account deletion has been scheduled", "info") return redirect(url_for("dashboard.setting")) return render_template("dashboard/delete_account.html", delete_form=delete_form) diff --git a/app/user_utils.py b/app/user_utils.py new file mode 100644 index 000000000..3c3a77338 --- /dev/null +++ b/app/user_utils.py @@ -0,0 +1,20 @@ +import arrow + +from app.db import Session +from app.log import LOG +from app.models import User, ApiKey +from app.session import logout_session +from app.user_audit_log_utils import emit_user_audit_log, UserAuditLogAction + + +def soft_delete_user(user: User, source: str): + LOG.i(f"Marked user {user} for soft-deletion") + emit_user_audit_log( + user=user, + action=UserAuditLogAction.UserMarkedForDeletion, + message=f"Marked user {user} ({user.email}) for deletion from {source}", + ) + user.delete_on = arrow.utcnow() + ApiKey.filter_by(user_id=user.id).delete() + Session.commit() + logout_session() diff --git a/email_handler.py b/email_handler.py index 5f0f6805b..0c52467ad 100644 --- a/email_handler.py +++ b/email_handler.py @@ -632,7 +632,7 @@ def handle_forward(envelope, msg: Message, rcpt_to: str) -> List[Tuple[bool, str if reply_contact: reply_to_contact.append(reply_contact) - if alias.user.delete_on is not None: + if alias.user.is_active(): LOG.d(f"user {user} is pending to be deleted. Do not forward") EmailLog.create( contact_id=contact.id, From 33e3982c0b0da4f02ae5899f690441bd8188f8f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Casaj=C3=BAs?= Date: Wed, 6 Aug 2025 10:28:43 +0200 Subject: [PATCH 2/6] Add soft-delete management on the admin panel --- app/admin_model.py | 63 ++++++++++++++++++++++++++++++- app/models.py | 1 + templates/admin/email_search.html | 42 ++++++++++++++++----- 3 files changed, 96 insertions(+), 10 deletions(-) diff --git a/app/admin_model.py b/app/admin_model.py index e59f2b089..4809bf337 100644 --- a/app/admin_model.py +++ b/app/admin_model.py @@ -969,7 +969,7 @@ def delete_partner_link(self): return redirect(url_for("admin.email_search.index", query=user_id)) AdminAuditLog.create( - admin_user_id=user.id, + admin_user_id=current_user.id, model=User.__class__.__name__, model_id=user.id, action=AuditLogActionEnum.unlink_user.value, @@ -979,6 +979,67 @@ def delete_partner_link(self): return redirect(url_for("admin.email_search.index", query=user_id)) + @expose("/stop_soft_delete_user", methods=["POST"]) + def stop_soft_delete_user(self): + user_id = request.form.get("user_id") + if not user_id: + flash("Missing user_id", "error") + return redirect(url_for("admin.email_search.index")) + try: + user_id = int(user_id) + except ValueError: + flash("Missing user_id", "error") + return redirect(url_for("admin.email_search.index", query=user_id)) + user = User.get(user_id) + if user is None: + flash("User not found", "error") + return redirect(url_for("admin.email_search.index", query=user_id)) + if user.delete_on is None: + flash("User is not pending deletion", "error") + return redirect(url_for("admin.email_search.index", query=user_id)) + + user.delete_on = None + AdminAuditLog.create( + admin_user_id=current_user.id, + model=User.__class__.__name__, + model_id=user.id, + action=AuditLogActionEnum.stop_soft_delete_user.value, + data={"user_id": user_id}, + ) + Session.commit() + + return redirect(url_for("admin.email_search.index", query=user_id)) + + @expose("/force_soft_delete_user", methods=["POST"]) + def force_soft_delete_user(self): + user_id = request.form.get("user_id") + if not user_id: + flash("Missing user_id", "error") + return redirect(url_for("admin.email_search.index")) + try: + user_id = int(user_id) + except ValueError: + flash("Missing user_id", "error") + return redirect(url_for("admin.email_search.index", query=user_id)) + user = User.get(user_id) + if user is None: + flash("User not found", "error") + return redirect(url_for("admin.email_search.index", query=user_id)) + if user.delete_on is None: + flash("User is not pending deletion", "error") + return redirect(url_for("admin.email_search.index", query=user_id)) + User.delete(user.id) + AdminAuditLog.create( + admin_user_id=current_user.id, + model=User.__class__.__name__, + model_id=user.id, + action=AuditLogActionEnum.delete_object.value, + data={"user_id": user_id}, + ) + Session.commit() + + return redirect(url_for("admin.email_search.index", query=user_id)) + class CustomDomainWithValidationData: def __init__(self, domain: CustomDomain): diff --git a/app/models.py b/app/models.py index b0526d83a..1435cea2a 100644 --- a/app/models.py +++ b/app/models.py @@ -242,6 +242,7 @@ class AuditLogActionEnum(EnumE): stop_trial = 11 unlink_user = 12 delete_custom_domain = 13 + stop_soft_delete_user = 14 class Phase(EnumE): diff --git a/templates/admin/email_search.html b/templates/admin/email_search.html index fe52d6cc1..2c646e685 100644 --- a/templates/admin/email_search.html +++ b/templates/admin/email_search.html @@ -39,15 +39,39 @@

User {{ user.email }} with ID {{ user.id }}.

{% endif %} {% if user.delete_on %} - {{ user.delete_on }} + +
+ {{ user.delete_on.format('YYYY-MM-DD HH:mm:ss') }} +
+
+ +
+ + +
+ +
+ + +
+
+ {% else %} None {% endif %} {{ "yes" if user.is_paid() else "No" }} {{ "yes" if user.is_premium() else "No" }} {{ user.get_active_subscription() }} - {{ user.created_at }} - {{ user.updated_at }} + {{ user.created_at.format('YYYY-MM-DD HH:mm:ss') }} + {{ user.updated_at.format('YYYY-MM-DD HH:mm:ss') }} {% if pu %} @@ -91,7 +115,7 @@

{{ mailbox.email }} {{ "Yes" if mailbox.verified else "No" }} - {{ mailbox.created_at }} + {{ mailbox.created_at.format('YYYY-MM-DD HH:mm:ss')}} {% endfor %} @@ -120,7 +144,7 @@

{{ alias.email }} {{ "Yes" if alias.enabled else "No" }} - {{ alias.created_at }} + {{ alias.created_at.format('YYYY-MM-DD HH:mm:ss') }} {% endfor %} @@ -141,7 +165,7 @@

Deleted Alias {{ deleted_alias.email }} with ID {{ deleted_alias.id }}.

{{ deleted_alias.id }} {{ deleted_alias.email }} - {{ deleted_alias.created_at }} + {{ deleted_alias.created_at.format('YYYY-MM-DD HH:mm:ss') }} {{ deleted_alias.reason }} @@ -171,7 +195,7 @@

{{ dom_deleted_alias.domain.domain }} {{ dom_deleted_alias.domain.id }} {{ dom_deleted_alias.domain.user_id }} - {{ dom_deleted_alias.created_at }} + {{ dom_deleted_alias.created_at.format('YYYY-MM-DD HH:mm:ss') }} @@ -201,7 +225,7 @@

Alias Audit Log

{{ entry.action }} {{ entry.message }} - {{ entry.created_at }} + {{ entry.created_at.format('YYYY-MM-DD HH:mm:ss') }} {% endfor %} @@ -227,7 +251,7 @@

User Audit Log

{{ entry.action }} {{ entry.message }} - {{ entry.created_at }} + {{ entry.created_at.format('YYYY-MM-DD HH:mm:ss') }} {% endfor %} From 5afde2bd8ce8e47f62e7410734d45b6e3ee8d473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Casaj=C3=BAs?= Date: Wed, 6 Aug 2025 10:36:41 +0200 Subject: [PATCH 3/6] Fix inverted condition --- email_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/email_handler.py b/email_handler.py index 0c52467ad..a85888d69 100644 --- a/email_handler.py +++ b/email_handler.py @@ -632,7 +632,7 @@ def handle_forward(envelope, msg: Message, rcpt_to: str) -> List[Tuple[bool, str if reply_contact: reply_to_contact.append(reply_contact) - if alias.user.is_active(): + if not alias.user.is_active(): LOG.d(f"user {user} is pending to be deleted. Do not forward") EmailLog.create( contact_id=contact.id, From aa9a582871baf09aa47efb52da91dd1e15f4e3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Casaj=C3=BAs?= Date: Wed, 6 Aug 2025 10:48:06 +0200 Subject: [PATCH 4/6] Update test --- tests/api/test_user.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index 76bc3b851..3ea82cf7c 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -2,9 +2,8 @@ from flask import url_for -from app.constants import JobType from app.db import Session -from app.models import Job, ApiToCookieToken +from app.models import Job, ApiToCookieToken, User, ApiKey from tests.api.utils import get_new_user_and_api_key @@ -45,11 +44,9 @@ def test_delete_with_sudo(flask_client): ) assert r.status_code == 200 - jobs = Job.all() - assert len(jobs) == 1 - job = jobs[0] - assert job.name == JobType.DELETE_ACCOUNT.value - assert job.payload == {"user_id": user.id} + db_user = User.get(user.id) + assert db_user.delete_on is not None + assert ApiKey.filter_by(user_id=db_user.id).count() == 0 def test_get_cookie_token(flask_client): From e2993c787775fb6f76063f1b948703731e757853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Casaj=C3=BAs?= Date: Wed, 6 Aug 2025 12:05:48 +0200 Subject: [PATCH 5/6] html format --- templates/admin/abuser_lookup.html | 275 +++++++++++----------- templates/admin/custom_domain_search.html | 24 +- templates/admin/email_search.html | 38 ++- templates/dashboard/delete_account.html | 10 +- templates/dashboard/setting.html | 4 +- 5 files changed, 179 insertions(+), 172 deletions(-) diff --git a/templates/admin/abuser_lookup.html b/templates/admin/abuser_lookup.html index f58c794e6..0e53e2b17 100644 --- a/templates/admin/abuser_lookup.html +++ b/templates/admin/abuser_lookup.html @@ -1,152 +1,163 @@ {% extends 'admin/master.html' %} {% macro show_user_overview(bundle) -%} -
Overview
- - - - - - - - - - - - - - - -
User IDPrimary email addressUser created
- {% if bundle.get('user', None) %} - {{ bundle.get('user').id }} - {% else %} - {{ bundle.get('account_id') }} - {% endif %} - - {% if bundle.get('user', None) %} - {{ bundle.get('user').email }} - {% else %} - {{ bundle.get('email', '') }} - {% endif %} - {{ bundle.get('user_created_at', '').strftime('%B %d, %Y %I:%M %p') }}
+
Overview
+ + + + + + + + + + + + + + + +
User IDPrimary email addressUser created
+ {% if bundle.get('user', None) %} + + {{ bundle.get("user").id }} + {% else %} + {{ bundle.get("account_id") }} + {% endif %} + + {% if bundle.get('user', None) %} + + {{ bundle.get("user").email }} + {% else %} + {{ bundle.get('email', '') }} + {% endif %} + {{ bundle.get('user_created_at', '').strftime('%B %d, %Y %I:%M %p') }}
{%- endmacro %} {% macro show_emails_table(emails) -%} - - +
+ + + + + + + + + {% for idx, email in emails|enumerate %} + - - - + + + - - - {% for idx, email in emails|enumerate %} - - - - - - {% endfor %} - -
#Email addressDate created
#Email addressDate created{{ idx + 1 }}{{ email.get('address', '') }}{{ email.get('created_at', '').strftime('%B %d, %Y %I:%M %p') }}
{{ idx + 1 }}{{ email.get('address', '') }}{{ email.get('created_at', '').strftime('%B %d, %Y %I:%M %p') }}
+ {% endfor %} + + {%- endmacro %} {% macro show_bundle(no, bundle) -%} -

Bundle #{{ no + 1 }}

- {{ show_user_overview(bundle) }} - {% if bundle.get('aliases', []) %} -
List of aliases
- {{ show_emails_table(bundle.get('aliases', [])) }} - {% endif %} - {% if bundle.get('mailboxes', []) %} -
List of mailboxes
- {{ show_emails_table(bundle.get('mailboxes', [])) }} - {% endif %} -
- -
-
+

Bundle #{{ no + 1 }}

+ {{ show_user_overview(bundle) }} + {% if bundle.get('aliases', []) %} + +
List of aliases
+ {{ show_emails_table(bundle.get('aliases', []) ) }} + {% endif %} + {% if bundle.get('mailboxes', []) %} + +
List of mailboxes
+ {{ show_emails_table(bundle.get('mailboxes', []) ) }} + {% endif %} +
+ +
+
{%- endmacro %} {% macro show_bundles(bundles) -%} -
- {% for idx, bundle in bundles|enumerate %} -
- {{ show_bundle(idx, bundle) }} -
- {% endfor %} -
+
+ {% for idx, bundle in bundles|enumerate %}
{{ show_bundle(idx, bundle) }}
{% endfor %} +
{%- endmacro %} {% macro show_audit_log(audit_log) -%} - {% if audit_log and audit_log|length > 0 %} -

- -

-
-
-
-
- - - - - - - - - - - - {% for idx, log in audit_log|enumerate %} - - - - - - - - {% endfor %} - -
#ActionMessageDate createdAdmin User ID
{{ idx + 1 }} - {{ log.get('action', '') }} - {{ log.get('message', '') }}{{ log.get('created_at', '').strftime('%B %d, %Y %I:%M %p') }} - {{ log.admin_id }} -
-
-
-
-
+ {% if audit_log and audit_log|length > 0 %} + +

+ +

+
+
+
+
+ + + + + + + + + + + + {% for idx, log in audit_log|enumerate %} + + + + + + + + + {% endfor %} + +
#ActionMessageDate createdAdmin User ID
{{ idx + 1 }} + {{ log.get('action', '') }} + {{ log.get('message', '') }}{{ log.get('created_at', '').strftime('%B %d, %Y %I:%M %p') }} + {{ log.admin_id }} +
+
+
- {% endif %} +
+
+ {% endif %} {%- endmacro %} {% block body %} +
+
+
+ + +
+ +
+
+ {% if data.no_match and query %} + + + {% endif %} + {% if data.bundles %} +
-
-
- - -
- -
-
- {% if data.no_match and query %} - - {% endif %} - {% if data.bundles %} -
-

Found abuser data for {{ data.query }}

- {{ show_audit_log(data.audit_log) }} - {{ show_bundles(data.bundles) }} -
- {% endif %} +

+ Found abuser data for {{ data.query }} +

+ {{ show_audit_log(data.audit_log) }} + {{ show_bundles(data.bundles) }}
+ {% endif %} + {% endblock %} diff --git a/templates/admin/custom_domain_search.html b/templates/admin/custom_domain_search.html index e3fefb2fa..19d9fdcbf 100644 --- a/templates/admin/custom_domain_search.html +++ b/templates/admin/custom_domain_search.html @@ -135,18 +135,17 @@
{{ title }}
{% macro show_domain(domain_with_data) -%}
- {% if domain_with_data.domain.pending_deletion == True %} -
-

Domain {{ domain_with_data.domain.domain }}

-
Scheduled for deletion
-
+ +
+

Domain {{ domain_with_data.domain.domain }}

+
Scheduled for deletion
+
{% else %} -
-

Domain {{ domain_with_data.domain.domain }}

-
+
+

Domain {{ domain_with_data.domain.domain }}

+
{% endif %} -
{% set domain = domain_with_data.domain %}
    @@ -158,17 +157,16 @@

    Domain {{ domain_with_data.domain.domain }}

    {{ show_verification("DKIM {}.{}".format(dkim_domain, domain.domain) , domain_with_data.dkim_expected[dkim_domain], [domain_with_data.dkim_validation.get(dkim_domain+"."+domain.domain,'')]) }} {% endfor %}
-
- + + {% if domain_with_data.domain_pending_deletion == False %} - {% if domain_with_data.domain_pending_deletion == False %} - {% endif %} + {% endif %}
diff --git a/templates/admin/email_search.html b/templates/admin/email_search.html index 2c646e685..6f24706d9 100644 --- a/templates/admin/email_search.html +++ b/templates/admin/email_search.html @@ -40,28 +40,24 @@

User {{ user.email }} with ID {{ user.id }}.

{% if user.delete_on %} -
- {{ user.delete_on.format('YYYY-MM-DD HH:mm:ss') }} -
+
{{ user.delete_on.format("YYYY-MM-DD HH:mm:ss") }}
- -
+ -
- -
+
+
-
+
{% else %} @@ -70,8 +66,8 @@

User {{ user.email }} with ID {{ user.id }}.

{{ "yes" if user.is_paid() else "No" }} {{ "yes" if user.is_premium() else "No" }} {{ user.get_active_subscription() }} - {{ user.created_at.format('YYYY-MM-DD HH:mm:ss') }} - {{ user.updated_at.format('YYYY-MM-DD HH:mm:ss') }} + {{ user.created_at.format("YYYY-MM-DD HH:mm:ss") }} + {{ user.updated_at.format("YYYY-MM-DD HH:mm:ss") }} {% if pu %} @@ -115,7 +111,7 @@

{{ mailbox.email }} {{ "Yes" if mailbox.verified else "No" }} - {{ mailbox.created_at.format('YYYY-MM-DD HH:mm:ss')}} + {{ mailbox.created_at.format("YYYY-MM-DD HH:mm:ss") }} {% endfor %} @@ -144,7 +140,7 @@

{{ alias.email }} {{ "Yes" if alias.enabled else "No" }} - {{ alias.created_at.format('YYYY-MM-DD HH:mm:ss') }} + {{ alias.created_at.format("YYYY-MM-DD HH:mm:ss") }} {% endfor %} @@ -165,7 +161,7 @@

Deleted Alias {{ deleted_alias.email }} with ID {{ deleted_alias.id }}.

{{ deleted_alias.id }} {{ deleted_alias.email }} - {{ deleted_alias.created_at.format('YYYY-MM-DD HH:mm:ss') }} + {{ deleted_alias.created_at.format("YYYY-MM-DD HH:mm:ss") }} {{ deleted_alias.reason }} @@ -195,7 +191,7 @@

{{ dom_deleted_alias.domain.domain }} {{ dom_deleted_alias.domain.id }} {{ dom_deleted_alias.domain.user_id }} - {{ dom_deleted_alias.created_at.format('YYYY-MM-DD HH:mm:ss') }} + {{ dom_deleted_alias.created_at.format("YYYY-MM-DD HH:mm:ss") }} @@ -225,7 +221,7 @@

Alias Audit Log

{{ entry.action }} {{ entry.message }} - {{ entry.created_at.format('YYYY-MM-DD HH:mm:ss') }} + {{ entry.created_at.format("YYYY-MM-DD HH:mm:ss") }} {% endfor %} @@ -251,7 +247,7 @@

User Audit Log

{{ entry.action }} {{ entry.message }} - {{ entry.created_at.format('YYYY-MM-DD HH:mm:ss') }} + {{ entry.created_at.format("YYYY-MM-DD HH:mm:ss") }} {% endfor %} diff --git a/templates/dashboard/delete_account.html b/templates/dashboard/delete_account.html index d708bec0a..e4e16e643 100644 --- a/templates/dashboard/delete_account.html +++ b/templates/dashboard/delete_account.html @@ -7,14 +7,16 @@
Account Deletion
-
- Once an account is deleted, it can't be restored. - All its records (aliases, domains, settings, etc.) are immediately deleted. +
+ Once an account is deleted, it can't be restored. You will lose access immediately to all the data stored in this account (aliases, domains, settings, etc.) +
+
+ You will not be able to create another account with the same email for some time.
{{ delete_form.csrf_token }} - +
diff --git a/templates/dashboard/setting.html b/templates/dashboard/setting.html index 60a68796f..f8d332b64 100644 --- a/templates/dashboard/setting.html +++ b/templates/dashboard/setting.html @@ -488,9 +488,9 @@ From a99e70d6a62504eecf4993cbc0db8a5a34a2eaca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Casaj=C3=BAs?= Date: Wed, 6 Aug 2025 12:15:03 +0200 Subject: [PATCH 6/6] Added logs --- app/user_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/user_utils.py b/app/user_utils.py index 3c3a77338..9be3adeec 100644 --- a/app/user_utils.py +++ b/app/user_utils.py @@ -8,7 +8,7 @@ def soft_delete_user(user: User, source: str): - LOG.i(f"Marked user {user} for soft-deletion") + LOG.i(f"Marked user {user} for soft-deletion from {source}") emit_user_audit_log( user=user, action=UserAuditLogAction.UserMarkedForDeletion,