Remove password authentication everywhere; the only session doors are passkey (WebAuthn), email OTP, in-game bind codes, QR scan-login, and op-login vouching. Remediates the 33-finding cross-check review across backend, CLI, panel, plugins, and docs. Backend/CLI: - Drop password routes and fields from account/user/onboard/auth handlers; align tests (new account subtests, naming reserves "console", op-login/onboard/qr-login test updates). - Add migrations 0016_op_login.sql and 0017_drop_password.sql. - Thread panel/admin hostnames from hostcfg through api.go, setup_panel.go, tui_root.go and tui_preflight.go instead of hardcoding; bootstrap.sh writes panel-hostname/admin-hostname into felis.toml. - Reword breakglass and TUI copy for passwordless flows. Panel: - Delete the ChangePassword page and all password UI; align login/auth/api/types with the passwordless contract; add the migration and op-login approval flows. - i18n: convert ImageBuildPage durations/status badges and ServerLuckPerms strings to translation keys; drop 72 orphan keys per locale; unify the title as "Felis - Console". Plugins (all six rebuilt): - Velocity waiting router returns 503 at_capacity during wake; MOTD/control-channel copy and config comments. - Paper zh menu title; Limbo bind-code TTL 600s with panel_url preference; unified /link lines in fabric/forge/neoforge; shared link-client javadoc contract fixes. Docs: openapi.yaml, sequence-diagrams.md, deploy/limbo/README.md and plugins/README.md aligned with the implementation. BREAKING CHANGE: migration 0017 irreversibly drops users.password_hash and users.must_change_password; password login cannot be restored after migrating.
86 lines
4.4 KiB
TypeScript
86 lines
4.4 KiB
TypeScript
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
|
|
import { ThemeProvider } from "@/lib/theme";
|
|
import { TierProvider } from "@/lib/tier";
|
|
import { AppShell } from "@/components/AppShell";
|
|
import { RequireAdmin } from "@/components/RequireAdmin";
|
|
import { RequireAuth } from "@/components/RequireAuth";
|
|
import { RequireOwner } from "@/components/RequireOwner";
|
|
import { Login } from "@/pages/Login";
|
|
import { Setup } from "@/pages/Setup";
|
|
import { Dashboard } from "@/pages/Dashboard";
|
|
import { ServersPage } from "@/pages/servers/ServersPage";
|
|
import { ServerConsole } from "@/pages/ServerConsole";
|
|
import { ServerPlayers } from "@/pages/ServerPlayers";
|
|
import { ServerBackups } from "@/pages/ServerBackups";
|
|
import { ServerLuckPerms } from "@/pages/ServerLuckPerms";
|
|
import { Account } from "@/pages/Account";
|
|
import { ImageAdmin } from "@/pages/admin/ImageAdmin";
|
|
import { ImageBuildPage } from "@/pages/admin/ImageBuildPage";
|
|
import { SubmissionsPage } from "@/pages/admin/SubmissionsPage";
|
|
import { UsersPage } from "@/pages/admin/UsersPage";
|
|
import { UserDetailPage } from "@/pages/admin/UserDetailPage";
|
|
import { MySubmissionsPage } from "@/pages/MySubmissionsPage";
|
|
import { UpdatesPage } from "@/pages/admin/UpdatesPage";
|
|
|
|
// Three UX surfaces over two Zero-Trust tiers (DESIGN-WEB-3SIDES):
|
|
// / User-Side — app-tier, every authenticated principal
|
|
// /admin/* Admin-Side — admin-tier, server & content administration
|
|
// /ops/* SysAdmin-Side— admin-tier, platform observability cockpit
|
|
// Both admin groups sit behind the SAME RequireAdmin wrapper (one is_admin gate);
|
|
// the split is by concern, not by tier. TierProvider fetches /me once at boot.
|
|
export default function App() {
|
|
return (
|
|
<ThemeProvider>
|
|
<TierProvider>
|
|
<BrowserRouter>
|
|
<Routes>
|
|
{/* Pre-app sign-in surface (spec §B, passwordless). It sits OUTSIDE
|
|
RequireAuth — RequireAuth redirects here — and outside AppShell, so
|
|
it renders its own centered chrome with no nav/tier dependency. */}
|
|
<Route path="/login" element={<Login />} />
|
|
{/* Owner first-run onboarding. Like /login it sits OUTSIDE RequireAuth:
|
|
the visitor arrives from the `felis setup` link with no session, and
|
|
redeeming the one-time token is what mints one. */}
|
|
<Route path="/setup" element={<Setup />} />
|
|
|
|
{/* Everything else requires a session. RequireAuth gates the whole app:
|
|
no/expired session → /login, transient /me failure → still renders
|
|
(graded ZT). */}
|
|
<Route element={<RequireAuth />}>
|
|
<Route element={<AppShell />}>
|
|
{/* User-Side — app-tier */}
|
|
<Route index element={<Dashboard />} />
|
|
<Route path="servers" element={<ServersPage />} />
|
|
<Route path="servers/:name" element={<ServerConsole />} />
|
|
<Route path="servers/:name/players" element={<ServerPlayers />} />
|
|
<Route path="servers/:name/backups" element={<ServerBackups />} />
|
|
<Route path="servers/:name/luckperms" element={<ServerLuckPerms />} />
|
|
<Route path="submissions" element={<MySubmissionsPage />} />
|
|
<Route path="account" element={<Account />} />
|
|
|
|
{/* Admin-Side — admin-tier (server & content ops).
|
|
/admin has no landing page; redirect to the first concrete view
|
|
so the section root and any stale bookmarks land somewhere useful. */}
|
|
<Route path="admin" element={<RequireAdmin />}>
|
|
<Route index element={<Navigate to="/admin/images" replace />} />
|
|
<Route path="images" element={<ImageAdmin />} />
|
|
<Route path="builds" element={<ImageBuildPage />} />
|
|
<Route path="submissions" element={<SubmissionsPage />} />
|
|
<Route path="updates" element={<UpdatesPage />} />
|
|
{/* Owner-gated: user management (one level above admin). */}
|
|
<Route element={<RequireOwner />}>
|
|
<Route path="users" element={<UsersPage />} />
|
|
<Route path="users/:id" element={<UserDetailPage />} />
|
|
</Route>
|
|
</Route>
|
|
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
</Route>
|
|
</Route>
|
|
</Routes>
|
|
</BrowserRouter>
|
|
</TierProvider>
|
|
</ThemeProvider>
|
|
);
|
|
}
|