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