Files
orca/src/preload/api/ssh-api.ts
T
93d8b1f042 fix(ssh): complete keyboard-interactive MFA prompt handling (#15588)
Honor SSH keyboard-interactive prompt echo and empty responses, reuse login
passwords without replaying rejected values, and stop cancelled or stale
credential requests from continuing authentication or restoring the cache.

Original implementation: Junho Kim (#8750).
Port and follow-up work: Allen (#15588).

Verified with 2,773 SSH/credential tests, full typecheck, changed-code quality,
a production Electron build, real-socket MFA fixtures, and rendered UI checks.

Fixes #8622

Co-authored-by: Junho Kim <arkimjh@illinois.edu>
Co-authored-by: microdaery <microdaery@gapp.nthu.edu.tw>
2026-09-27 01:12:29 -07:00

80 lines
3.2 KiB
TypeScript

import type {
EnrichedDetectedPort,
PortForwardEntry,
SshConfigHostListArgs,
SshConfigHostListResult,
SshConfigHostResolution,
SshConfigImportResult,
SshConnectionState,
SshTarget,
SshTargetAddResult,
SshTargetCreateInput,
SshTargetUpdateInput,
SshTerminateSessionsResult
} from '../../shared/ssh-types'
import type { FilesystemPathFlavor } from '../../shared/filesystem-entry-types'
export type SshApi = {
listTargets: () => Promise<SshTarget[]>
// Removed-target id → last known label, for a friendly host name on workspaces still pinned to a removed target.
listRemovedTargetLabels: () => Promise<Record<string, string>>
addTarget: (args: { target: SshTargetCreateInput }) => Promise<SshTargetAddResult>
updateTarget: (args: { id: string; updates: SshTargetUpdateInput }) => Promise<SshTarget>
removeTarget: (args: { id: string }) => Promise<void>
importConfig: (args?: { reAdopt?: boolean }) => Promise<SshConfigImportResult>
listConfigHosts: (args?: SshConfigHostListArgs) => Promise<SshConfigHostListResult>
resolveConfigHost: (args: { alias: string }) => Promise<SshConfigHostResolution | null>
connect: (args: { targetId: string }) => Promise<SshConnectionState | null>
disconnect: (args: { targetId: string }) => Promise<void>
terminateSessions: (args: { targetId: string }) => Promise<SshTerminateSessionsResult>
resetRelay: (args: { targetId: string }) => Promise<void>
getState: (args: { targetId: string }) => Promise<SshConnectionState | null>
needsPassphrasePrompt: (args: { targetId: string }) => Promise<boolean>
testConnection: (args: {
targetId: string
}) => Promise<{ success: boolean; error?: string; state?: SshConnectionState }>
onStateChanged: (
callback: (data: { targetId: string; state: SshConnectionState }) => void
) => () => void
addPortForward: (args: {
targetId: string
localPort: number
remoteHost: string
remotePort: number
label?: string
}) => Promise<PortForwardEntry>
updatePortForward: (args: {
id: string
targetId: string
localPort: number
remoteHost: string
remotePort: number
label?: string
}) => Promise<PortForwardEntry>
removePortForward: (args: { id: string }) => Promise<PortForwardEntry | null>
listPortForwards: (args?: { targetId?: string }) => Promise<PortForwardEntry[]>
listDetectedPorts: (args: { targetId: string }) => Promise<EnrichedDetectedPort[]>
onPortForwardsChanged: (
callback: (data: { targetId: string; forwards: PortForwardEntry[] }) => void
) => () => void
onDetectedPortsChanged: (
callback: (data: { targetId: string; ports: EnrichedDetectedPort[] }) => void
) => () => void
browseDir: (args: { targetId: string; dirPath: string }) => Promise<{
entries: { name: string; isDirectory: boolean }[]
resolvedPath: string
pathFlavor: FilesystemPathFlavor
}>
onCredentialRequest: (
callback: (data: {
requestId: string
targetId: string
kind: 'passphrase' | 'password' | 'keyboard-interactive'
detail: string
echo?: boolean
}) => void
) => () => void
onCredentialResolved: (callback: (data: { requestId: string }) => void) => () => void
submitCredential: (args: { requestId: string; value: string | null }) => Promise<void>
}