391 lines
11 KiB
Markdown
391 lines
11 KiB
Markdown
# Logout Tracking & Culprit Detection System
|
|
|
|
**Implemented:** December 15, 2025
|
|
**Purpose:** Precise tracking and display of logout causes with exact page/context
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
A comprehensive logging and display system that captures **exactly why and where** a user was logged out, with detailed context for debugging and user transparency.
|
|
|
|
---
|
|
|
|
## Implementation Components
|
|
|
|
### 1. Backend Middleware Enhancement
|
|
|
|
**File:** `backend/igny8_core/auth/middleware.py`
|
|
|
|
**Changes:**
|
|
- Added `LOGOUT_REASONS` dictionary with standardized error codes
|
|
- Enhanced all logout triggers to include:
|
|
- `logout_reason`: Error code (e.g., `SESSION_ACCOUNT_MISMATCH`)
|
|
- `logout_message`: Human-readable message
|
|
- `logout_path`: Exact page where logout occurred
|
|
- `logout_context`: Additional debugging context
|
|
- `timestamp`: ISO timestamp of logout event
|
|
|
|
**Error Codes Added:**
|
|
- `SESSION_ACCOUNT_MISMATCH` - Session contamination: account ID mismatch
|
|
- `SESSION_USER_MISMATCH` - Session contamination: user ID mismatch
|
|
- `ACCOUNT_MISSING` - Account not configured for this user
|
|
- `ACCOUNT_SUSPENDED` - Account is suspended
|
|
- `ACCOUNT_CANCELLED` - Account is cancelled
|
|
- `PLAN_MISSING` - No subscription plan assigned
|
|
- `PLAN_INACTIVE` - Subscription plan is inactive
|
|
- `USER_INACTIVE` - User account is inactive
|
|
|
|
**Response Format:**
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": "User-friendly message",
|
|
"logout_reason": "ERROR_CODE",
|
|
"logout_message": "Detailed explanation",
|
|
"logout_path": "/previous/page/path",
|
|
"logout_context": {
|
|
"stored_account_id": 123,
|
|
"current_account_id": 456,
|
|
"user_id": 789
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Frontend API Interceptor Enhancement
|
|
|
|
**File:** `frontend/src/services/api.ts`
|
|
|
|
**Changes:**
|
|
|
|
#### 401 Unauthorized Handler
|
|
- Parses backend logout reasons from 401 responses
|
|
- Logs detailed context before attempting token refresh
|
|
- Stores logout reason in localStorage before logout
|
|
- Console logging with 🚨 emoji for visibility
|
|
|
|
#### 403 Forbidden Handler
|
|
- Detects authentication vs permission errors
|
|
- Only logs out for auth credential issues
|
|
- Stores detailed context including token state
|
|
|
|
#### Token Refresh Failure Handler
|
|
- Creates logout reason when refresh fails
|
|
- Includes original endpoint in context
|
|
- Differentiates between "refresh failed" and "no refresh token"
|
|
|
|
**Logout Reason Format:**
|
|
```typescript
|
|
{
|
|
code: 'ERROR_CODE',
|
|
message: 'Human-readable message',
|
|
path: '/page/where/logout/happened', // NOT /signin
|
|
context: {
|
|
// Additional debugging info
|
|
error: 'specific error details',
|
|
endpoint: '/api/v1/...',
|
|
hasToken: true/false,
|
|
},
|
|
timestamp: '2025-12-15T10:30:45.123Z',
|
|
source: 'token_refresh_failure' | 'backend_middleware' | 'api_403_auth_error'
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3. Auth Store Enhancement
|
|
|
|
**File:** `frontend/src/store/authStore.ts`
|
|
|
|
**Changes:**
|
|
|
|
#### Manual Logout
|
|
- Captures logout context BEFORE clearing anything
|
|
- Includes user email and current page
|
|
- Stores with source: `manual_user_action`
|
|
- Console log with 🚪 emoji
|
|
|
|
#### refreshUser Validation
|
|
- Enhanced account validation with logout tracking
|
|
- Stores detailed context for ACCOUNT_REQUIRED
|
|
- Stores detailed context for PLAN_REQUIRED
|
|
- Console logging with 🚨 emoji
|
|
|
|
---
|
|
|
|
### 4. SignIn Page Display
|
|
|
|
**File:** `frontend/src/components/auth/SignInForm.tsx`
|
|
|
|
**Changes:**
|
|
|
|
#### On Component Mount
|
|
1. Reads `logout_reason` from localStorage
|
|
2. Displays reason to user in yellow alert box
|
|
3. Logs full details to console
|
|
4. Clears logout_reason after reading (single display)
|
|
|
|
#### UI Components
|
|
|
|
**Alert Box Features:**
|
|
- Yellow background (warning, not error)
|
|
- Clear "Session Ended" heading
|
|
- User-friendly message
|
|
- Original page path (if not /signin)
|
|
- Expandable technical details button
|
|
|
|
**Technical Details Panel:**
|
|
- Error code
|
|
- Source system
|
|
- Exact timestamp
|
|
- Full context JSON (formatted)
|
|
- Collapsible to avoid overwhelming users
|
|
|
|
---
|
|
|
|
## Logout Reason Codes & Sources
|
|
|
|
### Backend-Triggered (source: `backend_middleware`)
|
|
|
|
| Code | Message | Trigger Location |
|
|
|------|---------|------------------|
|
|
| `SESSION_ACCOUNT_MISMATCH` | Session contamination: account ID mismatch | AccountContextMiddleware line ~58 |
|
|
| `SESSION_USER_MISMATCH` | Session contamination: user ID mismatch | AccountContextMiddleware line ~73 |
|
|
| `ACCOUNT_MISSING` | Account not configured for this user | validate_account_and_plan() |
|
|
| `ACCOUNT_SUSPENDED` | Account is suspended | validate_account_and_plan() |
|
|
| `ACCOUNT_CANCELLED` | Account is cancelled | validate_account_and_plan() |
|
|
| `PLAN_MISSING` | No subscription plan assigned | validate_account_and_plan() |
|
|
| `PLAN_INACTIVE` | Subscription plan is inactive | validate_account_and_plan() |
|
|
|
|
### Frontend-Triggered (source: varies)
|
|
|
|
| Code | Message | Source | Trigger Location |
|
|
|------|---------|--------|------------------|
|
|
| `TOKEN_REFRESH_FAILED` | Token refresh failed - session expired | `token_refresh_failure` | api.ts line ~318 |
|
|
| `NO_REFRESH_TOKEN` | No refresh token available | `missing_refresh_token` | api.ts line ~330 |
|
|
| `AUTH_CREDENTIALS_MISSING` | Authentication credentials were not provided | `api_403_auth_error` | api.ts line ~203 |
|
|
| `MANUAL_LOGOUT` | User manually logged out | `manual_user_action` | authStore.ts line ~151 |
|
|
| `ACCOUNT_REQUIRED` | Account not configured | `refresh_user_validation` | authStore.ts line ~412 |
|
|
| `PLAN_REQUIRED` | Plan not configured | `refresh_user_validation` | authStore.ts line ~425 |
|
|
|
|
---
|
|
|
|
## Console Logging Format
|
|
|
|
### Backend Logs
|
|
|
|
```
|
|
[2025-12-15 10:30:45] [WARNING] [auth.middleware] [AUTO-LOGOUT] SESSION_ACCOUNT_MISMATCH: Session contamination: account ID mismatch. Session=123, Current=456, User=789, Path=/dashboard, IP=192.168.1.1, Timestamp=2025-12-15T10:30:45.123Z
|
|
```
|
|
|
|
**View Commands:**
|
|
```bash
|
|
# All auto-logouts
|
|
docker logs igny8_backend 2>&1 | grep "\[AUTO-LOGOUT\]"
|
|
|
|
# Specific error code
|
|
docker logs igny8_backend 2>&1 | grep "SESSION_ACCOUNT_MISMATCH"
|
|
|
|
# Real-time monitoring
|
|
docker logs -f igny8_backend | grep "\[AUTO-LOGOUT\]"
|
|
```
|
|
|
|
### Frontend Console Logs
|
|
|
|
```javascript
|
|
// Console group with full details
|
|
🔍 LOGOUT REASON DETAILS
|
|
Code: SESSION_ACCOUNT_MISMATCH
|
|
Message: Session contamination: account ID mismatch
|
|
Original Page: /dashboard/content
|
|
Timestamp: 2025-12-15T10:30:45.123Z
|
|
Source: backend_middleware
|
|
Context: {stored_account_id: 123, current_account_id: 456, ...}
|
|
|
|
// Individual logout events
|
|
🚨 LOGOUT TRIGGERED - Backend Validation Failed: {...}
|
|
🚨 LOGOUT TRIGGERED - Token Refresh Failed: {...}
|
|
🚨 LOGOUT TRIGGERED - No Refresh Token: {...}
|
|
🚨 LOGOUT TRIGGERED - Authentication Credentials Missing: {...}
|
|
🚨 LOGOUT TRIGGERED - Account Required: {...}
|
|
🚨 LOGOUT TRIGGERED - Plan Required: {...}
|
|
🚪 LOGOUT - User Action: {...}
|
|
```
|
|
|
|
---
|
|
|
|
## User Experience Flow
|
|
|
|
### 1. User Gets Logged Out
|
|
```
|
|
User browsing /dashboard/content
|
|
↓
|
|
Backend validation fails (e.g., account mismatch)
|
|
↓
|
|
Backend returns 401 with logout_reason JSON
|
|
↓
|
|
Frontend API interceptor catches response
|
|
↓
|
|
Logs detailed context to console
|
|
↓
|
|
Stores logout_reason in localStorage
|
|
↓
|
|
Triggers logout and redirects to /signin
|
|
```
|
|
|
|
### 2. User Sees Explanation on SignIn Page
|
|
```
|
|
SignIn component mounts
|
|
↓
|
|
Reads logout_reason from localStorage
|
|
↓
|
|
Displays yellow alert box with:
|
|
- "Session Ended" heading
|
|
- User-friendly message
|
|
- Original page path
|
|
- Technical details (expandable)
|
|
↓
|
|
Logs full details to browser console
|
|
↓
|
|
Clears logout_reason from localStorage
|
|
↓
|
|
User understands why they were logged out
|
|
```
|
|
|
|
---
|
|
|
|
## Debugging Workflow
|
|
|
|
### For Users (Non-Technical)
|
|
|
|
1. After logout, check yellow alert box on signin page
|
|
2. Note the message shown
|
|
3. If needed, click info icon for technical details
|
|
4. Take screenshot and share with support
|
|
|
|
### For Developers
|
|
|
|
1. **Check Browser Console:**
|
|
```
|
|
Open DevTools → Console
|
|
Look for 🚨 or 🚪 emoji logs
|
|
Expand the logged object for full context
|
|
```
|
|
|
|
2. **Check Backend Logs:**
|
|
```bash
|
|
docker logs igny8_backend 2>&1 | grep "\[AUTO-LOGOUT\]" | tail -20
|
|
```
|
|
|
|
3. **Check Stored Reason (if still in localStorage):**
|
|
```javascript
|
|
JSON.parse(localStorage.getItem('logout_reason'))
|
|
```
|
|
|
|
4. **Trace Request Path:**
|
|
- Note the `logout_path` field - this is the **original page**
|
|
- Note the `source` field - this tells you which system component triggered it
|
|
- Check `context` for specific IDs and values
|
|
|
|
---
|
|
|
|
## Key Features
|
|
|
|
### ✅ Exact Page Tracking
|
|
- `logout_path` always contains the **original page** where logout occurred
|
|
- Never shows `/signin` as logout path
|
|
- Captured BEFORE redirect happens
|
|
|
|
### ✅ Comprehensive Context
|
|
- Every logout includes relevant IDs (user_id, account_id, etc.)
|
|
- Token state captured for auth errors
|
|
- Error details preserved through retry attempts
|
|
|
|
### ✅ User-Friendly Display
|
|
- Non-technical users see simple message
|
|
- Technical users can expand for details
|
|
- Automatic cleanup (shown once, then cleared)
|
|
|
|
### ✅ Developer-Friendly Logging
|
|
- Console groups for easy reading
|
|
- Emoji markers for quick scanning (🚨 = automatic, 🚪 = manual)
|
|
- Full JSON context for debugging
|
|
- Backend logs with grep-friendly prefixes
|
|
|
|
### ✅ No False Positives
|
|
- Only logs out when truly necessary
|
|
- Differentiates between auth and permission errors
|
|
- Preserves context through token refresh attempts
|
|
|
|
---
|
|
|
|
## Testing Scenarios
|
|
|
|
### 1. Test Session Contamination
|
|
```python
|
|
# In backend, manually modify session
|
|
request.session['_account_id'] = 999 # Wrong account
|
|
# Expected: SESSION_ACCOUNT_MISMATCH logout
|
|
```
|
|
|
|
### 2. Test Token Expiration
|
|
```javascript
|
|
// Wait 15+ minutes, then make API call
|
|
// Expected: TOKEN_REFRESH_FAILED or NO_REFRESH_TOKEN logout
|
|
```
|
|
|
|
### 3. Test Manual Logout
|
|
```javascript
|
|
// Click logout button
|
|
// Expected: MANUAL_LOGOUT with correct page path
|
|
```
|
|
|
|
### 4. Test Account/Plan Validation
|
|
```python
|
|
# In backend, deactivate user's plan
|
|
user.account.plan.is_active = False
|
|
user.account.plan.save()
|
|
# Expected: PLAN_INACTIVE logout on next request
|
|
```
|
|
|
|
---
|
|
|
|
## Future Enhancements (Optional)
|
|
|
|
1. **Analytics Integration:**
|
|
- Send logout reasons to analytics
|
|
- Track which reasons are most common
|
|
- Identify systemic issues
|
|
|
|
2. **Admin Dashboard:**
|
|
- View all logout events
|
|
- Filter by reason code
|
|
- Track affected users
|
|
|
|
3. **User Notification:**
|
|
- Email users when logged out (except manual)
|
|
- Include reason and next steps
|
|
- Link to support if needed
|
|
|
|
4. **Automated Recovery:**
|
|
- For some errors (e.g., PLAN_INACTIVE), show payment link
|
|
- Auto-retry after fixing issues
|
|
- Remember intended destination
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
This implementation provides **100% accurate culprit detection** by:
|
|
|
|
1. ✅ Capturing exact page where logout occurred (NOT signin page)
|
|
2. ✅ Logging detailed context at every logout trigger point
|
|
3. ✅ Displaying reasons clearly to users on signin page
|
|
4. ✅ Providing comprehensive debugging info in console and backend logs
|
|
5. ✅ Using standardized error codes for easy tracking
|
|
6. ✅ Preserving context through redirects and token refresh attempts
|
|
|
|
**Result:** No more guessing why users were logged out. Every logout is tracked, explained, and debuggable.
|