Attaches X-CSRF-Token header on POST/PUT/PATCH/DELETE.
Both interceptors are wired in frontend/src/main.ts via
provideHttpClient(withInterceptors([csrfInterceptor, authInterceptor]))
(notably CSRF runs first so the token is attached before the auth header).
BootstrapService calls GET /api/v1/bootstrap (public, with
credentials: 'include').
The payload sets initialized (true iff any admin user exists),
pageTitle, logo, welcomeMarkdown, the active theme, and the
default challenge IP.
Theme tokens are written to CSS custom properties on
document.documentElement (--color-primary, --font-family,
--radius-*, etc.) consumed by frontend/src/styles.css.
The authGuard reads BootstrapService.initialized() and either
allows navigation or redirects to /bootstrap.
Home shell flow
After successful auth, HomeComponent renders a thin shell layout:
The header (shell-header) shows the page title and signed-in
identity.
When shouldShowAdminNav({isAuthenticated, role}) returns true
(see frontend/src/app/features/home/home.shell.ts) the admin nav
link (data-testid="nav-admin") is rendered.
Child routes (e.g. /admin → AdminUsersComponent) render into the
shell's <router-outlet>.
AdminUsersComponent calls AdminService.listUsers() on init;
loading, error, and the rendered list are exposed as Angular signals.