fix(ssh): accept full passwords in keyboard-interactive prompts (#1569)

This commit is contained in:
ZacharyZcR authored and GitHub committed 2026-10-07 16:37:45 -05:00
1 parent f412a28bb8
commit f4164a74b2
2 files changed
+74 -8

No files matched your search

+24 -8
View File
@@ -44,6 +44,7 @@ export function TOTPDialog({
if (!isOpen) return null; if (!isOpen) return null;
const isPassword = mode === "password";
const isPush = mode === "push"; const isPush = mode === "push";
const isMenu = mode === "menu"; const isMenu = mode === "menu";
const isTotp = !isPush && !isMenu && mode !== "password"; const isTotp = !isPush && !isMenu && mode !== "password";
@@ -62,16 +63,17 @@ export function TOTPDialog({
const input = e.currentTarget.elements.namedItem( const input = e.currentTarget.elements.namedItem(
"totpCode", "totpCode",
) as HTMLInputElement; ) as HTMLInputElement;
if (input?.value.trim()) { const value = isPassword ? input?.value : input?.value.trim();
onSubmit(input.value.trim()); if (value) onSubmit(value);
}
}; };
const title = isPush const title = isPush
? t("sshAuth.mfaPushRequired") ? t("sshAuth.mfaPushRequired")
: isMenu : isPassword
? t("sshAuth.mfaPromptRequired") ? t("common.password")
: t("sshAuth.totpRequired"); : isMenu
? t("sshAuth.mfaPromptRequired")
: t("sshAuth.totpRequired");
const label = prompt || (isTotp ? t("sshAuth.totpCodeLabel") : undefined); const label = prompt || (isTotp ? t("sshAuth.totpCodeLabel") : undefined);
@@ -102,7 +104,17 @@ export function TOTPDialog({
</div> </div>
) : ( ) : (
<form onSubmit={handleSubmit} className="flex flex-col gap-3"> <form onSubmit={handleSubmit} className="flex flex-col gap-3">
{isPush ? null : isMenu ? ( {isPush ? null : isPassword ? (
<Input
id="totpCode"
name="totpCode"
type="password"
autoComplete="current-password"
autoFocus
placeholder={t("placeholders.enterPassword")}
className="rounded-none bg-muted/50 border-border text-sm"
/>
) : isMenu ? (
<Input <Input
id="totpCode" id="totpCode"
name="totpCode" name="totpCode"
@@ -137,7 +149,11 @@ export function TOTPDialog({
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
{cancel} {cancel}
<button type="submit" className={PROMPT_PRIMARY_BUTTON}> <button type="submit" className={PROMPT_PRIMARY_BUTTON}>
{isPush ? t("sshAuth.mfaSendRequest") : t("sshAuth.totpVerify")} {isPush
? t("sshAuth.mfaSendRequest")
: isPassword
? t("common.connect")
: t("sshAuth.totpVerify")}
</button> </button>
</div> </div>
</form> </form>
+50
View File
@@ -0,0 +1,50 @@
import "@testing-library/jest-dom/vitest";
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { TOTPDialog } from "@/ssh/dialogs/TOTPDialog";
vi.mock("react-i18next", () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
describe("SSH password prompts", () => {
it("accepts a full password and preserves whitespace", () => {
const onSubmit = vi.fn();
const { container } = render(
<TOTPDialog
isOpen
prompt="Password:"
mode="password"
onSubmit={onSubmit}
onCancel={vi.fn()}
/>,
);
const input = container.querySelector("input")!;
expect(input.type).toBe("password");
expect(input).not.toHaveAttribute("maxLength");
expect(input).not.toHaveAttribute("pattern");
expect(input).not.toHaveAttribute("inputMode", "numeric");
fireEvent.change(input, { target: { value: " LongPassword!123 " } });
fireEvent.submit(container.querySelector("form")!);
expect(onSubmit).toHaveBeenCalledWith(" LongPassword!123 ");
expect(screen.queryByText("sshAuth.totpRequired")).not.toBeInTheDocument();
});
it("still restricts verification codes to digits", () => {
const { container } = render(
<TOTPDialog
isOpen
prompt="Code:"
mode="totp"
onSubmit={vi.fn()}
onCancel={vi.fn()}
/>,
);
expect(container.querySelector("input")).toHaveAttribute(
"pattern",
"[0-9]*",
);
expect(container.querySelector("input")).toHaveAttribute("maxLength", "8");
});
});