Files
MIKROTIK-RMM/frontend/src/App.tsx
T
2GT-RichandClaude Opus 4.8 c0f230bdf2 feat(auth): OIDC / SSO single sign-on (v0.17.0-beta)
Add OpenID Connect single sign-on so the platform can authenticate against
any standards-compliant provider (Entra ID, Okta, Google Workspace, Keycloak,
Authentik, …) alongside local accounts. Resolves the feature request in #73.

Flow (backend-driven Authorization Code + PKCE):
- GET /api/auth/oidc/login builds the authorize URL and stores the PKCE/nonce
  transaction in Redis (single-use, 10m TTL); the callback validates the ID
  token via openid-client (JWKS signature, iss/aud/exp/nonce), resolves the
  identity to a local user, and mints the platform's normal session JWT —
  handed to the SPA via URL fragment. No sessions/cookies introduced.

Identity mapping (users table gains email/oidc_subject/oidc_issuer/
auth_provider; password_hash now nullable; partial unique indexes):
- First sign-in auto-provisions a user, or links to an existing local account
  when the IdP email is verified and matches.
- Roles derive from a configurable IdP group -> role map with a default
  fallback; existing users are never auto-demoted on a groupless login.

Configuration (admin-only, no env/redeploy needed):
- Stored as an app_settings row with the client secret encrypted at rest
  (utils/crypto); masked in responses and excluded from GET /api/settings.
- New UI: Settings -> SSO / OIDC panel (issuer/client, claim + group->role
  mapping, provisioning toggles, discovery test, copyable redirect URI), a
  login-page SSO button, and an SSO badge in Users & Roles.

Security: local break-glass login preserved; PKCE + single-use state; email
linking requires email_verified; same-site-only post-login redirect.

Adds openid-client@^5 (CommonJS). 17 new unit tests (claim/role mapping,
username derivation, config secret round-trip/masking); 114 backend tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 18:54:36 -07:00

86 lines
4.6 KiB
TypeScript

import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { useAuthStore } from './store/authStore';
import AppLayout from './components/layout/AppLayout';
import LoginPage from './pages/LoginPage';
import DashboardPage from './pages/DashboardPage';
import DevicesPage from './pages/DevicesPage';
import DeviceDetailPage from './pages/DeviceDetailPage';
import ClientsPage from './pages/ClientsPage';
import EventsPage from './pages/EventsPage';
import TopologyPage from './pages/TopologyPage';
import BackupsPage from './pages/BackupsPage';
import FirmwarePage from './pages/FirmwarePage';
import SettingsPage from './pages/SettingsPage';
import OidcCallbackPage from './pages/OidcCallbackPage';
import WirelessPage from './pages/WirelessPage';
import WirelessSettingsPage from './pages/WirelessSettingsPage';
import GuestWifiPage from './pages/GuestWifiPage';
import ClientDetailPage from './pages/ClientDetailPage';
import NetworkServicesOverviewPage from './pages/NetworkServicesOverviewPage';
import NetworkServicesDHCPPage from './pages/NetworkServicesDHCPPage';
import NetworkServicesDNSPage from './pages/NetworkServicesDNSPage';
import NetworkServicesNTPPage from './pages/NetworkServicesNTPPage';
import NetworkServicesWireGuardPage from './pages/NetworkServicesWireGuardPage';
import NetworkServicesSyslogPage from './pages/NetworkServicesSyslogPage';
import NetworkServicesNetflowPage from './pages/NetworkServicesNetflowPage';
import NetworkServicesDiscoveryPage from './pages/NetworkServicesDiscoveryPage';
import TrafficAnalyticsPage from './pages/TrafficAnalyticsPage';
import SecurityPage from './pages/SecurityPage';
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const token = useAuthStore((s) => s.token);
if (!token) return <Navigate to="/login" replace />;
return <>{children}</>;
}
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/auth/callback" element={<OidcCallbackPage />} />
<Route
path="/"
element={
<ProtectedRoute>
<AppLayout />
</ProtectedRoute>
}
>
<Route index element={<Navigate to="/dashboard" replace />} />
<Route path="dashboard" element={<DashboardPage />} />
<Route path="devices" element={<DevicesPage />} />
<Route path="devices/:id" element={<DeviceDetailPage />} />
<Route path="clients" element={<ClientsPage />} />
<Route path="clients/:mac" element={<ClientDetailPage />} />
<Route path="events" element={<EventsPage />} />
<Route path="topology" element={<TopologyPage />} />
<Route path="backups" element={<BackupsPage />} />
<Route path="firmware" element={<FirmwarePage />} />
<Route path="settings" element={<SettingsPage />} />
{/* Legacy routes — consolidated in the v0.16.4 UI reorganization */}
<Route path="switches" element={<Navigate to="/devices?type=SW" replace />} />
<Route path="switches/settings" element={<Navigate to="/network-services/discovery" replace />} />
<Route path="routers" element={<Navigate to="/devices?type=RTR" replace />} />
<Route path="routers/settings" element={<Navigate to="/network-services/discovery" replace />} />
<Route path="wireless/clients" element={<Navigate to="/clients?type=wireless" replace />} />
<Route path="wireless" element={<WirelessPage />} />
<Route path="wireless/settings" element={<WirelessSettingsPage />} />
<Route path="wireless/guest" element={<GuestWifiPage />} />
<Route path="network-services" element={<NetworkServicesOverviewPage />} />
<Route path="network-services/dhcp" element={<NetworkServicesDHCPPage />} />
<Route path="network-services/dns" element={<NetworkServicesDNSPage />} />
<Route path="network-services/ntp" element={<NetworkServicesNTPPage />} />
<Route path="network-services/wireguard" element={<NetworkServicesWireGuardPage />} />
<Route path="network-services/syslog" element={<NetworkServicesSyslogPage />} />
<Route path="network-services/netflow" element={<NetworkServicesNetflowPage />} />
<Route path="network-services/discovery" element={<NetworkServicesDiscoveryPage />} />
<Route path="traffic" element={<TrafficAnalyticsPage />} />
<Route path="security" element={<SecurityPage />} />
</Route>
<Route path="*" element={<Navigate to="/dashboard" replace />} />
</Routes>
</BrowserRouter>
);
}