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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user