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 useProtect from '../../hooks/useProtect'; import useProtectedState from '../../hooks/useProtectedState'; const ManageChangedSSHKeysForm: FC = ({ mitmExternalHref = 'https://en.wikipedia.org/wiki/Man-in-the-middle_attack', refreshInterval = 60000, }) => { const { protect } = useProtect(); const confirmDialogRef = useRef({}); const listRef = useRef({}); const [apiMessage, setAPIMessage] = useProtectedState( undefined, protect, ); const [changedSSHKeys, setChangedSSHKeys] = useProtectedState( {}, protect, ); const [confirmDialogProps, setConfirmDialogProps] = useProtectedState( { actionProceedText: '', content: '', titleText: '', }, protect, ); 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} edit listEmpty={ No conflicting keys found. } listItems={changedSSHKeys} onAllCheckboxChange={(event, isChecked) => { Object.keys(changedSSHKeys).forEach((key) => { changedSSHKeys[key].isChecked = isChecked; }); setChangedSSHKeys((previous) => ({ ...previous })); }} onDelete={() => { let deleteCount = 0; const deleteRequestBody = Object.entries(changedSSHKeys).reduce<{ [hostUUID: string]: string[]; }>((previous, [stateUUID, { hostUUID, isChecked }]) => { if (isChecked) { if (!previous[hostUUID]) { previous[hostUUID] = []; } previous[hostUUID].push(stateUUID); deleteCount += 1; } return previous; }, {}); setConfirmDialogProps({ actionProceedText: 'Delete', content: `Resolve ${deleteCount} 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 ${deleteCount} conflicting SSH keys?`, }); confirmDialogRef.current.setOpen?.call(null, true); }} onItemCheckboxChange={(key, event, isChecked) => { changedSSHKeys[key].isChecked = isChecked; listRef.current.setCheckAll?.call( null, Object.values(changedSSHKeys).every( ({ isChecked: isItemChecked }) => isItemChecked, ), ); setChangedSSHKeys((previous) => ({ ...previous })); }} renderListItem={(hostUUID, { hostName, ipAddress }) => ( *': { flexBasis: '50%' } }} xs="column" > {hostName} {ipAddress} )} renderListItemCheckboxState={(key, { isChecked }) => isChecked === true } ref={listRef} /> {apiMessageElement} ); }; export default ManageChangedSSHKeysForm;