Enhanced error message on CSRF failure (#12788) (#12794)

* Enhanced error message on CSRF failure

* Defer to 'detail' field if exists

(cherry picked from commit 9a416df5d4)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
github-actions[bot]
2026-09-06 13:04:57 +10:00
committed by GitHub
co-authored by Oliver
parent 1368b7db08
commit 5b97accbf3
3 changed files with 20 additions and 15 deletions
@@ -110,7 +110,9 @@ def csrf_failure(request, reason=''):
"""Custom CSRF failure handler. """Custom CSRF failure handler.
Returns a JSON response for API/headless requests so the frontend can 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 from django.views.csrf import csrf_failure as django_default
@@ -120,14 +122,14 @@ def csrf_failure(request, reason=''):
or 'application/json' in request.headers.get('Accept', '') or 'application/json' in request.headers.get('Accept', '')
or 'application/json' in request.headers.get('Content-Type', '') or 'application/json' in request.headers.get('Content-Type', '')
): ):
return JsonResponse( detail = _(
{
'detail': _(
'CSRF verification failed. Ensure INVENTREE_SITE_URL and INVENTREE_TRUSTED_ORIGINS are configured correctly.' 'CSRF verification failed. Ensure INVENTREE_SITE_URL and INVENTREE_TRUSTED_ORIGINS are configured correctly.'
) )
},
status=403, if reason:
) detail = f'{detail} ({reason})'
return JsonResponse({'detail': detail}, status=403)
return django_default(request, reason=reason) return django_default(request, reason=reason)
@@ -310,7 +310,7 @@ class MiddlewareTests(InvenTreeTestCase):
request.META[f'HTTP_{key.upper().replace("-", "_")}'] = value request.META[f'HTTP_{key.upper().replace("-", "_")}'] = value
return request return request
# API path -> JSON 403 with meaningful message # API path -> JSON 403 with meaningful message, plus Django's own reason
response = csrf_failure( response = csrf_failure(
make_request('/api/part/'), reason='origin check failed' make_request('/api/part/'), reason='origin check failed'
) )
@@ -318,9 +318,9 @@ class MiddlewareTests(InvenTreeTestCase):
import json import json
data = json.loads(response.content) 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')) response = csrf_failure(make_request('/_allauth/browser/v1/auth/login'))
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
data = json.loads(response.content) data = json.loads(response.content)
@@ -333,16 +333,16 @@ class MiddlewareTests(InvenTreeTestCase):
) )
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
data = json.loads(response.content) 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 # Content-Type: application/json header -> JSON 403
response = csrf_failure( response = csrf_failure(
make_request('/some/other/path/', {'Content-Type': 'application/json'}), make_request('/some/other/path/', {'Content-Type': 'application/json'}),
reason='origin check failed', reason='CSRF cookie not set.',
) )
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
data = json.loads(response.content) 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) # Plain browser request -> falls back to Django default CSRF page (403 HTML, not JSON)
response = csrf_failure(make_request('/web/'), reason='origin check failed') response = csrf_failure(make_request('/web/'), reason='origin check failed')
+4 -1
View File
@@ -20,7 +20,10 @@ export function extractErrorMessage({
let message = ''; let message = '';
if (error_data) { 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 // No message? Look at the response status codes