Files
ui/_docs/02_document/modules/src__auth__ProtectedRoute.md
T
Oleksandr Bezdieniezhnykh 510df68bcf [AZ-447] autodev Steps 1-4 baseline: docs, tests, refactor specs
Captures the full output of autodev existing-code Phase A through
Step 4 (Code Testability Revision) for the Azaion UI workspace:

- Step 1 Document: _docs/02_document/ (FINAL_report, architecture,
  glossary, components/, modules/, diagrams/, system-flows,
  module-layout) plus _docs/00_problem/ + _docs/01_solution/ +
  _docs/legacy/ + _docs/how_to_test + README.
- Step 2 Architecture Baseline: architecture_compliance_baseline.md.
- Step 3 Test Spec: _docs/02_document/tests/ (environment,
  test-data, blackbox/performance/resilience/security/
  resource-limit tests, traceability-matrix), enum_spec_snapshot,
  expected_results/results_report.md (98 rows), plus the
  run-tests.sh + run-performance-tests.sh runners.
- Step 4 Code Testability Revision: 01-testability-refactoring/
  run dir (list-of-changes C01-C07, deferred_to_refactor,
  analysis/research_findings + refactoring_roadmap) and the 7
  child task specs AZ-448..AZ-454 under _docs/02_tasks/todo/
  plus _dependencies_table.md.
- _docs/_autodev_state.md pins the cursor at Step 4 / refactor
  Phase 4 entry so /autodev resumes cleanly.

Epic AZ-447 (UI testability gates) tracks the 7 child tasks that
will land in subsequent commits.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 00:38:49 +03:00

3.7 KiB

Module: src/auth/ProtectedRoute.tsx

Source: src/auth/ProtectedRoute.tsx (19 lines) Topo batch: B4 (depends on B3: auth/AuthContext)

Purpose

A tiny route guard that gates its children behind an authenticated session. While AuthContext is bootstrapping (loading === true) it shows a spinner; when the bootstrap finishes with no user it redirects to /login; otherwise it renders its children. Mounted exactly once in App.tsx between AuthProvider and FlightProvider so every authenticated page benefits from it. WPF parallel: the implicit "no LoginWindow open ⇒ MainWindow" gate (_docs/legacy/wpf-era.md §4).

Public interface

interface Props { children: ReactNode }
export default function ProtectedRoute(props: Props): JSX.Element

Always returns a JSX.Element — never null. The three rendered shapes are:

  1. Spinner (centered <div> with the orange ring) while loading.
  2. <Navigate to="/login" replace /> when loading === false && user == null.
  3. <>{children}</> otherwise.

replace is intentional: it rewrites the history entry so the back button does not return to a protected route the user was bounced off.

Internal logic

  • Single hook call: const { user, loading } = useAuth(). No local state.
  • Branch order matters — loading is checked before user so a freshly reloaded tab never renders the login redirect during the in-flight refresh attempt. (See the open AuthContext bootstrap-vs-refresh divergence flagged in src__auth__AuthContext.md; if that bug is fixed the spinner duration becomes accurate.)

Dependencies

  • Internal: ./AuthContextuseAuth.
  • External: react-router-dom (Navigate), react (ReactNode type only).

Consumers (intra-repo)

From the §7a dependency graph:

  • src/App.tsx — wraps the entire authenticated route tree: AuthProvider → ProtectedRoute → FlightProvider → Header + nested Routes.

Not used anywhere else; the SPA has a single protected zone.

Data models

None.

Configuration

The /login redirect target is hardcoded. Matches the public route declared in App.tsx. If the public route is ever renamed, update both sites.

The spinner uses Tailwind tokens bg-az-bg, border-az-orange (defined in src/index.css).

External integrations

None directly. Indirectly relies on whatever AuthContext calls during bootstrap — currently GET /api/admin/auth/refresh.

Security

  • No token check is duplicated here — relies entirely on AuthContext. The component cannot be confused into rendering protected children before the bootstrap resolves because loading defaults to true in AuthProvider.
  • Backend authority is unchanged — this is a UI affordance only. Every request the children make MUST also be enforced server-side.
  • The redirect uses Navigate, so query params on the original URL are lost. Acceptable today (no protected route relies on them); flag if a future "deep-link after login" UX appears.

Tests

None.

Notes / open questions

  • The spinner has no role="status" or accessible label — screen readers hear nothing while the bootstrap runs. Cosmetic; flag for Step 4 verification against _docs/ui_design/README.md accessibility notes.
  • No timeout on the loading state — if AuthContext's bootstrap somehow never resolves (e.g., the refresh endpoint hangs), the user sees an infinite spinner. client.ts does not currently set a request timeout either; flag jointly with Step 4.
  • The <>{children}</> Fragment wrap is intentional: returning children directly would make the type ReactNode rather than JSX.Element and would not satisfy the route element slot in react-router-dom@7.