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>
Branding: the page titles and app name are now "Ultimo Email".
Admin panel (now two columns with a divider):
- Users: search by login or by a mail account's address or name (results
only, nothing listed until something is typed). A user opens a window to
change their quota, block/unblock them, or delete them; block and delete ask
for confirmation, and an admin cannot do either to their own account.
- Blocking (users.blocked_at, migration 0019) refuses sign-in with a clear
message and revokes every session; a new user.signed_out event sends the
user's open tabs to the sign-in screen at once. Any "not signed in" answer
now does the same, not only /auth/me. Deleting removes mail accounts (sync
stops), plugin rows (cleared inside the user's RLS context, since plugin
tables have no FK to users), blobs, the search index, sessions and the user.
- Outgoing system mail (migration 0020): SMTP server, port, security, login,
password (vault-encrypted, never sent back), From address and the public
address reset links point to, plus a "Send test" that reports the server's
error.
Password reset:
- "Forgot password?" on the sign-in page asks for the user name and always
answers "If this account exists you will receive an email to your recovery
email address"; the mail goes out after the response, so neither the reply
nor its timing reveals an account, and it is capped at 3 per hour.
- The emailed link (token in the URL fragment, single use, 30 minutes) opens a
page to set a new password. With 2-step verification on, the code is checked
before anything changes; five wrong codes burn the link.
- Settings > Profile: the recovery email can be added, changed or removed,
confirmed with the current password, which must be typed — autofill and
paste are refused (TypedPasswordInput).
Also adds frontend/icons/ultimo-logo-bimi.svg, an SVG Tiny PS version of the
logo for a BIMI record.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>