reorg
This commit is contained in:
390
docs/logout-issues/LOGOUT-TRACKING-IMPLEMENTATION.md
Normal file
390
docs/logout-issues/LOGOUT-TRACKING-IMPLEMENTATION.md
Normal file
@@ -0,0 +1,390 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user