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:
2026-04-28 09:45:08 +02:00
parent ca23858946
commit 72c4a0ed04
5 changed files with 154 additions and 27 deletions

View File

@@ -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.
---