feat(admin-ui): SSO через Keycloak (PKCE, public client)

react-oidc-context + oidc-client-ts. Confidential client тоже работает —
SPA просто не использует client_secret.

- src/auth/oidcConfig.ts — UserManager settings (authority, redirect_uri,
  PKCE, automaticSilentRenew). VITE_KEYCLOAK_{URL,REALM,CLIENT_ID} с дефолтами
  на nstart realm + ordinis client.
- src/auth/TokenSync.tsx — синкает access_token в localStorage и axios default
  header после login/refresh. Existing API код не трогали — interceptor уже
  читает localStorage.
  Bonus: 401 interceptor триггерит signinSilent → signinRedirect fallback.
- src/auth/AuthBadge.tsx — header'ный бейдж: "Войти" если anonymous, имя +
  logout если залогинен.
- main.tsx обёрнут в <AuthProvider>.
- __root.tsx показывает AuthBadge рядом с language switch.
- .env.example с конфигом Keycloak.
- i18n auth.login / auth.logout (RU + EN).

Keycloak client `ordinis` — настроить:
  Valid redirect URIs: https://ordinis.k8s.265.nstart.cloud/*,
                       https://ordinis.k8s.264.nstart.cloud/*,
                       http://localhost:5173/*
  Web Origins: те же + (или "+"), PKCE Code Challenge: S256
This commit is contained in:
Zimin A.N.
2026-05-04 18:28:31 +03:00
parent 2fc9b451e4
commit cef78f9971
9 changed files with 224 additions and 10 deletions
+13 -7
View File
@@ -3,7 +3,10 @@ import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import { NStartUiProvider, DatePickerProvider, DATE_PICKER_LOCALE_RU } from '@nstart/ui'
import { AuthProvider } from 'react-oidc-context'
import { routeTree } from './routeTree.gen'
import { oidcAuthConfig } from '@/auth/oidcConfig'
import { TokenSync } from '@/auth/TokenSync'
import './i18n'
import './styles.css'
@@ -23,12 +26,15 @@ declare module '@tanstack/react-router' {
createRoot(document.getElementById('root')!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<NStartUiProvider>
<DatePickerProvider datePicker={DATE_PICKER_LOCALE_RU}>
<RouterProvider router={router} />
</DatePickerProvider>
</NStartUiProvider>
</QueryClientProvider>
<AuthProvider {...oidcAuthConfig}>
<TokenSync />
<QueryClientProvider client={queryClient}>
<NStartUiProvider>
<DatePickerProvider datePicker={DATE_PICKER_LOCALE_RU}>
<RouterProvider router={router} />
</DatePickerProvider>
</NStartUiProvider>
</QueryClientProvider>
</AuthProvider>
</StrictMode>,
)