"use client"; /** * `useMe()` — TanStack-хук для GET /api/v1/me. * * Backend возвращает `UserScope` (см. `backend/app/core/auth.py`): * { username, role: "admin"|"pilot"|"analyst", allowed_paths: string[], deny_paths: string[] } * * Failure modes: * - 401 — нет X-Authenticated-User (локально без Caddy basic_auth). Фронт * трактует это как «dev-режим» и в этом случае route-guard ничего не * блокирует (см. RouteGuard). * - 403 — header есть, но юзер не в `auth/roles.yaml`. Фронт показывает * полноэкранный NoAccessScreen и больше ничего. * * Кешируем «вечно» (`staleTime: Infinity`, без рефетча): роль не меняется * в рамках сессии. Если basic_auth обновится — юзер перезагрузит страницу. */ import { useQuery } from "@tanstack/react-query"; import { apiFetchWithStatus, HTTPError } from "@/lib/api"; // Error type для useQuery: либо `HTTPError` (от apiFetchWithStatus при 4xx/5xx), // либо обычный `Error` (network fail, fetch reject). RouteGuard / TopNav проверяют // статус через `error instanceof HTTPError` — это работает в runtime независимо // от type-level decl. // Должен совпадать с `Role` в `backend/app/core/auth.py` (Literal["admin", "pilot", "analyst"]). // Источник правды — `auth/roles.yaml`; при добавлении новой роли расширить тут и // `ROLE_LABELS` в `components/auth/UserMenu.tsx`. export type Role = "admin" | "pilot" | "analyst"; export interface UserScope { username: string; role: Role; allowed_paths: string[]; deny_paths: string[]; } export const ME_QUERY_KEY = ["auth", "me"] as const; async function fetchMe(): Promise { const { body } = await apiFetchWithStatus("/api/v1/me"); return body; } export function useMe() { return useQuery({ queryKey: ME_QUERY_KEY, queryFn: fetchMe, staleTime: Infinity, gcTime: Infinity, refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, // 401 (dev без Caddy) и 403 (unknown user) — финальные состояния, // ретраить бесполезно. Ретраим только сетевые сбои. retry: (failureCount, error) => { if (error instanceof HTTPError && (error.status === 401 || error.status === 403)) { return false; } return failureCount < 2; }, }); }