Admin user management, password reset by email, and the Ultimo Email name
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>
This commit is contained in:
@@ -69,6 +69,19 @@ class AppSettings(Base):
|
||||
default_quota_bytes: Mapped[int] = mapped_column(
|
||||
BigInteger, nullable=False, server_default=text("5368709120")
|
||||
)
|
||||
# Outgoing system mail (password-reset links): the SMTP account the server
|
||||
# sends from. The password is vault-encrypted (like Account.secret_ct).
|
||||
smtp_host: Mapped[str] = mapped_column(Text, nullable=False, server_default=text("''"))
|
||||
smtp_port: Mapped[int] = mapped_column(Integer, nullable=False, server_default=text("587"))
|
||||
smtp_security: Mapped[str] = mapped_column(
|
||||
Text, nullable=False, server_default=text("'starttls'")
|
||||
) # starttls | ssl | none
|
||||
smtp_username: Mapped[str] = mapped_column(Text, nullable=False, server_default=text("''"))
|
||||
smtp_secret_ct: Mapped[bytes | None] = mapped_column(BYTEA)
|
||||
smtp_from: Mapped[str] = mapped_column(Text, nullable=False, server_default=text("''"))
|
||||
# Where users reach the app, e.g. https://mail.example.com — reset links are
|
||||
# built from this, never from the request's Host header.
|
||||
public_url: Mapped[str] = mapped_column(Text, nullable=False, server_default=text("''"))
|
||||
updated_at: Mapped[dt.datetime] = _now()
|
||||
|
||||
|
||||
@@ -91,6 +104,9 @@ class User(Base):
|
||||
# The first account created on a fresh install bootstraps as administrator;
|
||||
# the admin panel can grant it to others later.
|
||||
is_admin: Mapped[bool] = mapped_column(nullable=False, server_default=text("false"))
|
||||
# Set while an administrator has blocked the account (no sign-in, sessions
|
||||
# refused); NULL when not blocked.
|
||||
blocked_at: Mapped[dt.datetime | None] = mapped_column(TIMESTAMP(timezone=True))
|
||||
created_at: Mapped[dt.datetime] = _now()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user