Files
Charon/docs/plans/current_spec.md
GitHub Actions 898066fb59 fix: correct localStorage key for WebSocket auth token
The WebSocket code in logs.ts was reading from 'token' instead of
'charon_auth_token', causing all WebSocket connections to fail
authentication with 401 errors. This resulted in the Security
Dashboard Live Log Viewer showing "Disconnected" with rapid
connect/disconnect cycling.

- Changed localStorage key from 'token' to 'charon_auth_token'
- Both connectLiveLogs and connectSecurityLogs functions updated
2025-12-16 05:08:14 +00:00

6.4 KiB

Security Dashboard Live Log Viewer Bug Fix Plan

Date: December 16, 2025 Issue: Live Log Viewer shows "Disconnected" error with rapid connect/disconnect flashing


Executive Summary

ROOT CAUSE IDENTIFIED: The WebSocket authentication token retrieval uses the wrong localStorage key.

The auth token is stored under charon_auth_token but the WebSocket code reads from token (which doesn't exist), causing every WebSocket connection to be sent without authentication, resulting in immediate 401 rejection.


1. Root Cause Analysis

Token Storage Key Mismatch

Component localStorage Key Used Correct?
AuthContext.tsx (login) charon_auth_token Source of truth
AuthContext.tsx (logout) charon_auth_token
client.ts (axios) Gets token from AuthContext
logs.ts (WebSocket) token WRONG

Code Evidence

AuthContext.tsx (correct storage):

// Line 31 - stores token with correct key
localStorage.setItem('charon_auth_token', token);

logs.ts (incorrect retrieval):

// Line 132 & 200 - reads wrong key
const token = localStorage.getItem('token'); // Returns null!

Result

  1. User logs in → token stored as charon_auth_token
  2. Security Dashboard opens → WebSocket tries localStorage.getItem('token') → returns null
  3. WebSocket URL: /api/v1/cerberus/logs/ws (no token query param)
  4. Backend auth middleware rejects with 401 Unauthorized
  5. Frontend receives onError event → shows "Disconnected" message
  6. User sees rapid flashing if any auto-reconnect logic exists

2. Files to Modify

File Change Type Description
frontend/src/api/logs.ts BUG FIX Change localStorage key from token to charon_auth_token

That's it! This is a one-line fix in two locations within the same file.


3. Specific Code Changes

File: frontend/src/api/logs.ts

Change #1: connectLiveLogs function (Line 132)

Before:

// Get auth token from localStorage
const token = localStorage.getItem('token');

After:

// Get auth token from localStorage (must match AuthContext key)
const token = localStorage.getItem('charon_auth_token');

Change #2: connectSecurityLogs function (Line 200)

Before:

// Get auth token from localStorage
const token = localStorage.getItem('token');

After:

// Get auth token from localStorage (must match AuthContext key)
const token = localStorage.getItem('charon_auth_token');

4. Phase 1: Backend Fix

No backend changes required.

The backend auth middleware already correctly handles token extraction from query parameters:

// backend/internal/api/middleware/auth.go - Lines 21-24
// Try query param (token passthrough)
if token := c.Query("token"); token != "" {
    authHeader = "Bearer " + token
}

The middleware checks in this order:

  1. Authorization header
  2. auth_token cookie
  3. token query parameter ← WebSocket uses this

The backend is working correctly. The issue is purely frontend.


5. Phase 2: Frontend Fix

Implementation Steps

  1. Edit frontend/src/api/logs.ts:

    • Line 132: Change localStorage.getItem('token') to localStorage.getItem('charon_auth_token')
    • Line 200: Change localStorage.getItem('token') to localStorage.getItem('charon_auth_token')
  2. Optional Enhancement - Add debug logging for auth issues:

    const token = localStorage.getItem('charon_auth_token');
    if (!token) {
      console.warn('WebSocket: No auth token found in localStorage');
    }
    
  3. Consider creating a shared constant for the token key:

    // In a shared constants file
    export const AUTH_TOKEN_KEY = 'charon_auth_token';
    

6. Testing Checklist

Manual Testing Steps

  1. Clear browser data (ensure clean state)
  2. Login to Charon (stores token correctly)
  3. Navigate to Security Dashboard
  4. Verify Live Log Viewer shows "Connected" (green badge)
  5. Verify logs start streaming (wait for traffic or trigger test request)
  6. Check browser DevTools:
    • Network tab: WebSocket connection should be 101 Switching Protocols
    • Console: Should show Cerberus logs WebSocket connection established
    • WebSocket frames: Should show JSON log entries, not error responses

Verification Commands

# Test WebSocket endpoint with token (from container)
TOKEN=$(cat /tmp/test_token)  # or get from browser localStorage
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" \
  "http://localhost:8080/api/v1/cerberus/logs/ws?token=$TOKEN"

# Should see: HTTP/1.1 101 Switching Protocols

Unit Test Update

Update frontend/src/components/__tests__/LiveLogViewer.test.tsx if needed to mock the correct localStorage key:

beforeEach(() => {
  // Mock localStorage with correct key
  vi.spyOn(Storage.prototype, 'getItem').mockImplementation((key) => {
    if (key === 'charon_auth_token') return 'mock-jwt-token';
    return null;
  });
});

7. Regression Prevention

Add ESLint Rule (Optional)

Consider adding a custom ESLint rule or code comment to prevent future mismatches:

// frontend/src/constants/auth.ts
/**
 * IMPORTANT: This key must match across all auth-related code.
 * Used by: AuthContext.tsx, logs.ts (WebSocket)
 * @see AuthContext.tsx for login/logout logic
 */
export const AUTH_TOKEN_STORAGE_KEY = 'charon_auth_token';

Update Contributing Guidelines

Add to .github/copilot-instructions.md or CONTRIBUTING.md:

Auth Token Key: Always use charon_auth_token for localStorage operations. Import from shared constants when possible.


8. Summary

Aspect Details
Bug WebSocket reads wrong localStorage key
Impact Security Dashboard Live Logs never connects
Fix Change tokencharon_auth_token in 2 locations
Risk Low - isolated change, no side effects
Effort 5 minutes
Testing Manual verification + existing unit tests

9. Implementation Command

# Single sed command to fix both occurrences
sed -i "s/localStorage.getItem('token')/localStorage.getItem('charon_auth_token')/g" \
  frontend/src/api/logs.ts

Or apply the fix manually in VS Code.