Skip to main content

Auth & session

auth/ · modules/auth/ · pages/auth/

Authorization is capability-keyed, never role-string-based in the UI, and is layered across three sources of increasing authority: SSR-decrypted cookies (bootstrap only) → a Redux permissions snapshot (fallback cache) → the live session-context React Query result (authoritative).

:::info The one invariant that matters A live false always overrides a persisted/bootstrap true, and anything in HIGH_RISK_CAPABILITY_KEYS fails closed whenever live data isn't available yet. This is tested explicitly in auth/__tests__/sessionContextPrecedence.test.ts. :::

See also: User flow for the full login → session → dashboard diagram.

SessionContextProvider​

modules/auth/SessionContextProvider.tsx — the core of the module. A React Context wrapping useSessionContextQuery (GET auth/session-context/, 45s staleTime, polls every 90s, no retry on 401/403). Reconciles the live result with Redux/cookie bootstrap and owns several side effects:

  • 401/403 → clears Redux permissions, drops the query cache, redirects to login.
  • permission_version changes → invalidates every cached query touching security/compliance/policy/fff_, forcing protected data to refetch under the new authorization.
  • capabilities.risk_dashboard_login === false → wipes state, redirects to /auth/access-denied.
  • Logged in but zero section capabilities → redirects to /dashboard/no-sections.

Exposes session, isLive, isDegraded, isUnauthorized, capability(key), hasPermission(name), refetch() via useSessionContext() / useSessionContextOptional() / useCapability(key).

Capability guards​

HelperFileUse
CapabilityGuard / useAuthorizedRouteCapabilityGuard.tsxComponent/hook that hides children (or soft-redirects) until a capability resolves true; waits rather than flashing content while loading.
resolveCapabilityAvailabilityresolveCapabilityAvailability.tsLayers feature flags/runtime toggles/eligibility on top of authorization for individual controls: visible = authorized; enabled = authorized && feature && runtime && eligibility — unauthorized always wins hidden.
useRiskDashboardEntryGuard / SessionCapabilityPageShellriskDashboardAccess.tsxPage-level dashboard-entry guard: redirects unauthenticated/unauthorized users, shows a distinct degraded/retry surface (never an infinite spinner), routes to the first authorized section via AUTHORIZED_SECTION_ROUTE_ORDER.
useCapabilityQueryEnabled / useCapabilityMutationGateuseCapabilityGuards.tsGates React Query enabled flags and mutation buttons so nothing fires while unauthorized, degraded, or still loading.

Login flow​

pages/auth/login → modules/auth/pages/loginUI/loginUI.tsx (form, wrapped in AuthWrapper) → useLogin.tsx posts credentials to auth/login_2fa/. If 2FA is required, verifyOTP2fa.tsx collects the OTP and calls login-2fa-verify/; if disabled, the verify call fires immediately with no OTP. Either path ends in a full router.reload() so SSR re-reads the newly-set cookies.

Logout (util/useLogout.ts) posts to auth/logout/, clears both Redux slices, purges redux-persist storage, and redirects to login.

:::note Dead code, for context modules/auth/pages/signupUI/signUpUI.tsx and several 2FA-adjacent components (PhoneVerification, ForgotPassword) are UI scaffolding with no wired mutation/route — signup and forgot-password are not live features despite PAGE_PATHS entries existing for them. :::