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 + ) + +