From 50953839b1bfa4ce3846d4f8309bab7622729be1 Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Mon, 4 Mar 2024 11:56:20 -0800 Subject: [PATCH] SSH Cleanup (#370) * feat: allow user input verification for install Depending on the method of installing waveshell, it may be desired to pop up a modal for user verification. This is a first pass at handling these special cases. The focus is on installing while previously connected and auto installing while connecting. * chore: update mshell to waveshell in error msg * fix: run waveshell remotely with chosen shell This ensures that the appropriate shell is used to run the waveshell command remotely. It hasn't made a difference in my experience but is desired in order to match the local launch. * chore: simplify command to run waveshell remotely This change removes the extra check for a directory and just tries to run the command instead. It pipes the usual error to null and prints an init packet instead. * fix: prevent wavesrv crash during bad connection The waveshell launch can fail in two different ways. If it has a recoverable failure, it will attempt to reinstall waveshell. If not, it is supposed to print an error. The unrecoverable case was causing a segfault due to a misnamed variable. This change corrects it. * fix: correct auto install user input modal The previous combination of flags to catch auto install did not work properly. This corrects them. * chore: add "s" to countdown for user input timer Makes it clear that the countdown is seconds. * fix: remove auto password entry for sudo remote The auto password entry for sudo remotes printed an error that was not in response to the user input. To avoid this confusion, it has been removed entirely. * feat: add auto focus to user input modal This automatically moves the cursor to the text box when the modal pops up. * feat: handle enter/escape keys for password entry The password modal previously had to have buttons clicked to close it. This change allows the user to close it with whatever is bound to escape and to submit with whatever is bound to enter. * chore: update an any type to correct type * fix: correct keyboard event type from last commit * fix: check identity files are readable early Previously, an invalid identity file would send a dummy signer if the file didn't exist. This resulted in extra sign in attempts that have no chance of success. This could cause someone to get locked out of a connection because of too many failed attempts. By performing the check early, we no longer have to make these extra attempts. * fix: only check global known hosts as root The root user should not be able to write to a local known_hosts file. If it does, it risks overwriting the default global behavior for only the root user. This problem would only occur if waveterm was launched as root, but we should protect against it just in case. * feat: add remote name for remote password prompt This change clarifies the remote name for password and keyboard interactive prompts. It displays a message that authentication has been requested from . It is not added to publickey passphrase since those phrases are specific to the key and not the remote. * revert "simplify cmd to run waveshell remotely" This reverts commit 4e5eea51b65318428f83a16133b7cc3df53832bd. --- src/app/common/elements/passwordfield.tsx | 4 +- src/app/common/modals/userinput.tsx | 28 +++++- wavesrv/pkg/cmdrunner/cmdrunner.go | 2 +- wavesrv/pkg/remote/remote.go | 108 ++++++++++++++-------- wavesrv/pkg/remote/sshclient.go | 80 +++++++++++----- 5 files changed, 156 insertions(+), 66 deletions(-) diff --git a/src/app/common/elements/passwordfield.tsx b/src/app/common/elements/passwordfield.tsx index de49ed31c..9065d22dc 100644 --- a/src/app/common/elements/passwordfield.tsx +++ b/src/app/common/elements/passwordfield.tsx @@ -42,7 +42,7 @@ class PasswordField extends TextField { } render() { - const { decoration, className, placeholder, maxLength, label } = this.props; + const { decoration, className, placeholder, maxLength, label, autoFocus } = this.props; const { focused, internalValue, error, passwordVisible } = this.state; const inputValue = this.props.value ?? internalValue; @@ -55,6 +55,8 @@ class PasswordField extends TextField { onChange: this.handleInputChange, onFocus: this.handleFocus, onBlur: this.handleBlur, + onKeyDown: this.props.onKeyDown, + autoFocus: autoFocus, placeholder: placeholder, maxLength: maxLength, }; diff --git a/src/app/common/modals/userinput.tsx b/src/app/common/modals/userinput.tsx index c43abfc87..47a3c55a8 100644 --- a/src/app/common/modals/userinput.tsx +++ b/src/app/common/modals/userinput.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import { GlobalModel } from "@/models"; import { Choose, When, If } from "tsx-control-statements/components"; import { Modal, PasswordField, Markdown } from "@/elements"; +import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil"; import "./userinput.less"; @@ -9,7 +10,7 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => { const [responseText, setResponseText] = React.useState(""); const [countdown, setCountdown] = React.useState(Math.floor(userInputRequest.timeoutms / 1000)); - const closeModal = React.useCallback(() => { + const handleSendCancel = React.useCallback(() => { GlobalModel.sendUserInput({ type: "userinputresp", requestid: userInputRequest.requestid, @@ -39,6 +40,19 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => { [userInputRequest] ); + function handleTextKeyDown(e: React.KeyboardEvent) { + let waveEvent = adaptFromReactOrNativeKeyEvent(e); + if (checkKeyPressed(waveEvent, "Enter")) { + e.preventDefault(); + e.stopPropagation(); + handleSendText(); + } else if (checkKeyPressed(waveEvent, "Escape")) { + e.preventDefault(); + e.stopPropagation(); + handleSendCancel(); + } + } + React.useEffect(() => { let timeout: ReturnType; if (countdown == 0) { @@ -55,7 +69,7 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => { return ( - +
@@ -65,13 +79,19 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
- + handleTextKeyDown(e)} + />
- +