Files
mdm-ordinis/ordinis-admin-ui/src/auth/RequireAuth.tsx
T
Zimin A.N. 03973a943e chore(ui): убрать упоминание TokenSync из RequireAuth JSDoc
TokenSync.tsx был удалён в Phase 1a (keycloak-js migration, MR !270).
В JSDoc на RequireAuth.tsx осталась ссылка на его поведение —
обновляем описание под текущую модель: apiClient request interceptor
+ ensureFreshToken на 401 (см. api/client.ts:64).

Минор-долг из checkpoint'а keycloak-js-altum-migration.
2026-05-26 15:28:24 +03:00

82 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { type ReactNode } from 'react'
import { useAuth } from '@/auth/useAuth'
/**
* Auth gate с гостевым режимом view-only.
*
* <p>До v1.1.1 этот компонент force-redirect'ил неаутентифицированных юзеров
* на Keycloak. Теперь — guest-friendly: anonymous user видит UI с PUBLIC scope
* данными (backend отдаёт PUBLIC dictionaries без auth, см. SecurityConfig
* permissive mode + ScopeContext anonymous → PUBLIC).
*
* <p>Логин теперь пользовательское действие: клик на кнопку «Войти» в top bar
* → {@link useAuth().signinRedirect()}. После возврата из Keycloak пользователь
* получает scope по ролям JWT и видит INTERNAL/RESTRICTED данные.
*
* <p>Сценарии:
* <ul>
* <li>First visit, нет токена → render children, top bar показывает «Войти».</li>
* <li>Click «Войти» → signinRedirect → Keycloak → callback → authenticated.</li>
* <li>OIDC error в URL (?error=...) → error screen (не редиректим в цикле).</li>
* <li>auth.error (Keycloak недоступен) → error screen с сообщением.</li>
* <li>Mutations пытаются 401 → apiClient interceptor ловит, делает
* ensureFreshToken + retry; на refresh failure keycloak-js сам
* редиректит на /login (full reload). Inline ошибка остаётся для
* anonymous users без CTA-сценария.</li>
* </ul>
*/
export function RequireAuth({ children }: { children: ReactNode }) {
const auth = useAuth()
const oidcError =
typeof window !== 'undefined' &&
new URLSearchParams(window.location.search).get('error')
if (oidcError) {
return (
<div className="min-h-screen flex items-center justify-center p-8">
<div className="max-w-md text-center space-y-2">
<h1 className="text-title-lg font-sans">Ошибка входа</h1>
<p className="text-body text-ink-2">
Keycloak вернул ошибку:{' '}
<code className="font-mono">{oidcError}</code>
</p>
<button
type="button"
className="mt-4 inline-flex items-center justify-center rounded-sm border border-accent text-accent px-4 py-2 text-body hover:bg-accent-bg"
onClick={() => {
window.location.href = '/'
}}
>
Попробовать снова
</button>
</div>
</div>
)
}
// 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}</>
}