Commit Graph
458 Commits
Author SHA1 Message Date
Linus Rath 6f1cad1c98 fix: close theme CSS sanitizer bypasses and enforce :root/.dark selectors
The theme sanitizer was a short denylist. It missed protocol-relative
`url(//host)`, schemes and keywords spelled with CSS escapes
(`url(\68ttps:...)`, `@\69mport`), `image-set("https://...")` and
`@font-face` sources, so a theme could load remote resources from the
app document. `validateThemeSelectors` was never called, so theme CSS
could also target `input[type=password]` or restyle real UI. The admin
routes and the zip validator only sanitized when the safety check
failed, and CSS returned by the `onThemeBeforeApply` plugin hook was
injected as-is.

- every `url()` except a `#fragment` and every `image-set()` is removed
  with its arguments; a banned construct spelled with escapes drops the
  whole stylesheet
- theme CSS keeps only `:root` and `.dark` rules (no nested rules),
  plus `@font-face`, `@keyframes` and `@media`/`@supports` wrappers
- sanitizing is unconditional everywhere, including plugin-transformed
  CSS; compiled token values may not contain escapes or `url()`
2026-09-26 14:36:02 +02:00
Linus Rath ad84588f6a fix: keep an SVG attachment thumbnail from running script in the webmail origin
Attachment thumbnails were object URLs typed with the sender's MIME
type. Object URLs share the webmail origin, so an `image/svg+xml`
attachment opened from its thumbnail ("Open image in new tab", or
dragged to the tab strip) ran the sender's script as the webmail in
Chromium and Firefox. The drag-out URL and Files thumbnails had the
same shape.

`fetchBlobAsObjectUrl` now re-types every blob to an inert type, and
image thumbnails in mail and Files go through `imageBlobUrl`, which
hands an SVG over as a `data:` URL: it still renders in the `<img>`,
but opened on its own it gets an opaque origin.
2026-09-26 14:30:19 +02:00
Linus Rath ff8b192508 fix: keep a folder from switching back to the unified inbox while it loads #1102 2026-09-26 12:41:13 +02:00
Linus Rath aea9edc03b Merge pull request #1103 from guisea/fix/folder-view-unified-blend
fix(email-store): stop late unified/cross-view fetches from stomping folder selection
2026-09-26 12:34:52 +02:00
Aaron Guise c08375974a fix(email-store): stop late unified/cross-view fetches from stomping folder selection 🐛
fetchUnifiedEmails and fetchCrossView set isUnifiedView:true synchronously
then await a network fetch with no check on completion that the view is
still current. If one resolved after the user had since selected a real
folder, it re-set isUnifiedView back to true while selectedMailbox stayed
on the folder - so a push-driven refreshCurrentMailbox (e.g. after
deleting a message) took the unified branch and blended in Inbox-tagged
messages instead of refreshing the folder.

Add a viewToken bumped by selectMailbox/selectKeyword/exitUnifiedView;
fetchUnifiedEmails/fetchCrossView capture it before fetching and discard
their result if it no longer matches on return. Also make selectMailbox/
selectKeyword defensively reset isUnifiedView/unifiedRole/crossView.

Upstream: bulwarkmail/webmail#1102
2026-09-25 07:30:55 +12:00
Linus Rath 3823d2da17 fix: sign in and show addresses on internationalized domains #1100 2026-09-24 21:15:22 +02:00
Linus Rath 84a639f5d4 fix: keep bringing up unread mail past the first page in unread-first order 2026-09-23 21:01:48 +02:00
Linus Rath f97218375e fix: end the identity provider session on sign-out #905 2026-09-23 20:44:46 +02:00
Linus Rath 691cde6468 fix: explain why calendar subscriptions fail with not authenticated #1073 2026-09-23 18:31:27 +02:00
Linus Rath 28fd880582 fix: keep single-occurrence edits on that occurrence and keep its details 2026-09-23 13:40:01 +02:00
Linus Rath f95b826434 feat: let attendees answer a single occurrence of a recurring event #1086 2026-09-23 13:06:41 +02:00
Aaron Guise 887b9e7f92 feat(notifications): add toggleable inbox-only push notifications ✨ 2026-09-23 11:07:58 +12:00
Linus Rath 84366dd810 fix: send search terms as typed in tag-view searches too 2026-09-22 21:48:59 +02:00
Linus Rath 8f285e57d3 Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-09-22 21:40:54 +02:00
Linus Rath d7fdd7ce61 fix: offer to save an event when the server refuses its invitations
From 0.16.21 Stalwart fails the whole CalendarEvent/set with
`forbidden` when the account may not send scheduling messages, so an
event with attendees was not saved at all and the calendar showed only
"Failed to save event".

Such a refusal now surfaces as SchedulingDeniedError, and the calendar
names the server's reason and offers to save the event without sending
the invitations.
2026-09-22 21:25:20 +02:00
Linus Rath fa33351f8d fix: stay signed in after changing the password in settings
After a password change the session kept authenticating with the old
password: the JMAP client's Basic header, the server-side context the
Stalwart management calls use, and the remembered login. The next
request was refused and the user was sent back to the login page.

A successful password change now switches all three to the new
password for Basic-auth sessions. OAuth sessions are unaffected by this
change.
2026-09-22 21:20:41 +02:00
Linus Rath cee760e992 fix: send mail search terms as typed instead of with a fake prefix wildcard 2026-09-22 21:04:18 +02:00
Linus Rath 73d971dbf9 fix: save contacts with calendar links and cleared fields correctly
Stalwart rejects a whole ContactCard over one property it does not
know, and several reached it:

- the form sent `calendarUri`, `schedulingUri` and `freeBusyUri`,
  so any contact with a calendar, scheduling or free/busy link failed
  to save ("Invalid property.")
- vCard import sent `source`, and Pro mode left `localAccountId` on
  the card
- vCard import wrote addresses as flat `street`/`locality` fields
  instead of RFC 9553 `components`

The card now passes through `lib/jmap/contact-wire.ts` on its way to
the server: client-only fields are dropped, the links are written as
`calendars` / `schedulingAddresses` (and SOURCE as a `directories`
entry), flat addresses become components, and reads map the links back
for the form and detail view.

Clearing a field (nickname, notes, a link) never reached the server:
the form set it to `undefined`, which JSON drops, and an omitted
property keeps its value. Updates now send `null` for cleared keys.

Deleting an address book that still held contacts always failed with
`addressBookHasContents`. The delete now passes
`onDestroyRemoveContents`, which the confirm dialog already promised.
2026-09-22 20:37:16 +02:00
Linus Rath 91a1b1cb51 fix: let non-admin users see their name and change a TOTP password
Bulwark read the live display name (#900) and the account profile with
`x:Account/get`, which Stalwart only allows for admins. Every other
user got `forbidden`, so the settings page showed no name and the
identity kept the stale snapshot. The name now comes from
`x:AccountSettings/get`, the account's own settings object; aliases
and quota still use `x:Account/get` where it is permitted.

With TOTP on, Stalwart requires the current code for a password change
and for switching TOTP off ("Current OTP code is required"). Neither
dialog asked for it. Both now do, and the password change sends it as
the `otpAuth/otpCode` patch, because a whole `otpAuth` object would
reset `otpUrl` and turn TOTP off.

Saving the display name also ignored a `notUpdated` answer and showed
success.
2026-09-22 20:28:01 +02:00
Linus Rath dadfb08f9c fix: keep filter flags and folder targets when mail is moved
The Sieve generator wrote actions in the order they were picked in the
rule editor. `fileinto` stores the flags set at that moment, so "mark
read", "star" or "add label" placed after a move never reached the
moved message. Flag actions are now emitted first.

Moves and copies also targeted the folder by path only. Renaming the
folder silently sent the rule's mail to Inbox. Rules now store the
folder id and emit `fileinto :mailboxid "<id>" "<path>"` when the
server lists `mailboxid` (declaring `mailbox` too, which Stalwart
requires), with the path as the fallback. Saving a rule refreshes the
path from the id, so existing rules pick up the id on their next save.

Forward actions get a "Keep a copy" option (`redirect :copy`), and the
editor honours the server's `maxNumberRedirects` (1 on Stalwart):
forward is disabled once the limit is used up, with a hint when
existing rules already exceed it, because the server skips extra
redirects without telling anyone.
2026-09-22 20:09:19 +02:00
Linus Rath 198a3c0d61 fix: keep mail filters running while the auto-reply is on
Stalwart allows one active Sieve script and turns the auto-reply on by
activating its own `vacation` script. Turning the auto-reply on switched
every Bulwark filter off (including junk and folder rules), and saving
any filter switched the auto-reply off again.

When the vacation script takes over from an existing filters script,
the filters script is now re-activated with
`include :personal :optional "vacation"`, so the server-built reply
(with its date window and HTML body) runs from there. Turning the
auto-reply off drops the include again. A filters save while the
vacation script is active keeps it through the same include, and the
vacation settings read the include as "on", because
VacationResponse.isEnabled reports false in that state.

Clients that only look at VacationResponse (Stalwart's web UI, phones)
still show the auto-reply as off while the include is active.
2026-09-22 20:02:06 +02:00
Patrick Rotter 90a6c0cb1d fix: a search inside a tag view narrows the tag instead of replacing it
Selecting a tag does not clear an active search: `handleTagSelect` leaves
`searchQuery`/`searchFilters` alone and `selectKeyword` leaves
`selectedMailbox`, so a tag view and a search are active together. The
paths then disagreed about which one wins:

  fetchEmails / refreshCurrentMailbox → `selectedKeyword` first, so the
    tag fan-out ran and the query was dropped;
  searchEmails / advancedSearch / loadMoreEmails → the search first, so
    since #1082 the all-folders fan-out ran and the tag was dropped.

Both orders are defensible on their own; having both is not. A
push-triggered refresh silently replaced a search result list with the
whole tag, and "load more" appended rows the tag does not contain — the
same class of inconsistency #1082 set out to remove between a search and
its own next page.

Resolve it the way the codebase already documents ("a tag view takes
precedence over the selected folder"), and keep the query: a search inside
a tag view now narrows the tag, in all five paths. `fetchTagEmails` grows
an optional `extraFilter`, which `getEmails` already accepts alongside
`hasKeyword` — the same argument the message-list category tabs use — so
no new query plumbing is needed.

Searching within the tag is also the more useful reading: a tag view is a
list the user deliberately opened, and a search box above it reads as
"within this", not "forget this".
2026-09-22 19:41:51 +02:00
Patrick Rotter 74f9b63f2c fix: drop a search folder scope that belongs to the account being left
`selectAccountMailbox` switches the viewed account but kept
`searchMailboxId`, which names a folder of the account being left.

Since #1082 that scope resolves against `resolveActionMailboxes()`, which
now returns the NEW account's mailbox list, so the id no longer matches
anything. `searchEmails` / `advancedSearch` then take the picked-folder
branch with `mailbox === undefined`, fall back to `jmapMailboxId =
searchMailboxId` and `accountId = undefined`, and query a foreign folder
id against the wrong account — which comes back empty.

It is invisible in the UI: the <select> binds `value={searchMailboxId}`
and has no matching <option>, so a native select renders the first one and
the dropdown reads "All folders" while the store still holds the stale id.
The user sees an empty result list for a search they believe is unscoped.

Reset it on the account switch, so the scope always matches what the
dropdown shows.
2026-09-22 19:25:18 +02:00
Linus Rath 9ef7068bd8 fix: include shared accounts in "All folders" mail search #1082 2026-09-22 17:25:43 +02:00
Linus Rath 106bc07f04 feat: add OpenID Connect login to the Stalwart Lite bundle 2026-09-21 23:45:08 +02:00
Linus Rath 5a6c340f18 Merge pull request #1013 from lucamzanon/fix/cross-account-thread-key
fix: scope thread identity to the owning account in unified views
2026-09-21 22:33:26 +02:00
Linus Rath 7a562e1830 Merge pull request #976 from bartfaizoli76/fix/sandbox-locale-automatic
fix: plugin sandbox locale ignores 'Automatic' language resolution
2026-09-21 22:33:14 +02:00
Linus Rath fdfcb1711e feat: add Bulwark Lite build for Stalwart Applications 2026-09-19 00:53:53 +02:00
Linus Rath 2d4a562432 fix: harden Bulwark Lite login, deep-link replay and settings gating 2026-09-18 22:16:57 +02:00
lucamzanon 5b63993bfe fix: scope thread identity to the owning account in unified views #1012
JMAP thread ids are only unique within their account - Stalwart hands out
per-account counters such as "b" - so the cross-account views merged
unrelated conversations that happened to share an id, and every
client-side thread map (expansion, cache, loading flag, counts,
virtualizer key) mixed them up the same way.

Give ThreadGroup an account-scoped `threadKey` derived from the email's
source stamps and key grouping and all thread state by it. `threadId`
stays the raw id for Thread/get, which #281 already routes to the owning
account. fetchThreadEmailCounts now asks each source account for its own
threads instead of one client for every id. Single-account views carry no
stamps, so their key is the bare id and nothing changes there.
2026-09-18 18:33:02 +02:00
Linus Rath 5de98bc04b feat: add Bulwark Lite static export build 2026-09-17 23:54:33 +02:00
Linus Rath fedfc89d44 fix: list tagged mail from every account in the tag view #1038 2026-09-17 22:26:52 +02:00
Linus Rath 863354d5f1 Merge pull request #980 from sanitz/fix/task-toggle-progress-updated-958
fix: omit progressUpdated from task completion payload (#958)
2026-09-17 22:03:43 +02:00
Linus Rath ee4763d5c0 Merge pull request #1036 from dealerweb/fix/calendar-principal-fetch
fix(calendar): stop the account principal fetch from failing on every load
2026-09-17 22:03:26 +02:00
Linus Rath a0316302d9 fix: strip local-account prefix from address-book ids on contact update #1043 2026-09-17 21:50:48 +02:00
dealerweb 1fa4915517 fix: recognise a refused principal read by its JMAP error type
Non-admins usually may not read their own Account object (no
sysAccountGet), and fetchPrincipal was written to treat that as the
normal case. It only matched the word "forbidden" in the message, though,
while Stalwart answers with a `forbidden` method error whose description
reads "You are not authorized to perform this action" - so every calendar
load still logged an error and left an error message in the store.

Classify by the method error type (and HTTP 403), keep the wording checks
as fallback, and log the refusal at debug level only.
2026-09-15 12:44:36 +02:00
Ctaoist fc812c9b23 fix: preserve label filtering during mailbox refreshes 2026-09-12 09:40:44 +08:00
Linus Rath fb24ef5d24 feat: sort contacts by last name #963 2026-09-09 19:47:33 +02:00
Linus Rath 565d5c1095 feat: choose exact-address or same-domain matching for reply from received address #1000 2026-09-09 19:27:08 +02:00
Linus Rath c395226926 fix: reject wrong passwords server-side so the browser never shows its Basic Auth dialog #969 2026-09-09 19:21:40 +02:00
Linus Rath b91b438076 test: keep stray fetches and timers from leaking across resilience tests 2026-09-09 18:56:51 +02:00
Linus Rath ae403b06af feat: setting to turn off free scrolling in the calendar 2026-09-09 18:27:01 +02:00
Linus Rath d4d3f317a5 fix: stop retrying token refreshes that fail permanently #972 2026-09-07 22:34:47 +02:00
Linus Rath 80dadfbbd4 fix: open search hits from shared folders in the right account #923 2026-09-07 22:34:47 +02:00
Linus Rath d28d8dcd86 fix: report calendar clear failures instead of counting zero #434 2026-09-07 22:34:47 +02:00
Linus Rath 07bbde4786 fix: make the iCal subscription size limit configurable and show the real error #692 2026-09-07 22:34:47 +02:00
Linus Rath 928e2e3794 feat: add a server-side switch for the Stalwart JMAP passthrough #904 2026-09-07 22:34:47 +02:00
Linus Rath 4afc2a5db0 fix: surface Email/set failures on delete and move #956 2026-09-07 22:34:47 +02:00
Linus Rath 150c25ca0e fix: mark as spam from the viewer when the message left the list #695 2026-09-07 22:34:47 +02:00
Linus Rath 8dc1564b72 fix: archive shared-inbox mail into the owner's archive #889 2026-09-07 22:34:47 +02:00