import React, { FunctionComponent, useMemo, useState } from "react"; import { AlertVariant, Button, ButtonVariant, Dropdown, DropdownItem, DropdownPosition, Form, FormGroup, KebabToggle, Modal, ModalVariant, Switch, Text, TextVariants, ValidatedOptions, } from "@patternfly/react-core"; import { Table, TableBody, TableComposable, TableHeader, TableVariant, Tbody, Td, Th, Thead, Tr, } from "@patternfly/react-table"; import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation"; import { useTranslation } from "react-i18next"; import { useAlerts } from "../components/alert/Alerts"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { useAdminClient, useFetch } from "../context/auth/AdminClient"; import { useWhoAmI } from "../context/whoami/WhoAmI"; import { Controller, useForm, useWatch } from "react-hook-form"; import { PasswordInput } from "../components/password-input/PasswordInput"; import { HelpItem } from "../components/help-enabler/HelpItem"; import "./user-section.css"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import type CredentialRepresentation from "@keycloak/keycloak-admin-client/lib/defs/credentialRepresentation"; type UserCredentialsProps = { user: UserRepresentation; }; type CredentialsForm = { password: string; passwordConfirmation: string; temporaryPassword: boolean; }; const defaultValues: CredentialsForm = { password: "", passwordConfirmation: "", temporaryPassword: true, }; type DisplayDialogProps = { titleKey: string; onClose: () => void; }; const DisplayDialog: FunctionComponent = ({ titleKey, onClose, children, }) => { const { t } = useTranslation("users"); return ( {children} ); }; export const UserCredentials = ({ user }: UserCredentialsProps) => { const { t } = useTranslation("users"); const { whoAmI } = useWhoAmI(); const { addAlert, addError } = useAlerts(); const [key, setKey] = useState(0); const refresh = () => setKey(key + 1); const [open, setOpen] = useState(false); const [openSaveConfirm, setOpenSaveConfirm] = useState(false); const [kebabOpen, setKebabOpen] = useState(false); const adminClient = useAdminClient(); const form = useForm({ defaultValues }); const { control, errors, handleSubmit, register } = form; const [credentials, setCredentials] = useState(); const [userCredentials, setUserCredentials] = useState< CredentialRepresentation[] >([]); const [selectedCredential, setSelectedCredential] = useState({}); const [isResetPassword, setIsResetPassword] = useState(false); const [showData, setShowData] = useState(false); useFetch( () => adminClient.users.getCredentials({ id: user.id! }), (credentials) => { setUserCredentials(credentials); }, [key] ); const passwordWatcher = useWatch({ control, name: "password", }); const passwordConfirmationWatcher = useWatch< CredentialsForm["passwordConfirmation"] >({ control, name: "passwordConfirmation", }); const isNotDisabled = passwordWatcher !== "" && passwordConfirmationWatcher !== ""; const toggleModal = () => { setOpen(!open); }; const toggleConfirmSaveModal = () => { setOpenSaveConfirm(!openSaveConfirm); }; const saveUserPassword = async () => { if (!credentials) { return; } const passwordsMatch = credentials.password === credentials.passwordConfirmation; if (!passwordsMatch) { addAlert( isResetPassword ? t("resetPasswordNotMatchError") : t("savePasswordNotMatchError"), AlertVariant.danger ); } else { try { await adminClient.users.resetPassword({ id: user.id!, credential: { temporary: credentials.temporaryPassword, type: "password", value: credentials.password, }, }); refresh(); addAlert( isResetPassword ? t("resetCredentialsSuccess") : t("savePasswordSuccess"), AlertVariant.success ); setIsResetPassword(false); setOpenSaveConfirm(false); } catch (error) { addError( isResetPassword ? t("resetPasswordError") : t("savePasswordError"), error ); } } }; const resetPassword = () => { setIsResetPassword(true); setOpen(true); }; const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: t("deleteCredentialsConfirmTitle"), messageKey: t("deleteCredentialsConfirm"), continueButtonLabel: t("common:delete"), continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { try { await adminClient.users.deleteCredential({ id: user.id!, credentialId: selectedCredential.id!, }); addAlert(t("deleteCredentialsSuccess"), AlertVariant.success); setKey((key) => key + 1); } catch (error) { addError(t("deleteCredentialsError"), error); } }, }); const rows = useMemo(() => { if (!selectedCredential.credentialData) { return []; } const credentialData = JSON.parse(selectedCredential.credentialData); const locale = whoAmI.getLocale(); return Object.entries(credentialData) .sort(([a], [b]) => a.localeCompare(b, locale)) .map<[string, string]>(([key, value]) => { if (typeof value === "string") { return [key, value]; } return [key, JSON.stringify(value)]; }); }, [selectedCredential.credentialData]); return ( <> {open && ( { setIsResetPassword(false); setOpen(false); }} actions={[ , , ]} >
} fieldId="kc-temporaryPassword" > {" "} ( onChange(value)} isChecked={value} label={t("common:on")} labelOff={t("common:off")} /> )} >
)} {openSaveConfirm && ( setOpenSaveConfirm(false)} actions={[ , , ]} > {isResetPassword ? `${t("resetPasswordConfirmText")} ${user.username} ${t( "questionMark" )}` : `${t("setPasswordConfirmText")} ${user.username} ${t( "questionMark" )}`} )} {showData && Object.keys(selectedCredential).length !== 0 && ( { setShowData(false); setSelectedCredential({}); }} >
)} {userCredentials.length !== 0 ? ( {t("type")} {t("userLabel")} {t("data")} {userCredentials.map((credential) => ( <> {credential.type?.charAt(0).toUpperCase()! + credential.type?.slice(1)} My Password setKebabOpen(open)} /> } isOpen={kebabOpen} onSelect={() => setSelectedCredential(credential)} dropdownItems={[ { toggleDeleteDialog(); setKebabOpen(false); }} > {t("deleteBtn")} , ]} /> ))} ) : ( )} ); };