From 1527ebffc8db4ccdb180bf808100113f6ac0ba93 Mon Sep 17 00:00:00 2001 From: John Coffey Date: Wed, 26 Aug 2026 16:41:51 -0700 Subject: [PATCH] Remove the two-factor entry point from the login form The field never worked here: Stalwart takes a TOTP code only through an OAuth flow, so a client posting a username and password had nothing to send it to. Offering the button advertised a feature the login path cannot honour, so it comes out until the flow works end to end. The login store still takes a totp argument and the server still accepts one; the form now passes an empty string, which the server reads as no code given. A failed sign-in no longer reveals the field, and the invalid-credentials message drops its mention of a verification code. --- web/src/views/Login.tsx | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/web/src/views/Login.tsx b/web/src/views/Login.tsx index 54b3dd7..402fd98 100644 --- a/web/src/views/Login.tsx +++ b/web/src/views/Login.tsx @@ -1,5 +1,5 @@ import { useEffect, useState, type FormEvent } from "react"; -import { Eye, EyeOff, LogIn, ShieldCheck } from "lucide-react"; +import { Eye, EyeOff, LogIn } from "lucide-react"; import { useSession } from "@/store/session"; import { ApiError } from "@/jmap/client"; import { DEFAULT_SOURCE_URL } from "@/lib/source"; @@ -21,8 +21,6 @@ export function LoginPage() { }, []); const [username, setUsername] = useState(() => localStorage.getItem("ihasmail:lastUser") ?? ""); const [password, setPassword] = useState(""); - const [totp, setTotp] = useState(""); - const [showTotp, setShowTotp] = useState(false); const [showPw, setShowPw] = useState(false); const [remember, setRemember] = useState(true); const [busy, setBusy] = useState(false); @@ -34,13 +32,14 @@ export function LoginPage() { setBusy(true); setError(null); try { - await login(username.trim(), password, totp.trim(), remember); + // No two-factor code: the field is not on this form until the flow works + // end to end, and the server treats an absent code as none given. + await login(username.trim(), password, "", remember); localStorage.setItem("ihasmail:lastUser", username.trim()); } catch (err) { if (err instanceof ApiError) { if (err.code === "invalid_credentials") { - setError(showTotp ? "Invalid credentials or verification code." : "Invalid username or password."); - if (!showTotp && password) setShowTotp(true); + setError("Invalid username or password."); } else if (err.code === "rate_limited") setError("Too many attempts. Please wait a few minutes and try again."); else setError(err.message || "Could not sign in."); } else setError("Network error. Please check your connection."); @@ -75,29 +74,6 @@ export function LoginPage() { - {showTotp ? ( -
- - setTotp(e.target.value)} autoFocus /> - {/* - Kept, and honest about itself. Stalwart accepts a TOTP code only - through an OAuth flow, and offers no password grant, so no client - holding a username and password can pass one — the field cannot - work here today. It stays because someone with 2FA will look for - it, and finding nothing is worse than finding this; the hint sends - them somewhere that does work, and the server explains it again if - they try anyway. - */} - - Most mail servers, Stalwart included, do not accept two-factor codes from webmail — use an app password instead, created in - your mail server's own settings. This field is here for servers that do. - -
- ) : ( - - )}