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
+14 -2
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
import uuid
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from sqlalchemy import select
@@ -152,12 +154,20 @@ async def _check_rate_limit(handle: str, ip: str) -> None:
async def authenticate(
db: AsyncSession, *, handle_input: str, password: str, ip: str, totp: str | None = None
db: AsyncSession,
*,
handle_input: str,
password: str,
ip: str,
totp: str | None = None,
trusted_device: Callable[[uuid.UUID], Awaitable[bool]] | None = None,
) -> User | None:
"""Verify credentials (constant-time). Returns the user or None; may rate-limit.
When the account has TOTP enabled, raises :class:`TotpRequired` (no code given)
or :class:`TotpInvalid` (wrong code) after the password check passes.
or :class:`TotpInvalid` (wrong code) after the password check passes — unless
``trusted_device`` says this browser was trusted for the account at an earlier
2FA sign-in, in which case the password is enough.
"""
handle = resolve_handle(handle_input)
await _check_rate_limit(handle, ip)
@@ -176,6 +186,8 @@ async def authenticate(
from app.identity.totp import decrypt_totp_secret
from app.identity.totp import verify as verify_totp
if not totp and trusted_device is not None and await trusted_device(user.id):
return user
if not totp:
raise TotpRequired
if not verify_totp(decrypt_totp_secret(user.totp_secret), totp):