import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { authBootstrapAvailable, authRuntimeConfig } from '../../api/client'; import { useAuthMe } from '../../hooks/useAuthMe'; import PageHeader from '../../components/PageHeader'; import { STALE_TIME } from '../../api/queryConstants'; import { getTimestampPref, setTimestampPref, type TimestampMode } from '../../api/timestampPref'; // ============================================================================= // Bundle 1 Phase 10 — AuthSettingsPage (stub). // // Surfaces: // // - The current actor's identity, roles, effective permissions // (from /v1/auth/me — already cached by useAuthMe). // - Bootstrap-endpoint availability so a fresh-deploy operator // knows whether they can mint the first admin via curl. Shows // "available" pre-admin, "closed" after the first admin lands. // // Bundle 2 will extend this page with OIDC provider config + session // management. Bundle 1 ships only the stub so the route exists and // the navigation entry is wired. // ============================================================================= export default function AuthSettingsPage() { const me = useAuthMe(); const bootstrapQuery = useQuery({ queryKey: ['auth', 'bootstrap', 'available'], queryFn: authBootstrapAvailable, staleTime: STALE_TIME.REFERENCE, // slow-changing auth-runtime data retry: 0, }); // Audit 2026-05-10 MED-12 — Auth runtime config panel. Gated // auth.role.assign server-side; query failure (403) is silently // swallowed (panel hidden) for non-admin viewers. const runtimeQuery = useQuery({ queryKey: ['auth', 'runtime-config'], queryFn: authRuntimeConfig, staleTime: STALE_TIME.REFERENCE, // slow-changing auth-runtime data retry: 0, }); return (
Current identity
From /api/v1/auth/me
{me.isLoading &&
Loading…
} {me.error &&
{me.error.message}
} {me.data && ( <>
Actor:{' '} {me.data.actor_id}{' '} ({me.data.actor_type})
Tenant:{' '} {me.data.tenant_id}
Admin:{' '} {me.data.admin ? 'yes' : 'no'}
Roles:{' '} {me.data.roles.join(', ') || '(none)'}
Effective permissions:{' '} {me.data.effective_permissions.length}
{me.data.effective_permissions.length > 0 && (
Show permission list
    {me.data.effective_permissions.map((p, i) => (
  • {p.permission} @ {p.scope_type} {p.scope_id ? ` (${p.scope_id})` : ''}
  • ))}
)} )}
Bootstrap endpoint
Bundle 1 Phase 6 — mints the first admin API key when no admin exists yet.
{bootstrapQuery.isLoading &&
Probing…
} {bootstrapQuery.error && (
Could not reach /v1/auth/bootstrap: {bootstrapQuery.error.message}
)} {bootstrapQuery.data && ( <>
Status:{' '} {bootstrapQuery.data.available ? 'OPEN — first-admin path callable' : 'closed'}
{bootstrapQuery.data.available && (
Run: curl -X POST $URL/api/v1/auth/bootstrap -d '{'{'}"token":"…","actor_name":"first-admin"{'}'}' to mint the first admin key.
)} {!bootstrapQuery.data.available && (
Either CERTCTL_BOOTSTRAP_TOKEN is unset, an admin already exists, or the strategy was already consumed.
)} )}
{/* Audit 2026-05-10 MED-12 — Auth runtime config panel. */} {runtimeQuery.data && (
Auth runtime config
Deployed CERTCTL_* values gated `auth.role.assign`. Sensitive values (tokens, secrets, CIDRs) surface as set/unset or counts only — never raw bytes.
{Object.entries(runtimeQuery.data) .sort(([a], [b]) => a.localeCompare(b)) .map(([k, v]) => ( ))}
Setting Value
{k} {v || (empty)}
)} {/* Phase 6 closure (I18N-H3): operator timestamp-display preference. */}
); } // ────────────────────────────────────────────────────────────────── // Timestamp-display preference (Phase 6 I18N-H3) // ────────────────────────────────────────────────────────────────── function TimestampPreferenceCard() { const [mode, setMode] = useState(() => getTimestampPref().mode); const [customTz, setCustomTz] = useState(() => getTimestampPref().customTz); function persist(next: { mode: TimestampMode; customTz: string }) { setMode(next.mode); setCustomTz(next.customTz); setTimestampPref(next); } return (
Timestamp display
Default UTC matches the server audit log byte-for-byte. Pick Local for browser time; Custom for a specific IANA timezone (e.g. America/New_York).
{(['utc', 'local', 'custom'] as const).map((m) => ( ))}
{mode === 'custom' && (
persist({ mode, customTz: e.target.value })} placeholder="America/New_York" spellCheck={false} className="w-full px-2 py-1 border border-surface-border rounded bg-page text-ink font-mono text-xs" data-testid="timestamp-custom-tz-input" />
)}
); }