mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 12:11:11 +00:00
[AZ-510] Auth bootstrap: POST refresh + chained /users/me
Replace the broken `GET /api/admin/auth/refresh` (no `credentials:'include'`) mount-time bootstrap with `POST /api/admin/auth/refresh` (with credentials) chained to `GET /api/admin/users/me`. Returning users with a valid HttpOnly refresh cookie no longer flash through `/login`. Closes Finding B3 / Vision P3. - Add module-scoped `bootstrapInflight` guard (StrictMode double-mount safety) + test-only reset hook exported via the `src/auth` barrel; `tests/setup.ts` resets it in `afterEach` to prevent pending-promise leakage between tests. - Defensive `hasPermission` against legacy `/users/me` payloads omitting `permissions`; default MSW handler now seeds `permissions` explicitly. - Add `endpoints.admin.usersMe()` builder (STC-ARCH-02 forbids the literal). - Bulk-swap 15 test files from `http.get` -> `http.post` for the refresh override so intentional bootstrap-fail tests still fail correctly. - Update auth component description; mark B3 closed. - Code review verdict PASS; static + fast suites green (231 / 13 skipped). Batch report: _docs/03_implementation/batch_13_cycle3_report.md Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
|
||||
| Export | Signature | Notes |
|
||||
|--------|-----------|-------|
|
||||
| `AuthProvider({ children })` | React component | Wraps the app below `BrowserRouter`. Bootstraps via `GET /api/admin/auth/refresh` on mount. |
|
||||
| `AuthProvider({ children })` | React component | Wraps the app below `BrowserRouter`. Bootstraps via `POST /api/admin/auth/refresh` (with `credentials: 'include'`) chained with `GET /api/admin/users/me` on mount — same wire shape as the 401-retry path in `api/client.ts`. |
|
||||
| `useAuth(): AuthContextValue` | hook | Read-only access to `{ user, permissions, login, logout, refresh, loading }`. |
|
||||
|
||||
**`AuthContextValue`** (output DTO):
|
||||
@@ -51,19 +51,20 @@ Consumes only — does not expose. Endpoint set (from `_docs/02_document/modules
|
||||
|
||||
**State Management**: Single React context. Token lives in an HTTP-only cookie (server-managed); the React state holds only the parsed user + permissions. No `localStorage`.
|
||||
|
||||
**Bootstrap sequence**:
|
||||
**Bootstrap sequence** (consolidated by AZ-510):
|
||||
1. Mount → set `loading: true`.
|
||||
2. `api.post('/api/admin/auth/refresh')` to ask the server "do I have a valid session?".
|
||||
3. On 200 → store user + permissions, `loading: false`.
|
||||
4. On 4xx → user stays `null`, `loading: false`. `ProtectedRoute` then redirects.
|
||||
2. `fetch(getApiBase() + endpoints.admin.authRefresh(), { method: 'POST', credentials: 'include' })` to ask the server "do I have a valid session?". Direct `fetch` (not `api.post`) because `api.post` does not thread `credentials: 'include'` and widening it would change CORS posture for every authed callsite.
|
||||
3. On 200 → `setToken(data.token)`, then `api.get(endpoints.admin.usersMe())` to fetch the user shape (the POST refresh response is `{ token }` only — no user payload). On `/users/me` 200 → `setUser(authUser)`, `loading: false`. On `/users/me` failure → `setToken(null)`, `setUser(null)`, `loading: false`, `console.error` carries the diagnostic (refresh OK / user GET failed).
|
||||
4. On refresh 4xx or network failure → `setUser(null)`, `loading: false`. `ProtectedRoute` then redirects to `/login`.
|
||||
5. **StrictMode**: a module-scoped in-flight promise deduplicates the bootstrap network round-trip across React 18+ StrictMode double-mounts so the backend cookie rotation does not race itself.
|
||||
|
||||
> **PRIORITY finding (B3, copied from state.json)**: the bootstrap call inside `AuthContext.tsx` does not pass `credentials: 'include'` consistently — the cookie is therefore not sent on the very first request and bootstrap silently fails on a fresh page load. Confirmed real bug; Step 4 fix.
|
||||
Bootstrap and the 401-retry path in `api/client.ts:88` now share a single wire shape — `POST /api/admin/auth/refresh` with credentials. Finding **B3** (bootstrap missing `credentials: 'include'`) is closed.
|
||||
|
||||
**Spinner UX**: `ProtectedRoute` renders a centered spinner during `loading`. The spinner has **no** `role="status"` / no accessible label / no timeout. (Findings B4, joint with Step 4 client.ts timeout flag.)
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
- **Bootstrap missing `credentials: 'include'`** → users land on `/login` even with a valid cookie session. PRIORITY Step 4.
|
||||
- ~~**Bootstrap missing `credentials: 'include'`**~~ — closed by AZ-510. Bootstrap now uses POST refresh + chained `/users/me` with credentials, matching the 401-retry path.
|
||||
- **Spinner accessibility** — Step 4.
|
||||
- **Token-rotation interaction with SSE** — see `01_api-transport`. Auth refresh works for fetch but breaks every active EventSource.
|
||||
- **No idle-timeout / inactivity logout** — server-side concern; UI tolerates whatever the server enforces.
|
||||
|
||||
Reference in New Issue
Block a user