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',
|
'users',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
"users.auth_backends.EmailOrUsernameBackend",
|
||||||
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
'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:
|
if self.user_cache is not None:
|
||||||
return cleaned_data
|
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):
|
if user and not user.is_active and user.check_password(password):
|
||||||
raise ValidationError(self.error_messages["inactive"])
|
raise ValidationError(self.error_messages["inactive"])
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class UserAuthFlowTests(TestCase):
|
|||||||
|
|
||||||
def test_login_with_email_works_for_active_user(self):
|
def test_login_with_email_works_for_active_user(self):
|
||||||
user = User.objects.create_user(
|
user = User.objects.create_user(
|
||||||
username="oleg@example.com",
|
username="oleg",
|
||||||
email="oleg@example.com",
|
email="oleg@example.com",
|
||||||
password="StrongPassword123",
|
password="StrongPassword123",
|
||||||
is_active=True,
|
is_active=True,
|
||||||
@@ -69,6 +69,22 @@ class UserAuthFlowTests(TestCase):
|
|||||||
self.assertRedirects(response, reverse("users:profile"))
|
self.assertRedirects(response, reverse("users:profile"))
|
||||||
self.assertEqual(int(self.client.session["_auth_user_id"]), user.pk)
|
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):
|
def test_password_reset_sends_email_for_existing_user(self):
|
||||||
User.objects.create_user(
|
User.objects.create_user(
|
||||||
username="maria@example.com",
|
username="maria@example.com",
|
||||||
|
|||||||
Reference in New Issue
Block a user