fix(auth): dedupe handler + error utils refactor
- Add 401/403 dedup guard to API client to prevent double logout - Extract fetchError util: isAuthError + getErrorMessage - AuthProvider uses new error utils, removes duplicate logic - Remove completed task docs from Tasks.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -28,7 +28,7 @@
|
||||
* If the browser does not support BroadcastChannel, the storage event listener
|
||||
* serves as a fallback (though it only fires when storage is changed in another tab).
|
||||
*
|
||||
* **Auth Error Handling:**
|
||||
* **Auth Error Handling & Deduplication:**
|
||||
* AuthProvider registers two auth error handlers to ensure that 401/403 errors
|
||||
* are never silently swallowed:
|
||||
* - `setUnauthorizedHandler()` in `api/client.ts` — handles auth errors from the
|
||||
@@ -36,8 +36,15 @@
|
||||
* - `setAuthErrorHandler()` in `utils/fetchError.ts` — handles auth errors that
|
||||
* reach hooks and ensures deterministic error handling with fallback logging
|
||||
*
|
||||
* This dual-handler approach ensures auth errors are caught and handled at
|
||||
* multiple layers, preventing silent error loss regardless of where the error occurs.
|
||||
* A module-level `isLoggingOut` flag in each file prevents double-firing: the
|
||||
* first handler to fire sets its flag, and the second checks it before invoking
|
||||
* its handler. Both flags are reset in `handleSessionExpired()` so future 401s
|
||||
* can trigger the handlers again after the user reaches the login page.
|
||||
*
|
||||
* This dual-handler approach with deduplication ensures auth errors are caught
|
||||
* at multiple layers, preventing silent error loss regardless of where the
|
||||
* error occurs, while avoiding the React state mutation race caused by
|
||||
* concurrent logout dispatches.
|
||||
*/
|
||||
|
||||
import React, {
|
||||
@@ -49,8 +56,8 @@ import React, {
|
||||
} from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import * as authApi from "../api/auth";
|
||||
import { setUnauthorizedHandler } from "../api/client";
|
||||
import { setAuthErrorHandler } from "../utils/fetchError";
|
||||
import { setUnauthorizedHandler, resetLogoutState } from "../api/client";
|
||||
import { setAuthErrorHandler, resetLogoutState as resetFetchErrorLogoutState } from "../utils/fetchError";
|
||||
import { STORAGE_KEY_AUTHENTICATED } from "../utils/constants";
|
||||
import { SessionValidationLoading } from "../components/SessionValidationLoading";
|
||||
import { useSessionValidation } from "../hooks/useSessionValidation";
|
||||
@@ -114,6 +121,9 @@ export function AuthProvider({
|
||||
const handleSessionExpired = useCallback((): void => {
|
||||
sessionStorage.removeItem(STORAGE_KEY_AUTHENTICATED);
|
||||
setIsAuthenticated(false);
|
||||
// Reset deduplication flags so future 401s can trigger handlers again.
|
||||
resetLogoutState();
|
||||
resetFetchErrorLogoutState();
|
||||
navigate("/login", { replace: true });
|
||||
}, [navigate]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user