fix: prevent silent auth error swallowing in fetch error utility
- Add setAuthErrorHandler() registration mechanism to utils/fetchError.ts - Implement fallback logging when auth errors (401/403) occur without registered handler - Update AuthProvider to register both API client and fetch error handlers - Ensure auth errors are handled deterministically at multiple layers - Add comprehensive tests for auth error handler registration and fallback logging - Update Web-Development.md documentation with auth error handling contract Fixes issue #21: Silent auth errors are now caught and logged if the handler is not registered, preventing actionable errors from being silently swallowed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -971,10 +971,43 @@ HttpOnly cookies provide superior protection against XSS (Cross-Site Scripting)
|
||||
|
||||
### Error Handling
|
||||
|
||||
When an API request returns 401 or 403:
|
||||
1. The `client.ts` module dispatches a `SESSION_EXPIRED_EVENT`.
|
||||
2. The `AuthProvider` listener handles it by clearing `isAuthenticated` and redirecting to `/login`.
|
||||
3. Hooks must use `handleFetchError` (from `utils/fetchError.ts`) to avoid displaying auth errors as user-facing error messages.
|
||||
**Auth Error Handling Contract:**
|
||||
|
||||
The frontend employs a **dual-handler approach** to ensure 401/403 auth errors are never silently swallowed:
|
||||
|
||||
1. **API Client Layer** (`api/client.ts`):
|
||||
- When `api/client.ts` receives a 401/403 response, it invokes `setUnauthorizedHandler()` (set by `AuthProvider`)
|
||||
- This catches auth errors at the HTTP boundary before they reach hooks
|
||||
- Prevents errors from being lost in error translation layers
|
||||
|
||||
2. **Hook Utility Layer** (`utils/fetchError.ts`):
|
||||
- When `handleFetchError()` encounters a 401/403 error, it:
|
||||
- Invokes the registered `authErrorHandler()` (also set by `AuthProvider`), OR
|
||||
- Falls back to logging a warning if no handler is registered
|
||||
- This acts as a safety net for auth errors that escape the API client layer
|
||||
- Ensures deterministic error handling with fallback logging
|
||||
|
||||
3. **AuthProvider** (`providers/AuthProvider.tsx`):
|
||||
- Registers both handlers on mount: `setUnauthorizedHandler()` and `setAuthErrorHandler()`
|
||||
- Both handlers call `handleSessionExpired()`, which clears `isAuthenticated` and redirects to `/login`
|
||||
- Cleans up handlers on unmount to prevent stale closures
|
||||
|
||||
**Usage in Hooks:**
|
||||
|
||||
Hooks must use `handleFetchError()` (from `utils/fetchError.ts`) when catching errors. It automatically:
|
||||
- Silently ignores abort errors (expected cleanup)
|
||||
- Invokes the registered auth handler for 401/403 errors (prevents display in component state)
|
||||
- Passes other errors to the provided state setter
|
||||
|
||||
```ts
|
||||
// In a hook's .catch() handler:
|
||||
.catch((err: unknown) => {
|
||||
handleFetchError(err, setError, "Failed to load data");
|
||||
// Auth errors are handled globally; non-auth errors are set in component state
|
||||
});
|
||||
```
|
||||
|
||||
This design ensures auth errors are caught and handled deterministically at multiple layers, regardless of where they originate.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user