diff --git a/defender/config.py b/defender/config.py
index c450ec6..dc743f8 100644
--- a/defender/config.py
+++ b/defender/config.py
@@ -93,6 +93,12 @@ LOCKOUT_URL = get_setting("DEFENDER_LOCKOUT_URL")
USE_CELERY = get_setting("DEFENDER_USE_CELERY", False)
+EMAIL_USER_ON_ACCOUNT_LOCKED = get_setting("DEFENDER_EMAIL_USER_ON_ACCOUNT_LOCKED", False)
+# A 'user' instance is passed as context to lockout email template
+LOCKOUT_EMAIL_TEMPLATE_PATH = get_setting("DEFENDER_LOCKOUT_EMAIL_TEMPLATE_PATH", "defender/lockout_email.html")
+
+DEFAULT_FROM_EMAIL = get_setting("DEFENDER_DEFAULT_FROM_EMAIL")
+
STORE_ACCESS_ATTEMPTS = get_setting("DEFENDER_STORE_ACCESS_ATTEMPTS", True)
# Used by the management command to decide how long to keep access attempt
diff --git a/defender/templates/defender/lockout_email.html b/defender/templates/defender/lockout_email.html
new file mode 100644
index 0000000..8a2a96b
--- /dev/null
+++ b/defender/templates/defender/lockout_email.html
@@ -0,0 +1,6 @@
+Hi {{ user.username }},
+
+
+Your account has been locked out due to too many failed login attempts.
+Please contact the administrator to unlock your account.
+
diff --git a/defender/utils.py b/defender/utils.py
index 75ef8fa..af1dbb4 100644
--- a/defender/utils.py
+++ b/defender/utils.py
@@ -8,6 +8,9 @@ from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.core.validators import validate_ipv46_address
from django.core.exceptions import ValidationError
+from django.core.mail import send_mail
+from django.contrib.auth import get_user_model
+from django.template.loader import render_to_string
from django.utils.module_loading import import_string
from .connection import get_redis_connection
@@ -27,6 +30,7 @@ from .signals import (
REDIS_SERVER = get_redis_connection()
LOG = logging.getLogger(__name__)
+User = get_user_model()
def is_valid_ip(ip_address):
@@ -274,6 +278,8 @@ def block_username(username):
REDIS_SERVER.set(key, "blocked")
if not already_blocked:
send_username_block_signal(username)
+ if config.EMAIL_USER_ON_ACCOUNT_LOCKED:
+ send_account_locked_email_to_user(username)
def record_failed_attempt(ip_address, username):
@@ -476,3 +482,25 @@ def add_login_attempt_to_db(
store_login_attempt(
user_agent, ip_address, username, http_accept, path_info, login_valid
)
+
+
+def send_account_locked_email_to_user(username:str):
+ """ Send an email to the user to notify them that their account has been locked """
+ try:
+ user = User.objects.get(username=username)
+ except User.DoesNotExist:
+ # TODO: Handle the case where the user does not exist
+ return
+
+ html_message = render_to_string(config.LOCKOUT_EMAIL_TEMPLATE_PATH, {"user": user})
+ plan_text_message = f"Hi {user.username}, Your account has been locked due to too many failed login attempts.Please contact an admin to unlock your account."
+ send_mail(
+ 'Account Locked',
+ plan_text_message,
+ from_email=config.DEFAULT_FROM_EMAIL,
+ recipient_list=[user.email],
+ html_message=html_message,
+ fail_silently=False
+ )
+
+