A server entry's OAuth client ID already has a meaning while OAuth is
off globally: it is the registered client that password and TOTP logins
use with Stalwart's require_client_registration. The PR made every such
entry offer OAuth on its own, so existing installs would suddenly run
discovery and show SSO buttons on the login page, and multi-IdP setups
would get one extra button per server.
A server now gets its own button only with both a client ID and a button
label, which no existing config has. The rule lives in `offersOwnOAuth`
with a test. Also:
- the OAuth-only login form uses the selected server's label
- the public config validator checks `buttonLabel` is a string
- the admin hint and `.env.example` say the label enables the button
The admin login limit keys on the client IP taken from
X-Forwarded-For. With no reverse proxy in front, that header is
whatever the client sends (Next only fills it from the socket when the
request carries none), so each forged address got a fresh bucket of
five guesses. Route handlers cannot see the socket address, so the
limiter now also stops at 50 attempts per 15 minutes across all
clients. `.env.example` says why a proxy that appends the header is
still needed for trustworthy per-IP limits and audit addresses.
An impersonation handoff sealed `<target>%<master>` and the master
password into the session cookie, and `PUT /api/auth/session` hands that
cookie's contents to the browser on every restore. Anyone who received
a handoff could read a credential that signs in as every mailbox and
into Stalwart administration, with no expiry and no audit trail.
The impersonate route now uses the master credential server-side, once,
to create an app password on the target mailbox that expires after
8 hours. Only that app password reaches the cookies. Signing out
revokes it through a sealed grant cookie, and PUT refuses any session
that still holds the configured master password, so cookies minted
before this change are dropped instead of returned.
The heartbeat's `stalwart_version` came from the `Server` header of
`/.well-known/jmap`. Stalwart never sends one (0.16.0 through 1.0), so
the field was null, or the version of a reverse proxy in front of
Stalwart. The probe is gone: the version is reported only when
`STALWART_VERSION` is set.
Lets a per-brand authorize host front a single canonical issuer, so the
IdP token's `iss` stays constant for downstream validation while login
branding varies per domain. Discovery, token exchange and refresh keep
using OAUTH_ISSUER_URL.
Reorganize the Branding section with subsections (App identity, Icons &
favicon, PWA appearance, Logos, Login page) and document the new variables
APP_SHORT_NAME, APP_DESCRIPTION, PWA_ICON_URL, PWA_THEME_COLOR and
PWA_BACKGROUND_COLOR.
- Display Bulwark logo on the login page, with theme-aware switching
between light and dark variants based on the resolved theme
- Add LOGIN_LOGO_LIGHT_URL and LOGIN_LOGO_DARK_URL env variables to
allow custom logo overrides (defaults to bundled Bulwark branding)
- Expose logo config through /api/config endpoint and useConfig hook
- Add logo URL prompts to the setup.sh installer branding step
- Rename branding assets to use underscores instead of spaces
- Added support for parsing and generating additional vCard properties including GENDER, LOGO, SOUND, LABEL, CALURI, CALADRURI, FBURL, and SOURCE.
- Extended ContactCard interface to include new fields such as gender, media, anniversaries, online services, and personal info.
- Implemented logic to handle multi-part TLDs for domain extraction in avatars.
- Improved avatar component to prioritize contact photos and handle inline images in emails.
- Updated localization files to include new fields and labels for contact details.
- Refactored contact store to expose a method for retrieving contact photos.
- Enhanced unit tests to cover new vCard properties and ensure correct parsing and generation.