feat: add custom authentication backend to support login with email or username
All checks were successful
Build and Push Docker Image / build (push) Successful in 28s
All checks were successful
Build and Push Docker Image / build (push) Successful in 28s
- Implemented `EmailOrUsernameBackend` to allow authentication via email or username - Updated settings to include the new authentication backend - Modified tests to cover email-based login scenarios - Fixed user lookup logic in forms to handle case-insensitive email matching
This commit is contained in:
@@ -192,6 +192,10 @@ INSTALLED_APPS = [
|
||||
'users',
|
||||
]
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
"users.auth_backends.EmailOrUsernameBackend",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||
|
||||
35
users/auth_backends.py
Normal file
35
users/auth_backends.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
||||
|
||||
class EmailOrUsernameBackend(ModelBackend):
|
||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||
if username is None:
|
||||
username = kwargs.get(UserModel.USERNAME_FIELD)
|
||||
|
||||
if username is None or password is None:
|
||||
return None
|
||||
|
||||
user = self._get_user_by_username_or_email(username)
|
||||
if user is None:
|
||||
return None
|
||||
|
||||
if user.check_password(password) and self.user_can_authenticate(user):
|
||||
return user
|
||||
|
||||
return None
|
||||
|
||||
def _get_user_by_username_or_email(self, value):
|
||||
try:
|
||||
return UserModel._default_manager.get_by_natural_key(value)
|
||||
except UserModel.DoesNotExist:
|
||||
email_field = UserModel.get_email_field_name()
|
||||
try:
|
||||
return UserModel._default_manager.get(**{f"{email_field}__iexact": value})
|
||||
except UserModel.DoesNotExist:
|
||||
return None
|
||||
except UserModel.MultipleObjectsReturned:
|
||||
return None
|
||||
@@ -36,7 +36,7 @@ class LoginForm(forms.Form):
|
||||
if self.user_cache is not None:
|
||||
return cleaned_data
|
||||
|
||||
user = User.objects.filter(username=email).first()
|
||||
user = User.objects.filter(email__iexact=email).first()
|
||||
if user and not user.is_active and user.check_password(password):
|
||||
raise ValidationError(self.error_messages["inactive"])
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class UserAuthFlowTests(TestCase):
|
||||
|
||||
def test_login_with_email_works_for_active_user(self):
|
||||
user = User.objects.create_user(
|
||||
username="oleg@example.com",
|
||||
username="oleg",
|
||||
email="oleg@example.com",
|
||||
password="StrongPassword123",
|
||||
is_active=True,
|
||||
@@ -69,6 +69,22 @@ class UserAuthFlowTests(TestCase):
|
||||
self.assertRedirects(response, reverse("users:profile"))
|
||||
self.assertEqual(int(self.client.session["_auth_user_id"]), user.pk)
|
||||
|
||||
def test_login_with_email_works_when_username_differs(self):
|
||||
user = User.objects.create_user(
|
||||
username="oleg-old",
|
||||
email="oleg2@example.com",
|
||||
password="StrongPassword123",
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("users:login"),
|
||||
data={"email": "oleg2@example.com", "password": "StrongPassword123"},
|
||||
)
|
||||
|
||||
self.assertRedirects(response, reverse("users:profile"))
|
||||
self.assertEqual(int(self.client.session["_auth_user_id"]), user.pk)
|
||||
|
||||
def test_password_reset_sends_email_for_existing_user(self):
|
||||
User.objects.create_user(
|
||||
username="maria@example.com",
|
||||
|
||||
Reference in New Issue
Block a user