Replaces the dev-bypass-only AuthProvider with a real backend-mediated
OIDC client. Backend ships the OIDC plumbing in
Dialectic.Backend@2463129; this SPA drives it.
Auth flow:
1. On mount: GET /api/auth/oidc/status (is OIDC configured?) + GET
/api/auth/me (who am I — reads the dialectic_session HttpOnly
cookie if present).
2. Anon + click 'login' → window.location = /api/auth/oidc/start
(backend redirects to IdP authorize URL with PKCE + state).
3. IdP returns to backend /api/auth/oidc/callback → backend redirects
here to /oidc/callback#oidc_ticket=<base64url>.
4. OidcCallbackPage POSTs the ticket to /api/auth/oidc/exchange →
backend Set-Cookie's the session JWT → refresh() AuthProvider →
navigate to /.
5. Header shows user.name + 'logout' button. Logout → POST
/api/auth/logout (cookie cleared by backend) → user state cleared.
UI changes:
- Header gets a Login button (acid primary style) when anon + OIDC
enabled; logged-in pill (name + logout) when authenticated.
- 'oidc not configured' message when backend reports
/api/auth/oidc/status enabled=false (operator needs to run
dialectic-cli config oidc first).
- Removed /agents/:id route + AgentActivity page + nav link entirely
(per user request; admin endpoint stays on backend for curl).
- Removed Link to /agents/* from TopicDetail + Verdict pages.
api.ts:
- Adds credentials:'same-origin' on every fetch so the session cookie
rides along.
- Drops the admin: opt + x-dialectic-admin-key header path (no more
AgentActivity consumer).
- Keeps dev-bypass header support gated on VITE_OIDC_DEV_BYPASS for
dev convenience — backend's OIDC_ONLY=true env disables it server-
side anyway.
types.ts: AgentSummary type removed.
Build delta: 178KB → 179KB / gzip 57.12 → 57.70 (oidc-client overhead
is just the AuthProvider polling code; no oidc-client-ts lib needed
since the backend drives the redirect flow).
63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
import { NavLink, Route, Routes } from 'react-router-dom';
|
|
import { useAuth } from './auth';
|
|
import { TopicListPage } from './pages/TopicList';
|
|
import { TopicDetailPage } from './pages/TopicDetail';
|
|
import { VerdictPage } from './pages/Verdict';
|
|
import { NotFoundPage } from './pages/NotFound';
|
|
import { OidcCallbackPage } from './pages/OidcCallback';
|
|
import './styles/app.css';
|
|
|
|
export function App() {
|
|
const { user, loading, oidcEnabled, login, logout } = useAuth();
|
|
return (
|
|
<div className="app">
|
|
<header className="app-header">
|
|
<div className="app-header-inner">
|
|
<a href="/" className="app-brand brand-d">
|
|
dialectic
|
|
</a>
|
|
<nav className="app-nav">
|
|
<NavLink to="/" end>
|
|
topics
|
|
</NavLink>
|
|
</nav>
|
|
<div className="app-user">
|
|
{loading ? (
|
|
<span className="eyebrow">loading…</span>
|
|
) : user ? (
|
|
<span className="row" style={{ gap: 12 }}>
|
|
<span className="eyebrow" title={user.email}>
|
|
{user.name}
|
|
</span>
|
|
<button onClick={logout} className="auth-btn">
|
|
logout
|
|
</button>
|
|
</span>
|
|
) : oidcEnabled ? (
|
|
<button onClick={login} className="auth-btn auth-btn-primary">
|
|
login
|
|
</button>
|
|
) : (
|
|
<span className="eyebrow" title="OIDC not configured — set up via dialectic-cli">
|
|
anonymous · oidc not configured
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</header>
|
|
<main className="app-main">
|
|
<Routes>
|
|
<Route path="/" element={<TopicListPage />} />
|
|
<Route path="/topics/:id" element={<TopicDetailPage />} />
|
|
<Route path="/topics/:id/verdict" element={<VerdictPage />} />
|
|
<Route path="/oidc/callback" element={<OidcCallbackPage />} />
|
|
<Route path="*" element={<NotFoundPage />} />
|
|
</Routes>
|
|
</main>
|
|
<footer className="app-footer">
|
|
<span className="eyebrow">hangman lab · dialectic v2</span>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|