diff --git a/src/ui/ssh/dialogs/TOTPDialog.tsx b/src/ui/ssh/dialogs/TOTPDialog.tsx index acbe73fc8..47d1dbf2f 100644 --- a/src/ui/ssh/dialogs/TOTPDialog.tsx +++ b/src/ui/ssh/dialogs/TOTPDialog.tsx @@ -44,6 +44,7 @@ export function TOTPDialog({ if (!isOpen) return null; + const isPassword = mode === "password"; const isPush = mode === "push"; const isMenu = mode === "menu"; const isTotp = !isPush && !isMenu && mode !== "password"; @@ -62,16 +63,17 @@ export function TOTPDialog({ const input = e.currentTarget.elements.namedItem( "totpCode", ) as HTMLInputElement; - if (input?.value.trim()) { - onSubmit(input.value.trim()); - } + const value = isPassword ? input?.value : input?.value.trim(); + if (value) onSubmit(value); }; const title = isPush ? t("sshAuth.mfaPushRequired") - : isMenu - ? t("sshAuth.mfaPromptRequired") - : t("sshAuth.totpRequired"); + : isPassword + ? t("common.password") + : isMenu + ? t("sshAuth.mfaPromptRequired") + : t("sshAuth.totpRequired"); const label = prompt || (isTotp ? t("sshAuth.totpCodeLabel") : undefined); @@ -102,7 +104,17 @@ export function TOTPDialog({ ) : (
- {isPush ? null : isMenu ? ( + {isPush ? null : isPassword ? ( + + ) : isMenu ? ( {cancel}
diff --git a/src/ui/tests/ssh/TOTPDialog.test.tsx b/src/ui/tests/ssh/TOTPDialog.test.tsx new file mode 100644 index 000000000..0580a8ea1 --- /dev/null +++ b/src/ui/tests/ssh/TOTPDialog.test.tsx @@ -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( + , + ); + 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( + , + ); + expect(container.querySelector("input")).toHaveAttribute( + "pattern", + "[0-9]*", + ); + expect(container.querySelector("input")).toHaveAttribute("maxLength", "8"); + }); +});