import { FC, useMemo, useRef } from 'react'; import API_BASE_URL from '../../lib/consts/API_BASE_URL'; import api from '../../lib/api'; import ConfirmDialog from '../ConfirmDialog'; import Divider from '../Divider'; import FlexBox from '../FlexBox'; import handleAPIError from '../../lib/handleAPIError'; import Link from '../Link'; import List from '../List'; import MessageBox, { Message } from '../MessageBox'; import { ExpandablePanel } from '../Panels'; import periodicFetch from '../../lib/fetchers/periodicFetch'; import { BodyText } from '../Text'; import useChecklist from '../../hooks/useChecklist'; import useProtectedState from '../../hooks/useProtectedState'; const ManageChangedSSHKeysForm: FC = ({ mitmExternalHref = 'https://en.wikipedia.org/wiki/Man-in-the-middle_attack', refreshInterval = 60000, }) => { const confirmDialogRef = useRef({}); const [apiMessage, setAPIMessage] = useProtectedState( undefined, ); const [changedSSHKeys, setChangedSSHKeys] = useProtectedState( {}, ); const [confirmDialogProps, setConfirmDialogProps] = useProtectedState({ actionProceedText: '', content: '', titleText: '', }); const { checks, getCheck, hasAllChecks, hasChecks, setAllChecks, setCheck } = useChecklist({ list: changedSSHKeys }); const apiMessageElement = useMemo( () => apiMessage && , [apiMessage], ); const isAllowCheckAll = useMemo( () => Object.keys(changedSSHKeys).length > 1, [changedSSHKeys], ); const { isLoading } = periodicFetch( `${API_BASE_URL}/ssh-key/conflict`, { onError: (error) => { setAPIMessage({ children: `Failed to fetch SSH key conflicts. Error: ${error}`, type: 'error', }); }, onSuccess: (data) => { setChangedSSHKeys((previous) => Object.values(data).reduce((nyu, stateList) => { Object.values(stateList).forEach( ({ hostName, hostUUID, ipAddress, stateUUID }) => { nyu[stateUUID] = { ...previous[stateUUID], hostName, hostUUID, ipAddress, }; }, ); return nyu; }, {}), ); }, refreshInterval, }, ); return ( <> The identity of the following targets have unexpectedly changed. If you haven't rebuilt the listed targets, then you could be experiencing a{' '} "Man In The Middle" {' '} attack. Please verify the targets have changed for a known reason before proceeding to remove the broken keys. :not(:last-child)': { display: { xs: 'none', sm: 'flex' }, }, '& > :last-child': { display: { xs: 'initial', sm: 'none' }, marginLeft: 0, }, }} > Host name IP address } allowCheckAll={isAllowCheckAll} allowCheckItem allowDelete allowEdit={false} disableDelete={!hasChecks} edit getListCheckboxProps={() => ({ checked: hasAllChecks, })} listEmpty={ No conflicting keys found. } listItems={changedSSHKeys} onAllCheckboxChange={(event, checked) => { setAllChecks(checked); }} onDelete={() => { const deleteRequestBody = checks.reduce<{ [hostUUID: string]: string[]; }>((previous, stateUUID) => { const checked = getCheck(stateUUID); if (!checked) return previous; const { hostUUID } = changedSSHKeys[stateUUID]; if (!previous[hostUUID]) { previous[hostUUID] = []; } previous[hostUUID].push(stateUUID); return previous; }, {}); setConfirmDialogProps({ actionProceedText: 'Delete', content: `Resolve ${checks.length} SSH key conflicts. Please make sure the identity change(s) are expected to avoid MITM attacks.`, onProceedAppend: () => { api .delete('/ssh-key/conflict', { data: deleteRequestBody }) .catch((error) => { const emsg = handleAPIError(error); emsg.children = `Failed to delete selected SSH key conflicts. ${emsg.children}`; setAPIMessage(emsg); }); }, proceedColour: 'red', titleText: `Delete ${checks.length} conflicting SSH keys?`, }); confirmDialogRef.current.setOpen?.call(null, true); }} onItemCheckboxChange={(key, event, checked) => { setCheck(key, checked); }} renderListItem={(hostUUID, { hostName, ipAddress }) => ( *': { flexBasis: '50%' } }} xs="column" > {hostName} {ipAddress} )} renderListItemCheckboxState={(key) => getCheck(key)} /> {apiMessageElement} ); }; export default ManageChangedSSHKeysForm;