mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 11:31:11 +00:00
[AZ-510][AZ-511][AZ-512][AZ-513] Cycle 3 Steps 12-15 + admin prereq
ci/woodpecker/push/build-arm Pipeline failed
ci/woodpecker/push/build-arm Pipeline failed
Wrap up cycle 3 across the autodev existing-code Phase B steps that follow Implement (Steps 12-15), plus the cross-workspace prerequisite ticket filed for AZ-512. Step 12 - Test-Spec Sync: - Un-quarantine FT-P-01 in traceability-matrix (closed by AZ-510) - Add AZ-510 chained /users/me failure-path test reference under AC-23 - Note AZ-512 deferral status under O9 (P12 Phase B target) Step 13 - Update Docs (task mode): - Refresh src__auth__AuthContext module doc with AZ-510 wire shape (POST refresh + chained /users/me + bootstrapInflight guard) - Add usersMe() to src__api__endpoints module doc + consumer note - Rename src__features__annotations__classColors module doc to src__class-colors__classColors (matches AZ-511 git mv); refresh header - Refresh src__components__DetectionClasses + src__features__annotations module group doc for the new class-colors barrel import path - Update components/11_class-colors Module Inventory to point at the renamed module doc filename - Rewrite system-flows.md Flow F2 (Bearer auto-refresh) with the AZ-510 POST + chained /users/me sequence; close Finding B3 references - Generate ripple_log_cycle3 documenting all changed source files, their reverse-dependency search results, and the docs touched Step 14 - Security Audit (cycle-3 delta): - Resume mode against cycle-2 baseline; cycle-2 artifacts untouched - Re-run bun audit on both roots: clean (cycle-2 inline fix held) - Re-rate OWASP A06: FAIL -> PASS; A07: PASS_WITH_KNOWN -> PASS (B3 closed by AZ-510) - New finding F-SAST-CY3-1 (LOW): __resetBootstrapInflightForTests exposed via src/auth public barrel; defer to hygiene cycle - Verdict: FAIL -> PASS_WITH_WARNINGS; one HIGH (F-SAST-1 mission-planner git-history key, unchanged) remains - Add amendment banner to cycle-2 security_report.md Step 15 - Performance Test: - Static profile NFT-PERF-01 PASS (290 575 B gzipped vs 2 MB budget; ~14% of budget; no regression from AZ-510 surface additions) - E2E profile SKIP (Playwright perf project still pending AZ-457..AZ-482); legitimate skip per test-run skill, gap acknowledged in report - AZ-510 200ms p95 chain NFR verified at spec level only - no CI gate yet (covered by future AZ-457..AZ-482 work) Cross-workspace prerequisite (AZ-513 just filed): - Updated _docs/_process_leftovers/2026-05-13_az-512-admin-classes-prereq.md to reflect AZ-513 filing on admin/ workspace (parent epic AZ-509, Blocks link to AZ-512). Companion task spec added in admin/ repo (separate commit there, owned by admin/ workspace). State file: advanced to Step 16 (Deploy) per autodev existing-code flow. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -123,16 +123,18 @@ flowchart TD
|
||||
|
||||
---
|
||||
|
||||
## Flow F2: Bearer auto-refresh on 401 (TWO refresh paths exist in code)
|
||||
## Flow F2: Bearer auto-refresh (bootstrap + 401-retry)
|
||||
|
||||
> **Cycle 3 / 2026-05-13 — AZ-510 consolidated the two refresh paths.** The historical "two divergent paths" wording below has been rewritten. The previous bug (finding B3 / Vision P3 violation) is now CLOSED.
|
||||
|
||||
### Description
|
||||
|
||||
There are **two distinct refresh code paths** in the source — the verification pass (Step 4) caught both:
|
||||
There are two refresh trigger points in the source, but they now share a single wire shape:
|
||||
|
||||
1. **Bootstrap path** — `AuthContext.tsx:24` calls `api.get('/api/admin/auth/refresh')` on app mount. This **does NOT have `credentials:'include'`** because `api/client.ts` doesn't add it on GET. Result: the cookie is not sent, the bootstrap silently fails, the user starts unauthenticated even when they have a valid refresh cookie.
|
||||
2. **401-retry path** — `api/client.ts:44` calls `fetch('/api/admin/auth/refresh', { method: 'POST', credentials: 'include' })` automatically when any authenticated fetch returns 401. This path IS correct.
|
||||
1. **Bootstrap path** — `AuthContext.tsx` (`runBootstrap()` helper, guarded by a module-scoped `bootstrapInflight` promise to deduplicate React 18+ StrictMode dev double-mounts). On `<AuthProvider>` mount it calls `fetch(getApiBase() + endpoints.admin.authRefresh(), { method: 'POST', credentials: 'include' })`. On success it sets the bearer and **chains** `api.get<AuthUser>(endpoints.admin.usersMe())` (= `GET /api/admin/users/me`) to fetch the user record (the POST refresh response is `{ token }` only). On any failure path the bearer is cleared first, then `user: null` + `loading: false`.
|
||||
2. **401-retry path** — `api/client.ts:73` automatically calls `fetch('/api/admin/auth/refresh', { method: 'POST', credentials: 'include' })` and replays the original request when any authenticated fetch returns 401.
|
||||
|
||||
The bootstrap path is the bug surfaced as finding B3 PRIORITY. The 401-retry path is the silent fallback that does work but only after the user has already hit a 401.
|
||||
Both paths now POST with `credentials:'include'` and rely on the HttpOnly refresh cookie set on `/login`.
|
||||
|
||||
### Preconditions
|
||||
|
||||
@@ -157,7 +159,7 @@ sequenceDiagram
|
||||
ApiClient-->>Page: response
|
||||
```
|
||||
|
||||
### Sequence Diagram (Bootstrap path on app mount — broken)
|
||||
### Sequence Diagram (Bootstrap path on app mount — POST refresh + chained `/users/me`, AZ-510)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@@ -166,18 +168,25 @@ sequenceDiagram
|
||||
participant AdminApi as admin/ service
|
||||
|
||||
App->>AuthCtx: <AuthProvider> mounts
|
||||
AuthCtx->>AdminApi: GET /admin/auth/refresh (NO credentials:'include' — finding B3)
|
||||
AdminApi-->>AuthCtx: 401 (no cookie sent)
|
||||
AuthCtx->>AuthCtx: setLoading(false), user stays null
|
||||
AuthCtx-->>App: ProtectedRoute sees null user → redirects to /login
|
||||
AuthCtx->>AuthCtx: bootstrapInflight guard (StrictMode dedupe)
|
||||
AuthCtx->>AdminApi: POST /admin/auth/refresh (credentials:'include')
|
||||
AdminApi-->>AuthCtx: 200 {token} + Set-Cookie: refresh=...
|
||||
AuthCtx->>AuthCtx: setToken(token)
|
||||
AuthCtx->>AdminApi: GET /admin/users/me (Authorization: Bearer <token>)
|
||||
AdminApi-->>AuthCtx: 200 {id, email, permissions}
|
||||
AuthCtx->>AuthCtx: setUser(...), setLoading(false)
|
||||
AuthCtx-->>App: ProtectedRoute sees user → renders gated route
|
||||
```
|
||||
|
||||
### Error Scenarios
|
||||
|
||||
| Error | Where | Detection | Recovery |
|
||||
|-------|-------|-----------|----------|
|
||||
| Bootstrap GET refresh missing `credentials:'include'` | `AuthContext.tsx:24` | server returns 401 because cookie was not sent | **Bug today** — finding B3 PRIORITY. Symptom: a user with a valid refresh cookie still gets bounced to `/login` on every fresh tab. Step 4 fix: change to POST with `credentials:'include'` (matching the 401-retry path), or just delete the bootstrap GET and let the first authenticated fetch's 401 trigger the retry path. |
|
||||
| 401-retry path | `api/client.ts:44` | works | (no fix needed) |
|
||||
| ~~Bootstrap GET refresh missing `credentials:'include'`~~ | — | — | **CLOSED 2026-05-13 by AZ-510.** Bootstrap now POSTs with `credentials:'include'`. Finding B3 / Vision P3 violation resolved. |
|
||||
| Refresh 401 on bootstrap | `AuthContext.tsx` `runBootstrap()` | non-OK response from POST refresh | `setUser(null)` + `setLoading(false)` → `ProtectedRoute` redirects to `/login`. No console.error (expected on first visit / signed-out user). |
|
||||
| Refresh network error on bootstrap | `AuthContext.tsx` `runBootstrap()` | outer `.catch` on the POST refresh fetch | `setToken(null)` + `setUser(null)` + `setLoading(false)` + `console.error('[AuthContext] Bootstrap failed:', err)`. UI redirects to `/login`. |
|
||||
| Refresh 200 → `/users/me` failure (401, network, etc.) | `AuthContext.tsx` `runBootstrap()` | inner `try/catch` around `api.get(usersMe())` | `setToken(null)` first (Constraint #4 — bearer cleared before user state) + `console.error('[AuthContext] Refresh succeeded but /users/me failed:', err)` + return null → top-level then-handler sets `user: null` + `loading: false`. Covered by `AC-4 (AZ-510)` regression test. |
|
||||
| 401-retry path inside `api/client.ts` | `api/client.ts:73` | works | (no fix needed) |
|
||||
| Refresh cookie expired or revoked | refresh call | 401 | UI redirects to `/login`. |
|
||||
| SSE subscription holds a stale bearer | active EventSource | server closes the SSE stream | No reconnect logic today (Step 8 hardening). |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user