Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions custom_auth/tests/test_02_login_redirect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from __future__ import annotations

from django.test import TestCase, RequestFactory
from django.contrib.auth import get_user_model
from django.conf import settings
from unittest import mock

from custom_auth.views import email_token_login
from custom_auth.utils.auth import generate_login_token

User = get_user_model()


class EmailTokenLoginRedirectTest(TestCase):
"""Test that email token login respects the 'next' parameter"""

fixtures = ["registry/tests/fixtures/integration.json"]

def setUp(self):
self.factory = RequestFactory()
self.user = User.objects.get(username="Mounfem")

def test_redirect_with_next_parameter(self):
"""Test that login redirects to the 'next' URL when provided"""
token = generate_login_token(self.user)

# Create a POST request with a valid token and next parameter
request = self.factory.post('/auth/magic/', {
'token': token,
'next': '/payments/'
})
# Set the host for URL validation (use testserver for test compatibility)
request.META['HTTP_HOST'] = 'testserver'

with mock.patch('custom_auth.views.authenticate', return_value=self.user):
with mock.patch('custom_auth.views.login'):
response = email_token_login(request)

# Should redirect to /payments/
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, '/payments/')

def test_redirect_without_next_parameter(self):
"""Test that login redirects to LOGIN_REDIRECT_URL when 'next' is not provided"""
token = generate_login_token(self.user)

# Create a POST request with a valid token but no next parameter
request = self.factory.post('/auth/magic/', {
'token': token
})
# Set the host for URL validation (use testserver for test compatibility)
request.META['HTTP_HOST'] = 'testserver'

with mock.patch('custom_auth.views.authenticate', return_value=self.user):
with mock.patch('custom_auth.views.login'):
response = email_token_login(request)

# Should redirect to LOGIN_REDIRECT_URL (default: '/')
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, settings.LOGIN_REDIRECT_URL)

def test_redirect_with_empty_next_parameter(self):
"""Test that login redirects to LOGIN_REDIRECT_URL when 'next' is empty"""
token = generate_login_token(self.user)

# Create a POST request with a valid token and empty next parameter
request = self.factory.post('/auth/magic/', {
'token': token,
'next': ''
})
# Set the host for URL validation (use testserver for test compatibility)
request.META['HTTP_HOST'] = 'testserver'

with mock.patch('custom_auth.views.authenticate', return_value=self.user):
with mock.patch('custom_auth.views.login'):
response = email_token_login(request)

# Should redirect to LOGIN_REDIRECT_URL (default: '/')
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, settings.LOGIN_REDIRECT_URL)

def test_redirect_blocks_external_urls(self):
"""Test that external URLs are blocked (security check)"""
token = generate_login_token(self.user)

# Create a POST request with a valid token and external next URL
request = self.factory.post('/auth/magic/', {
'token': token,
'next': 'https://evil.com/phishing'
})
# Set the host for URL validation (use testserver for test compatibility)
request.META['HTTP_HOST'] = 'testserver'

with mock.patch('custom_auth.views.authenticate', return_value=self.user):
with mock.patch('custom_auth.views.login'):
response = email_token_login(request)

# Should redirect to LOGIN_REDIRECT_URL, not the external URL
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, settings.LOGIN_REDIRECT_URL)
9 changes: 6 additions & 3 deletions custom_auth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
from django.contrib.auth import authenticate, login, views
from django.core.exceptions import SuspiciousOperation
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect
from django.shortcuts import render
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.http import url_has_allowed_host_and_scheme
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
Expand Down Expand Up @@ -46,8 +47,8 @@ def email_token_login(request: HttpRequest) -> HttpResponse:

if res is not None:
login(request, res)
next_url = request.POST.get("next", None)
return HttpResponseRedirect(next_url)
next_url = request.POST.get("next", settings.LOGIN_REDIRECT_URL)
return redirect(next_url)
else:
return render(request, "auth/token_login.html", context={"error": True})

Expand Down Expand Up @@ -77,6 +78,8 @@ class ClientIdLoginView(views.LoginView):
def get_context_data(self, **kwargs):
context = super(views.LoginView, self).get_context_data(**kwargs)
context["googlefail"] = self.request.GET.get("error", "") == "googlefail"
context["next"] = self.request.GET.get("next", settings.LOGIN_REDIRECT_URL)

context.update(self.extra_context)

return context
Expand Down
Loading