Logo in system mail; "don't ask for the 2FA code on this device"

System mail: the password-reset email (and the admin panel's test email) now
opens with the Ultimo logo, centered. It is a PNG rendered from
icons/ultimo-logo.svg and embedded in the message (multipart/related, cid:),
so it shows where remote images are blocked and in clients that do not
render SVG. The PNG is listed in package-data so the Docker image carries it.

Trusted devices: a 2FA sign-in offers "Don't ask for the code on this device
in future". Ticked with a correct code, the browser gets its own HttpOnly
trusted-device cookie (token in Redis, 90 days, renewed on use); later
sign-ins to the same account from that browser need only the password. Other
accounts and browsers are unaffected. The trust is revoked on a password
reset, when 2FA is turned off, and when an admin blocks or deletes the user;
signing out keeps it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 21:02:44 +02:00
co-authored by Claude Opus 5.5
parent d1f6d183e0
commit 165442d897
14 changed files with 334 additions and 12 deletions
+11 -1
View File
@@ -11,13 +11,14 @@ export function Login() {
const [password, setPassword] = useState("");
const [totp, setTotp] = useState("");
const [needTotp, setNeedTotp] = useState(false);
const [rememberDevice, setRememberDevice] = useState(false);
const navigate = useNavigate();
const queryClient = useQueryClient();
// Only offer the signup link when the server would actually accept one.
const registration = useQuery({ queryKey: ["registration"], queryFn: api.registrationStatus });
const mutation = useMutation({
mutationFn: () => api.login(handle, password, needTotp ? totp : undefined),
mutationFn: () => api.login(handle, password, needTotp ? totp : undefined, needTotp && rememberDevice),
onSuccess: (user) => {
queryClient.setQueryData(["me"], user);
navigate("/mail");
@@ -71,6 +72,15 @@ export function Login() {
autoFocus
/>
<p className="text-xs text-app-muted">{t("auth.totpPrompt")}</p>
<label className="flex items-center gap-2 pt-1 text-sm text-app-fg">
<input
type="checkbox"
checked={rememberDevice}
onChange={(e) => setRememberDevice(e.target.checked)}
className="h-4 w-4 accent-[rgb(var(--app-accent))]"
/>
{t("auth.rememberDevice")}
</label>
</div>
)}
<button