[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:
Oleksandr Bezdieniezhnykh
2026-05-13 02:59:31 +03:00
parent 098a556460
commit 70fb452805
29 changed files with 471 additions and 92 deletions
@@ -0,0 +1,108 @@
# Batch 13 — AZ-510 (Auth bootstrap refresh consolidation)
**Date**: 2026-05-13
**Cycle**: 3 — autodev Step 10 (Implement), batch 1 of 3 (fixes-first order: AZ-510 → AZ-511 → AZ-512)
**Tickets**: AZ-510 (Epic AZ-509)
**Verdict**: PASS
---
## Task Results
| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|------|--------|----------------|-------|-------------|--------|
| AZ-510_auth_bootstrap_consolidation | Done | 25 files | 231 passed / 13 skipped (full fast suite) | 6/6 ACs covered | None |
## AC Test Coverage: 6/6 covered
- AC-1 → `AuthContext.test.tsx` FT-P-01 (POST + `credentials:'include'` + no GET refresh)
- AC-2 → FT-P-01 (chain to `/users/me`, bearer set, loading false)
- AC-3 → `ProtectedRoute.test.tsx` (failed bootstrap → spinner → `/login` once); also
exercised by NFT-SEC-01's intermediate state
- AC-4 → `AuthContext.test.tsx` "AC-4 (AZ-510)" test (new, lines 108-138)
- AC-5 → `ProtectedRoute.test.tsx` admin-route success cases (no `/login` on success bootstrap)
- AC-6 → `AuthContext.test.tsx` NFT-SEC-01 + FT-P-03 (401-retry path unchanged); plus existing
`src/api/client.test.ts` retry tests
## Code Review Verdict: PASS
- Report: `_docs/03_implementation/reviews/batch_13_review.md`
- 0 findings (Critical / High / Medium / Low)
- Resolved baseline finding **B3** (Auth bootstrap missing `credentials:'include'` — Vision P3 violation)
## Auto-Fix Attempts: 0
No auto-fix loop needed.
## Stuck Agents: 0
---
## Implementation Notes
### Changed Files
**Production code**:
- `src/auth/AuthContext.tsx` — replaced GET-refresh `useEffect` with `runBootstrap()` POST +
chained `/users/me`; added module-scoped `bootstrapInflight` for StrictMode safety; defensive
`hasPermission` against legacy `/users/me` payloads missing `permissions`.
- `src/auth/index.ts` — re-exports `__resetBootstrapInflightForTests` to keep tests off deep
imports (STC-ARCH-01).
- `src/api/endpoints.ts` — added `endpoints.admin.usersMe()` builder; STC-ARCH-02 forbids the
literal `/api/admin/users/me` outside `endpoints.ts`.
**Tests** (handler swaps + new AC-4 + setup hook):
- `src/auth/AuthContext.test.tsx` — un-quarantined FT-P-01 (now POST regression guard); updated
FT-P-03 / NFT-SEC-01 / NFT-SEC-02 to POST refresh + chained `/users/me`; added AC-4 (AZ-510)
test.
- `src/auth/ProtectedRoute.test.tsx``withUser` helper now uses POST refresh + GET `/users/me`;
all `http.get('/api/admin/auth/refresh', …)` mocks swapped to POST.
- `src/components/Header.test.tsx``wireAuthAndFlights` updated to POST refresh + `/users/me`.
- `src/api/endpoints.test.ts` — wire-contract assertion for `endpoints.admin.usersMe()`.
- `tests/msw/handlers/admin.ts` — default `GET /users/me` handler returns user with explicit
`permissions: seedPermissions[opAlice.id] ?? []` (was missing → caused
`TypeError: Cannot read properties of undefined (reading 'includes')`).
- `tests/setup.ts``afterEach` hook calls `__resetBootstrapInflightForTests` to prevent
module-scoped inflight promise leakage between tests.
- 15 broader test files (`tests/*.test.tsx`) — bulk swap of intentional-fail bootstrap
handlers from `http.get``http.post` for `/api/admin/auth/refresh`. Without the swap the
POST-based bootstrap would auto-authenticate from the default handler and break tests that
expect `user: null`.
**Documentation**:
- `_docs/02_document/components/02_auth/description.md` — bootstrap section rewritten to
describe POST + chained `/users/me`; Finding B3 marked closed.
### Resolved Finding
- **B3** (`_docs/02_document/04_verification_log.md`): Auth bootstrap missing
`credentials:'include'` — closed by AZ-510. Architecture Vision principle P3 ("bearer in
memory, refresh in HttpOnly cookie") now satisfied on the bootstrap path.
### Test Run
- Static profile: PASS (all gates including STC-ARCH-01 / STC-ARCH-02 green)
- Fast profile: 31 files, 231 passed / 13 skipped (quarantined). No new failures.
- Suite duration: ~30s (fast) + ~55s (static).
### Notable Failure-Then-Fix Path During Implementation
1. **`ProtectedRoute.test.tsx` hangs (3 tests)** — module-scoped `bootstrapInflight` leaked
the never-resolving promise from one test into subsequent renders. Fix: test-only export
+ afterEach reset hook.
2. **STC-ARCH-01 violation**`tests/setup.ts` initially imported the test helper directly
from `src/auth/AuthContext`. Fix: re-export through the `src/auth` barrel; switch import.
3. **Widespread test failures** (`flight_selection_persistence.test.tsx`,
`browser_support_responsive.test.tsx`, …) — default `/users/me` handler omitted
`permissions`, so `hasPermission` crashed on `undefined.includes`. Fix: defensive
`hasPermission` + handler now seeds `permissions` from `seedPermissions[opAlice.id]`.
4. **Bulk handler swap** — 15 test files mocked `http.get('/api/admin/auth/refresh', …)` to
force bootstrap fail. Production now uses POST so the GET override is ignored and bootstrap
auto-authenticates from defaults. Fixed via per-file `sed` in a `for` loop (single `sed`
with the full file list hit a shell command-line length issue and reported "No such file
or directory").
## Next Batch
**Batch 14 (cycle 3 / batch 2 of 3)** — AZ-511 classColors carve-out to `src/class-colors/`
(closes Finding F3 + 5-coupled-places exemption).