fix: proper Axios error extraction, token refresh, auth state restoration, ProtectedRoute
This commit is contained in:
@@ -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"
|
||||
Reference in New Issue
Block a user