"use client"; import { useCallback, useEffect, useState, type ChangeEvent, type ReactElement, type SyntheticEvent, } from "react"; import { FleetSettingsNav } from "@/components/settings/FleetSettingsNav"; import { SettingsAccessDenied } from "@/components/settings/SettingsAccessDenied"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { deleteFleetOidcConfig, fetchFleetOidcConfig, resetBreakglassAdminPassword, updateFleetOidcConfig, type FleetOidcConfig, } from "@/lib/api/fleet-settings"; import { fetchOnboardingStatus } from "@/lib/api/onboarding"; interface OidcFormState { issuerUrl: string; clientId: string; clientSecret: string; } interface BreakglassFormState { username: string; currentPassword: string; newPassword: string; confirmPassword: string; } const INITIAL_OIDC_FORM: OidcFormState = { issuerUrl: "", clientId: "", clientSecret: "", }; const INITIAL_BREAKGLASS_FORM: BreakglassFormState = { username: "", currentPassword: "", newPassword: "", confirmPassword: "", }; function getErrorMessage(error: unknown, fallback: string): string { if (error instanceof Error && error.message.trim().length > 0) { return error.message; } return fallback; } function isAdminGuardError(error: unknown): boolean { if (!(error instanceof Error)) { return false; } const normalized = error.message.toLowerCase(); return ( normalized.includes("requires system administrator") || normalized.includes("forbidden") || normalized.includes("403") ); } export default function AuthSettingsPage(): ReactElement { const [oidcConfig, setOidcConfig] = useState(null); const [oidcForm, setOidcForm] = useState(INITIAL_OIDC_FORM); const [isLoading, setIsLoading] = useState(true); const [isSavingOidc, setIsSavingOidc] = useState(false); const [isDeletingOidc, setIsDeletingOidc] = useState(false); const [oidcError, setOidcError] = useState(null); const [oidcSuccessMessage, setOidcSuccessMessage] = useState(null); const [showRemoveOidcDialog, setShowRemoveOidcDialog] = useState(false); const [breakglassForm, setBreakglassForm] = useState(INITIAL_BREAKGLASS_FORM); const [breakglassStatus, setBreakglassStatus] = useState<"active" | "inactive">("inactive"); const [isResettingPassword, setIsResettingPassword] = useState(false); const [breakglassError, setBreakglassError] = useState(null); const [breakglassSuccessMessage, setBreakglassSuccessMessage] = useState(null); const [isAccessDenied, setIsAccessDenied] = useState(false); const loadAuthSettings = useCallback(async (): Promise => { setIsLoading(true); try { const [oidcResponse, onboardingStatus] = await Promise.all([ fetchFleetOidcConfig(), fetchOnboardingStatus().catch(() => ({ completed: false })), ]); setOidcConfig(oidcResponse); setOidcForm({ issuerUrl: oidcResponse.issuerUrl ?? "", clientId: oidcResponse.clientId ?? "", clientSecret: "", }); setBreakglassStatus(onboardingStatus.completed ? "active" : "inactive"); setIsAccessDenied(false); setOidcError(null); } catch (loadError: unknown) { if (isAdminGuardError(loadError)) { setIsAccessDenied(true); return; } setOidcError(getErrorMessage(loadError, "Failed to load authentication settings.")); } finally { setIsLoading(false); } }, []); useEffect(() => { void loadAuthSettings(); }, [loadAuthSettings]); async function handleSaveOidc(event: SyntheticEvent): Promise { event.preventDefault(); setOidcError(null); setOidcSuccessMessage(null); const issuerUrl = oidcForm.issuerUrl.trim(); const clientId = oidcForm.clientId.trim(); const clientSecret = oidcForm.clientSecret.trim(); if (issuerUrl.length === 0 || clientId.length === 0 || clientSecret.length === 0) { setOidcError("Issuer URL, client ID, and client secret are required."); return; } try { setIsSavingOidc(true); await updateFleetOidcConfig({ issuerUrl, clientId, clientSecret, }); setOidcSuccessMessage("OIDC configuration updated."); await loadAuthSettings(); } catch (saveError: unknown) { setOidcError(getErrorMessage(saveError, "Failed to update OIDC configuration.")); } finally { setIsSavingOidc(false); } } async function handleRemoveOidc(): Promise { try { setIsDeletingOidc(true); await deleteFleetOidcConfig(); setOidcSuccessMessage("OIDC configuration removed."); setShowRemoveOidcDialog(false); await loadAuthSettings(); } catch (deleteError: unknown) { setOidcError(getErrorMessage(deleteError, "Failed to remove OIDC configuration.")); } finally { setIsDeletingOidc(false); } } async function handleResetBreakglassPassword(event: SyntheticEvent): Promise { event.preventDefault(); setBreakglassError(null); setBreakglassSuccessMessage(null); const username = breakglassForm.username.trim(); const newPassword = breakglassForm.newPassword; const confirmPassword = breakglassForm.confirmPassword; if (username.length === 0) { setBreakglassError("Username is required."); return; } if (newPassword.length < 8) { setBreakglassError("New password must be at least 8 characters."); return; } if (newPassword !== confirmPassword) { setBreakglassError("Password confirmation does not match."); return; } try { setIsResettingPassword(true); await resetBreakglassAdminPassword({ username, newPassword, }); setBreakglassSuccessMessage(`Password reset for "${username}".`); setBreakglassStatus("active"); setBreakglassForm((previous) => ({ ...previous, currentPassword: "", newPassword: "", confirmPassword: "", })); } catch (resetError: unknown) { setBreakglassError(getErrorMessage(resetError, "Failed to reset breakglass password.")); } finally { setIsResettingPassword(false); } } return (

Authentication Settings

Configure OIDC and breakglass admin recovery credentials.

{isLoading ? ( Loading authentication settings... ) : null} {!isLoading && isAccessDenied ? ( ) : null} {!isLoading && !isAccessDenied ? ( <> OIDC Provider Manage your OpenID Connect issuer and OAuth client credentials.

Configured

{oidcConfig?.configured ? "Yes" : "No"}

Issuer URL: {oidcConfig?.issuerUrl ?? "Not configured"}

Client ID: {oidcConfig?.clientId ?? "Not configured"}

Client secret: hidden

void handleSaveOidc(event)} className="space-y-4">
) => { setOidcForm((previous) => ({ ...previous, issuerUrl: event.target.value })); }} placeholder="https://issuer.example.com" disabled={isSavingOidc} required />
) => { setOidcForm((previous) => ({ ...previous, clientId: event.target.value })); }} placeholder="mosaic-web" disabled={isSavingOidc} required />
) => { setOidcForm((previous) => ({ ...previous, clientSecret: event.target.value, })); }} placeholder="Enter new secret" autoComplete="new-password" disabled={isSavingOidc} required />

The secret is encrypted on save and never returned to the UI.

{oidcError ? (

{oidcError}

) : null} {oidcSuccessMessage ? (

{oidcSuccessMessage}

) : null}
Breakglass Admin Reset breakglass credentials for emergency local access.

Status

{breakglassStatus}
void handleResetBreakglassPassword(event)} className="space-y-4" >
) => { setBreakglassForm((previous) => ({ ...previous, username: event.target.value, })); }} placeholder="admin" disabled={isResettingPassword} required />
) => { setBreakglassForm((previous) => ({ ...previous, currentPassword: event.target.value, })); }} placeholder="Optional operator confirmation" autoComplete="current-password" disabled={isResettingPassword} />
) => { setBreakglassForm((previous) => ({ ...previous, newPassword: event.target.value, })); }} placeholder="At least 8 characters" autoComplete="new-password" disabled={isResettingPassword} required />
) => { setBreakglassForm((previous) => ({ ...previous, confirmPassword: event.target.value, })); }} placeholder="Re-enter password" autoComplete="new-password" disabled={isResettingPassword} required />
{breakglassError ? (

{breakglassError}

) : null} {breakglassSuccessMessage ? (

{breakglassSuccessMessage}

) : null}
) : null} { if (!open && !isDeletingOidc) { setShowRemoveOidcDialog(false); } }} > Remove OIDC Configuration This will remove issuer URL, client ID, and client secret from system configuration. Cancel {isDeletingOidc ? "Removing..." : "Remove OIDC"}
); }