logo and architecture fixes
This commit is contained in:
35
backend/igny8_core/auth/backends.py
Normal file
35
backend/igny8_core/auth/backends.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
Custom Authentication Backend - No Caching
|
||||
Prevents cross-request user contamination by disabling Django's default user caching
|
||||
"""
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
|
||||
|
||||
class NoCacheModelBackend(ModelBackend):
|
||||
"""
|
||||
Custom authentication backend that disables user object caching.
|
||||
|
||||
Django's default ModelBackend caches the user object in thread-local storage,
|
||||
which can cause cross-request contamination when the same worker process
|
||||
handles requests from different users.
|
||||
|
||||
This backend forces a fresh DB query on EVERY request to prevent user swapping.
|
||||
"""
|
||||
|
||||
def get_user(self, user_id):
|
||||
"""
|
||||
Get user from database WITHOUT caching.
|
||||
|
||||
This overrides the default behavior which caches user objects
|
||||
at the process level, causing session contamination.
|
||||
"""
|
||||
from django.contrib.auth import get_user_model
|
||||
UserModel = get_user_model()
|
||||
|
||||
try:
|
||||
# CRITICAL: Use select_related to load account/plan in ONE query
|
||||
# But do NOT cache the result - return fresh object every time
|
||||
user = UserModel.objects.select_related('account', 'account__plan').get(pk=user_id)
|
||||
return user
|
||||
except UserModel.DoesNotExist:
|
||||
return None
|
||||
@@ -31,33 +31,45 @@ class AccountContextMiddleware(MiddlewareMixin):
|
||||
# First, try to get user from Django session (cookie-based auth)
|
||||
# This handles cases where frontend uses credentials: 'include' with session cookies
|
||||
if hasattr(request, 'user') and request.user and request.user.is_authenticated:
|
||||
# User is authenticated via session - refresh from DB to get latest account/plan data
|
||||
# This ensures changes to account/plan are reflected immediately without re-login
|
||||
# CRITICAL FIX: Never query DB again or mutate request.user
|
||||
# Django's AuthenticationMiddleware already loaded the user correctly
|
||||
# Just use it directly and set request.account from the ALREADY LOADED relationship
|
||||
try:
|
||||
from .models import User as UserModel
|
||||
# CRITICAL FIX: Never mutate request.user - it causes session contamination
|
||||
# Instead, just read the current user and set request.account
|
||||
# Django's session middleware already sets request.user correctly
|
||||
user = request.user # Use the user from session, don't overwrite it
|
||||
|
||||
validation_error = self._validate_account_and_plan(request, user)
|
||||
# Validate account/plan - but use the user object already set by Django
|
||||
validation_error = self._validate_account_and_plan(request, request.user)
|
||||
if validation_error:
|
||||
return validation_error
|
||||
request.account = getattr(user, 'account', None)
|
||||
|
||||
# Set request.account from the user's account relationship
|
||||
# This is already loaded, no need to query DB again
|
||||
request.account = getattr(request.user, 'account', None)
|
||||
|
||||
# CRITICAL: Add account ID to session to prevent cross-contamination
|
||||
# This ensures each session is tied to a specific account
|
||||
if request.account:
|
||||
request.session['_account_id'] = request.account.id
|
||||
request.session['_user_id'] = request.user.id
|
||||
# Verify session integrity - if stored IDs don't match, logout
|
||||
stored_account_id = request.session.get('_account_id')
|
||||
stored_user_id = request.session.get('_user_id')
|
||||
if stored_account_id and stored_account_id != request.account.id:
|
||||
# Session contamination detected - force logout
|
||||
logout(request)
|
||||
return JsonResponse(
|
||||
{'success': False, 'error': 'Session integrity violation detected. Please login again.'},
|
||||
status=status.HTTP_401_UNAUTHORIZED
|
||||
)
|
||||
if stored_user_id and stored_user_id != request.user.id:
|
||||
# Session contamination detected - force logout
|
||||
logout(request)
|
||||
return JsonResponse(
|
||||
{'success': False, 'error': 'Session integrity violation detected. Please login again.'},
|
||||
status=status.HTTP_401_UNAUTHORIZED
|
||||
)
|
||||
|
||||
return None
|
||||
except (AttributeError, UserModel.DoesNotExist, Exception):
|
||||
# If refresh fails, fallback to cached account
|
||||
try:
|
||||
user_account = getattr(request.user, 'account', None)
|
||||
if user_account:
|
||||
validation_error = self._validate_account_and_plan(request, request.user)
|
||||
if validation_error:
|
||||
return validation_error
|
||||
request.account = user_account
|
||||
return None
|
||||
except (AttributeError, Exception):
|
||||
pass
|
||||
# If account access fails (e.g., column mismatch), set to None
|
||||
except (AttributeError, Exception):
|
||||
# If anything fails, just set account to None and continue
|
||||
request.account = None
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user