import { type ReactNode } from 'react'
import { useAuth } from '@/auth/useAuth'
/**
* Auth gate с гостевым режимом view-only.
*
*
До v1.1.1 этот компонент force-redirect'ил неаутентифицированных юзеров
* на Keycloak. Теперь — guest-friendly: anonymous user видит UI с PUBLIC scope
* данными (backend отдаёт PUBLIC dictionaries без auth, см. SecurityConfig
* permissive mode + ScopeContext anonymous → PUBLIC).
*
*
Логин теперь пользовательское действие: клик на кнопку «Войти» в top bar
* → {@link useAuth().signinRedirect()}. После возврата из Keycloak пользователь
* получает scope по ролям JWT и видит INTERNAL/RESTRICTED данные.
*
*
Сценарии:
*
*
First visit, нет токена → render children, top bar показывает «Войти».
OIDC error в URL (?error=...) → error screen (не редиректим в цикле).
*
auth.error (Keycloak недоступен) → error screen с сообщением.
*
Mutations пытаются 401 → TokenSync ловит, делает silentRenew или
* показывает inline ошибку (anonymous user → не редиректим без CTA).
*
*/
export function RequireAuth({ children }: { children: ReactNode }) {
const auth = useAuth()
const oidcError =
typeof window !== 'undefined' &&
new URLSearchParams(window.location.search).get('error')
if (oidcError) {
return (
Ошибка входа
Keycloak вернул ошибку:{' '}
{oidcError}
)
}
// Keycloak unreachable / OIDC discovery failed — раньше показывали amber
// banner вверху страницы, но per user feedback ("Авторизация недоступна:
// Failed to fetch.?") это noise: anonymous mode работает без auth, error
// banner лишь пугает. Тихо рендерим children в guest mode — AuthBadge
// в TopBar показывает Sign in для retry.
if (auth.error && !auth.isAuthenticated) {
return <>{children}>
}
// ВАЖНО: НЕ блокируем UI пока {@code auth.isLoading=true}. Initial OIDC
// bootstrap может зависать на 10-20 сек если Keycloak silent SSO iframe
// блокируется X-Frame-Options или сеть медленная. Anonymous user в это
// время видит только spinner и может подумать что сайт сломан.
// Решение: рендерим children immediately. AuthBadge в top bar сам
// показывает loading state своим маленьким индикатором, а PUBLIC данные
// (которые backend отдаёт без auth) грузятся параллельно с silent SSO.
// Если silent SSO succeed позже — useAuth() обновит scope, кнопки
// create/edit активируются автоматически (canMutate реактивен).
// Anonymous OR authenticated OR loading — рендерим children всегда. UI в
// компонентах сам адаптируется (через useAuth().isAuthenticated):
// hide mutation buttons, show «Войти» CTA, тонировать blocked actions.
return <>{children}>
}