import { fetchWithError } from "@keycloak/keycloak-admin-client"; import type AuthenticationFlowRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationFlowRepresentation"; import RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import { AlertVariant, Button, ButtonVariant, Label, PageSection, Tab, TabTitleText, ToolbarItem, } from "@patternfly/react-core"; import { sortBy } from "lodash-es"; import { useState } from "react"; import { Trans, useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import { adminClient } from "../admin-client"; import { useAlerts } from "../components/alert/Alerts"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { RoutableTabs, useRoutableTab, } from "../components/routable-tabs/RoutableTabs"; import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { useRealm } from "../context/realm-context/RealmContext"; import helpUrls from "../help-urls"; import { addTrailingSlash } from "../util"; import { getAuthorizationHeaders } from "../utils/getAuthorizationHeaders"; import { useFetch } from "../utils/useFetch"; import useLocaleSort, { mapByKey } from "../utils/useLocaleSort"; import useToggle from "../utils/useToggle"; import { BindFlowDialog } from "./BindFlowDialog"; import { DuplicateFlowModal } from "./DuplicateFlowModal"; import { RequiredActions } from "./RequiredActions"; import { UsedBy } from "./components/UsedBy"; import { Policies } from "./policies/Policies"; import { AuthenticationTab, toAuthentication } from "./routes/Authentication"; import { toCreateFlow } from "./routes/CreateFlow"; import { toFlow } from "./routes/Flow"; type UsedBy = "SPECIFIC_CLIENTS" | "SPECIFIC_PROVIDERS" | "DEFAULT"; export type AuthenticationType = AuthenticationFlowRepresentation & { usedBy?: { type?: UsedBy; values: string[] }; realm: RealmRepresentation; }; export const REALM_FLOWS = new Map([ ["browserFlow", "browser"], ["registrationFlow", "registration"], ["directGrantFlow", "direct grant"], ["resetCredentialsFlow", "reset credentials"], ["clientAuthenticationFlow", "clients"], ["dockerAuthenticationFlow", "docker auth"], ["firstBrokerLoginFlow", "firstBrokerLogin"], ]); const AliasRenderer = ({ id, alias, usedBy, builtIn }: AuthenticationType) => { const { t } = useTranslation(); const { realm } = useRealm(); return ( <> {alias} {" "} {builtIn && } ); }; export default function AuthenticationSection() { const { t } = useTranslation(); const { realm: realmName } = useRealm(); const [key, setKey] = useState(0); const refresh = () => { setRealm(undefined); setKey(key + 1); }; const { addAlert, addError } = useAlerts(); const localeSort = useLocaleSort(); const [selectedFlow, setSelectedFlow] = useState(); const [open, toggleOpen] = useToggle(); const [bindFlowOpen, toggleBindFlow] = useToggle(); const [realm, setRealm] = useState(); useFetch(() => adminClient.realms.findOne({ realm: realmName }), setRealm, [ key, ]); const loader = async () => { const flowsRequest = await fetchWithError( `${addTrailingSlash( adminClient.baseUrl, )}admin/realms/${realmName}/ui-ext/authentication-management/flows`, { method: "GET", headers: getAuthorizationHeaders(await adminClient.getAccessToken()), }, ); const flows = await flowsRequest.json(); if (!flows) { return []; } return sortBy( localeSort(flows, mapByKey("alias")), (flow) => flow.usedBy?.type, ); }; const useTab = (tab: AuthenticationTab) => useRoutableTab(toAuthentication({ realm: realmName, tab })); const flowsTab = useTab("flows"); const requiredActionsTab = useTab("required-actions"); const policiesTab = useTab("policies"); const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: "deleteConfirmFlow", children: ( {" "} {{ flow: selectedFlow ? selectedFlow.alias : "" }}. ), continueButtonLabel: "delete", continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { try { await adminClient.authenticationManagement.deleteFlow({ flowId: selectedFlow!.id!, }); refresh(); addAlert(t("deleteFlowSuccess"), AlertVariant.success); } catch (error) { addError("deleteFlowError", error); } }, }); if (!realm) return ; return ( <> {open && ( { refresh(); toggleOpen(); }} /> )} {bindFlowOpen && ( { toggleBindFlow(); refresh(); }} flowAlias={selectedFlow?.alias!} /> )} {t("flows")}} {...flowsTab} > } actionResolver={({ data }) => [ { title: t("duplicate"), onClick: () => { toggleOpen(); setSelectedFlow(data); }, }, ...(data.usedBy?.type !== "DEFAULT" ? [ { title: t("bindFlow"), onClick: () => { toggleBindFlow(); setSelectedFlow(data); }, }, ] : []), ...(!data.builtIn && !data.usedBy ? [ { title: t("delete"), onClick: () => { setSelectedFlow(data); toggleDeleteDialog(); }, }, ] : []), ]} columns={[ { name: "alias", displayKey: "flowName", cellRenderer: (row) => , }, { name: "usedBy", displayKey: "usedBy", cellRenderer: (row) => ( ), }, { name: "description", displayKey: "description", }, ]} emptyState={ } /> {t("requiredActions")}} {...requiredActionsTab} > {t("policies")}} {...policiesTab} > ); }