diff --git a/src/backend/InvenTree/InvenTree/middleware.py b/src/backend/InvenTree/InvenTree/middleware.py index 0c6ff3c94b..356a8cc677 100644 --- a/src/backend/InvenTree/InvenTree/middleware.py +++ b/src/backend/InvenTree/InvenTree/middleware.py @@ -110,7 +110,9 @@ def csrf_failure(request, reason=''): """Custom CSRF failure handler. Returns a JSON response for API/headless requests so the frontend can - provide a meaningful error message to the user + provide a meaningful error message to the user. + + This Includes Django's own message (e.g. "CSRF cookie not set.") alongside the generic hint. """ from django.views.csrf import csrf_failure as django_default @@ -120,15 +122,15 @@ def csrf_failure(request, reason=''): or 'application/json' in request.headers.get('Accept', '') or 'application/json' in request.headers.get('Content-Type', '') ): - return JsonResponse( - { - 'detail': _( - 'CSRF verification failed. Ensure INVENTREE_SITE_URL and INVENTREE_TRUSTED_ORIGINS are configured correctly.' - ) - }, - status=403, + detail = _( + 'CSRF verification failed. Ensure INVENTREE_SITE_URL and INVENTREE_TRUSTED_ORIGINS are configured correctly.' ) + if reason: + detail = f'{detail} ({reason})' + + return JsonResponse({'detail': detail}, status=403) + return django_default(request, reason=reason) diff --git a/src/backend/InvenTree/InvenTree/test_middleware.py b/src/backend/InvenTree/InvenTree/test_middleware.py index b6b89cf31d..3b155f7308 100644 --- a/src/backend/InvenTree/InvenTree/test_middleware.py +++ b/src/backend/InvenTree/InvenTree/test_middleware.py @@ -310,7 +310,7 @@ class MiddlewareTests(InvenTreeTestCase): request.META[f'HTTP_{key.upper().replace("-", "_")}'] = value return request - # API path -> JSON 403 with meaningful message + # API path -> JSON 403 with meaningful message, plus Django's own reason response = csrf_failure( make_request('/api/part/'), reason='origin check failed' ) @@ -318,9 +318,9 @@ class MiddlewareTests(InvenTreeTestCase): import json data = json.loads(response.content) - self.assertEqual(data['detail'], EXPECTED_DETAIL) + self.assertEqual(data['detail'], f'{EXPECTED_DETAIL} (origin check failed)') - # allauth headless path -> JSON 403 + # allauth headless path -> JSON 403 (no reason supplied -> generic message only) response = csrf_failure(make_request('/_allauth/browser/v1/auth/login')) self.assertEqual(response.status_code, 403) data = json.loads(response.content) @@ -333,16 +333,16 @@ class MiddlewareTests(InvenTreeTestCase): ) self.assertEqual(response.status_code, 403) data = json.loads(response.content) - self.assertEqual(data['detail'], EXPECTED_DETAIL) + self.assertEqual(data['detail'], f'{EXPECTED_DETAIL} (origin check failed)') # Content-Type: application/json header -> JSON 403 response = csrf_failure( make_request('/some/other/path/', {'Content-Type': 'application/json'}), - reason='origin check failed', + reason='CSRF cookie not set.', ) self.assertEqual(response.status_code, 403) data = json.loads(response.content) - self.assertEqual(data['detail'], EXPECTED_DETAIL) + self.assertEqual(data['detail'], f'{EXPECTED_DETAIL} (CSRF cookie not set.)') # Plain browser request -> falls back to Django default CSRF page (403 HTML, not JSON) response = csrf_failure(make_request('/web/'), reason='origin check failed') diff --git a/src/frontend/src/functions/api.tsx b/src/frontend/src/functions/api.tsx index 7fb2a14da1..3e8b569d7e 100644 --- a/src/frontend/src/functions/api.tsx +++ b/src/frontend/src/functions/api.tsx @@ -20,7 +20,10 @@ export function extractErrorMessage({ let message = ''; if (error_data) { - message = error_data[field ?? 'error'] ?? error_data['non_field_errors']; + message = + error_data[field ?? 'error'] ?? + error_data['detail'] ?? + error_data['non_field_errors']; } // No message? Look at the response status codes