fix: proper Axios error extraction, token refresh, auth state restoration, ProtectedRoute

This commit is contained in:
Marko (Hermes Implementer)
2026-05-29 00:13:31 +00:00
parent a0b3ae7e34
commit 84c6262b3d
7 changed files with 469 additions and 24 deletions
+37 -1
View File
@@ -221,4 +221,40 @@ Axios-based HTTP client with:
- Inactive accounts rejected at login time
- User emails normalized to lowercase before storage
- JWT tokens stored in localStorage (trade-off: simple SPA integration vs XSS exposure — refresh tokens mitigate short exposure)
- `AUTH_HEADER_TYPES` set to `("Bearer",)` following RFC 6750
- `AUTH_HEADER_TYPES` set to `("Bearer",)` following RFC 6750
---
## Post-Merge Fixes (PR #14)
**Issue:** Issue #3 was re-opened after initial merge. Root cause: frontend error handling was using `err.message` from AxiosError objects, which produces `"Request failed with status code 400"` instead of the actual backend validation errors.
### Changes
| Area | Change | Detail |
|---|---|---|
| `api/accounts/urls.py` | Added `token/refresh/` endpoint | Maps to `rest_framework_simplejwt.views.TokenRefreshView` |
| `web/src/services/authApi.ts` | **Error extraction** | New `extractErrorMessage()` function parses `err.response.data` — handles `non_field_errors`, `detail`, and field-level error arrays |
| `web/src/services/authApi.ts` | **Token refresh interceptor** | Axios response interceptor intercepts 401, refreshes access token using stored refresh_token, replays queued requests |
| `web/src/services/authApi.ts` | Export `setTokens`, `clearTokens`, `getAccessToken` | Shared token utility functions for AuthContext |
| `web/src/contexts/AuthContext.tsx` | **Fixed error handling** | Uses `extractErrorMessage(err)` instead of `err instanceof Error ? err.message : ...` |
| `web/src/contexts/AuthContext.tsx` | **Auth state restoration** | `RESTORE_COMPLETE` action; `isRestoring` flag prevents protected route flash on page refresh |
| `web/src/contexts/AuthContext.tsx` | **Persist user data** | Stores `user_data` in localStorage alongside tokens; restores `UserProfile` on page reload |
| `web/src/components/ProtectedRoute.tsx` | New component | Route guard — redirects to `/login` if unauthenticated; shows loading spinner during restoration |
| `web/src/App.tsx` | Route restructure | Public routes (`/login`, `/register`) outside ProtectedRoute; protected routes (`/`) inside it |
### Error handling flow
1. Backend returns `{"email": ["A user with this email already exists."]}` (DRF standard)
2. Axios interceptor catches non-401 responses, passes error through
3. `extractErrorMessage()` iterates `response.data` keys, returns first error string
4. AuthContext dispatches `AUTH_FAILURE` with extracted message
5. LoginPage/RegisterPage renders `state.error` — user sees: "A user with this email already exists."
### Token refresh flow
1. User logs in — `access_token` and `refresh_token` stored in localStorage
2. When access token expires, next API call receives 401
3. Response interceptor catches it, calls `POST /api/auth/token/refresh/`
4. On success — new access token stored, original request retried, queued requests replayed
5. On failure — tokens cleared, all pending requests rejected with "Session expired"