mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-11-09 15:52:14 +00:00
* feat(ssh): restore FIDO2 security key support (#2308) Consolidate the restored feature and its review fixes before rebasing onto current main. Original PR head: 5d0a0c6c9a550364d3ec5c2331395cedfc9ea7ae. * fix(ssh): verify and repair FIDO authentication with simulated hardware Preserve complete OpenSSH SK signature payloads, classify combined PIN/presence prompts correctly, expand IdentityFile tokens, retain certificate selectors, and route shared-agent signing prompts to their originating window. Dismiss prompts when notification helpers exit. Add a software SK provider and opt-in real OpenSSH integration coverage for both algorithms, certificates, SFTP, forwarding, and concurrent windows. * refactor(ui): refine security key forms and authentication motion Use shared Select and Switch controls, concise localized copy and compact token-based dialogs. Add scoped enter/exit, field transition and presence animations with reduced-motion support. Preserve raw prompt details and visible PIN errors. * fix(ssh): align security key prompts with hardware presence and verify Docker logins * fix(ui): keep security key dialog above its remounted overlay * fix: address Codex review on PR #2823 * feat(ssh): guide security key recovery and explicit authentication fallback * test(ssh): exercise security key authentication on native Windows * test(ssh): verify Windows fixture signatures and explicit fallback * test(ssh): configure Windows OpenSSH toolchain and document coverage --------- Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
112 lines
4.4 KiB
TypeScript
112 lines
4.4 KiB
TypeScript
import { useLayoutEffect, useMemo, useSyncExternalStore } from 'react';
|
|
|
|
import { getHostSearchMatch } from '../../../lib/searchMatcher';
|
|
import type { Host } from '../../../types';
|
|
import { useEditorTabChromeList } from '../../state/editorTabStore';
|
|
import { useVaultSnapshot } from '../../state/vaultSnapshotStore';
|
|
import { getAppHandlers, subscribeAppHandlers } from '../appHandlersBridge';
|
|
import {
|
|
publishAppShellDomainSlice,
|
|
publishAppShellOverlays,
|
|
} from '../appShellPropsStore';
|
|
import { useAppLocalUiStore } from '../appLocalUiStore';
|
|
|
|
const EMPTY_HOST_RESULTS: Host[] = [];
|
|
|
|
/**
|
|
* Dialogs island: local dialog/queue state from `appLocalUiStore`, plus a
|
|
* selective vault hosts subscription for quick-search results when open.
|
|
* Assembles dialogs + overlays field-by-field — never spreads a prepared bag.
|
|
*/
|
|
export function DialogsHost() {
|
|
const local = useAppLocalUiStore();
|
|
const vault = useVaultSnapshot();
|
|
const editorTabs = useEditorTabChromeList();
|
|
const handlers = useSyncExternalStore(
|
|
subscribeAppHandlers,
|
|
getAppHandlers,
|
|
getAppHandlers,
|
|
);
|
|
|
|
const quickResults = useMemo(() => {
|
|
if (!local.isQuickSwitcherOpen) return EMPTY_HOST_RESULTS;
|
|
const term = local.quickSearch.trim();
|
|
if (!term) return vault.hosts as Host[];
|
|
return (vault.hosts as Host[])
|
|
.map((host) => ({ host, match: getHostSearchMatch(term, host) }))
|
|
.filter((entry) => entry.match.matched)
|
|
.sort((left, right) => {
|
|
if (left.match.score !== right.match.score) {
|
|
return right.match.score - left.match.score;
|
|
}
|
|
return left.host.label.localeCompare(right.host.label);
|
|
})
|
|
.map((entry) => entry.host);
|
|
}, [local.isQuickSwitcherOpen, local.quickSearch, vault.hosts]);
|
|
|
|
const dialogsDomain = useMemo(() => {
|
|
if (!handlers) return null;
|
|
return {
|
|
addToWorkspaceDialog: local.addToWorkspaceDialog,
|
|
clearAndRemoveSource: handlers.clearAndRemoveSource,
|
|
clearAndRemoveSources: handlers.clearAndRemoveSources,
|
|
editorTabs,
|
|
emptyVaultConflict: local.emptyVaultConflict,
|
|
handleHostConnectWithProtocolCheck: handlers.handleHostConnectWithProtocolCheck,
|
|
handleKeyboardInteractiveCancel: handlers.handleKeyboardInteractiveCancel,
|
|
handleKeyboardInteractiveSubmit: handlers.handleKeyboardInteractiveSubmit,
|
|
handlePassphraseCancel: handlers.handlePassphraseCancel,
|
|
handlePassphraseSkip: handlers.handlePassphraseSkip,
|
|
handlePassphraseSubmit: handlers.handlePassphraseSubmit,
|
|
handleFidoPromptCancel: handlers.handleFidoPromptCancel,
|
|
handleFidoPromptSubmit: handlers.handleFidoPromptSubmit,
|
|
handleProtocolSelect: handlers.handleProtocolSelect,
|
|
handleRequestCloseEditorTabRef: handlers.handleRequestCloseEditorTabRef,
|
|
isCreateWorkspaceOpen: local.isCreateWorkspaceOpen,
|
|
isQuickSwitcherOpen: local.isQuickSwitcherOpen,
|
|
keyboardInteractiveQueue: local.keyboardInteractiveQueue,
|
|
passphraseQueue: local.passphraseQueue,
|
|
fidoPromptQueue: local.fidoPromptQueue,
|
|
protocolSelectHost: local.protocolSelectHost,
|
|
quickResults,
|
|
quickSearch: local.quickSearch,
|
|
resolveEmptyVaultConflict: handlers.resolveEmptyVaultConflict,
|
|
setAddToWorkspaceDialog: handlers.setAddToWorkspaceDialog,
|
|
setIsCreateWorkspaceOpen: handlers.setIsCreateWorkspaceOpen,
|
|
setIsQuickSwitcherOpen: handlers.setIsQuickSwitcherOpen,
|
|
setProtocolSelectHost: handlers.setProtocolSelectHost,
|
|
setQuickSearch: handlers.setQuickSearch,
|
|
};
|
|
}, [
|
|
editorTabs,
|
|
handlers,
|
|
local.addToWorkspaceDialog,
|
|
local.emptyVaultConflict,
|
|
local.isCreateWorkspaceOpen,
|
|
local.isQuickSwitcherOpen,
|
|
local.keyboardInteractiveQueue,
|
|
local.passphraseQueue,
|
|
local.fidoPromptQueue,
|
|
local.protocolSelectHost,
|
|
local.quickSearch,
|
|
quickResults,
|
|
]);
|
|
|
|
const overlays = useMemo(() => {
|
|
if (!handlers) return null;
|
|
return {
|
|
onAddKnownHost: handlers.handleAddKnownHost as (knownHost: never) => void,
|
|
deleteHostConfirm: local.deleteHostConfirm,
|
|
onCancelDeleteHost: handlers.handleCancelDeleteHost as () => void,
|
|
onConfirmDeleteHost: handlers.handleConfirmDeleteHost as () => void,
|
|
};
|
|
}, [handlers, local.deleteHostConfirm]);
|
|
|
|
useLayoutEffect(() => {
|
|
if (dialogsDomain) publishAppShellDomainSlice('dialogs', dialogsDomain);
|
|
if (overlays) publishAppShellOverlays(overlays as never);
|
|
}, [dialogsDomain, overlays]);
|
|
|
|
return null;
|
|
}
|