Commit Graph
363 Commits
Author SHA1 Message Date
Linus Rath c3225b3fa8 fix: share files on Stalwart versions before 0.16.6
Stalwart before 0.16.6 implements an older JMAP File Storage draft
whose rights are only `mayRead`, `mayWrite` and `mayShare`. Bulwark
always sent the newer `mayAddChildren` / `mayRename` / `mayDelete` /
`mayModifyContent`, so sharing a file or folder failed there with
`Invalid permission "mayAddChildren"`, and the rights such a server
reported did not match what the UI checks.

When the account's filenode capability lacks `forbiddenNameChars`
(added by the same draft update), shares are now written with
`mayWrite` set from the finer rights, and `mayWrite` read from the
server is spread over them.
2026-09-22 20:49:44 +02:00
Linus Rath 1ecddf3cbb fix: copy whole folders and write to shared drives in Files
Copying a folder sent one FileNode with no content, which Stalwart
0.16.0 rejected ("size") and 0.16.6+ turned into an empty folder. Copy
now recreates the subtree level by level, and copies into another
account move the file contents with `Blob/copy`.

Every write (new folder, upload, rename, move, delete, copy, share)
went to the primary account, including for nodes of a shared drive,
which the listing names "<accountId>:<nodeId>". Inside a shared drive
the server read those ids as local ones and failed or touched the
wrong node. Writes now go to the account that owns the node, with the
bare id, and a move between accounts is refused instead of being sent.

Creates pass `onExists: "rename"`, so an upload or new folder that
clashes with an existing name becomes "name (2)" instead of failing.
Stalwart before 0.16.6 ignores that argument and refuses the clash, so
the client retries with a numbered name there.
2026-09-22 20:47:41 +02:00
Linus Rath a002b09e02 fix: query calendar ranges in the right time zone and past the limits
CalendarEvent/query reads `after`/`before` as local date-times in the
request's `timeZone`, and Stalwart silently drops a `Z` or offset. The
proactive alert fetch and the contact activity panel sent UTC strings,
so their windows were shifted by the user's UTC offset (two hours in
Berlin in summer). UTC and offset bounds are now converted to the wall
clock of the request time zone before the query is sent.

With server-side expansion, a range with more than
`maxRecurrenceExpansions` occurrences (a daily series over ten years)
failed the whole query with `invalidArguments`, and the calendar showed
nothing. The query now falls back to the unexpanded series, which the
calendar expands on the client as it does for older servers.

The query also stopped at a fixed 1000 ids. Without an explicit limit it
now pages until the range is complete.
2026-09-22 20:42:07 +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 bab19898d9 fix: cap scheduled send at the 7 days Stalwart actually accepts
Stalwart advertises `maxDelayedSend` as a fixed 30 days, but its MTA
rejects any hold beyond `futureRelease`, 7 days by default. Scheduling
a message 8 to 30 days ahead passed the picker and then failed with
"501 5.5.4 Requested hold time exceeds maximum of 604800 seconds".

On Stalwart the advertised 30 days now counts as 7. When a submission
is still rejected for its hold time, the limit from the error message
replaces it for the session, and the composer shows the translated
"later than the server allows" message instead of the raw SMTP reply.
2026-09-22 20:31:53 +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 06788567a1 fix: report failed JMAP calls in send and search instead of succeeding
JMAP names a failed method call `"error"` (RFC 8620 §3.6.2). The send
paths (normal send, raw/S-MIME send and submit, iMIP reply, invitation
and cancellation) and Sieve validation tested for `"<Method>/error"`,
which never matches, so a refused Email/set or EmailSubmission/set
could be reported as sent.

A method error before the submission is created now fails the send.
One after it comes from the implicit onSuccessUpdateEmail /
onSuccessDestroyEmail call: the message has already left, so it is
reported as a filing warning, not as a failed send that would invite a
duplicate resend.

Search turned every failure, including a method error such as
`unsupportedFilter`, into an empty result that read as "No results
found". A failed Email/query now throws, which the search view shows as
an error and the "All folders" fan-out reports per account.
2026-09-22 20:27:16 +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 9893b0a8ad fix: list every file when the account exceeds maxObjectsInGet #1069 2026-09-22 00:17:44 +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 25832a3d69 Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-09-21 22:57:24 +02:00
Linus Rath a4afc407e5 feat: publish Bulwark Lite as a container image #1081 2026-09-21 22:56:26 +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 77ef60caae feat: resolve deep links to the local instance 2026-09-20 19:59:37 +02:00
Linus Rath d7548a4bab refactor: migrate icon set from Lucide to Tabler Icons 2026-09-19 13:32:41 +02:00
Linus Rath ab67d9b07e feat: add "Flat fields" theme 2026-09-19 13:01:07 +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 df966693b0 Merge pull request #997 from thejdubb02/fix/oauth-discovery-session-url-971
fix(oauth): normalize discovery base so a session JMAP_SERVER_URL can refresh (#971)
2026-09-17 22:03:52 +02:00
Linus Rath dba03bd39a Merge pull request #995 from mulatta/fix/plugin-sandbox-cors
fix: allow plugin sandbox chunks to load with CORS
2026-09-17 22:03:47 +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 15308f59e2 Merge pull request #994 from mulatta/fix/calendar-task-progress-normalization
fix: normalize calendar task progress states
2026-09-17 22:03:38 +02:00
Linus Rath 2e88a0c830 Merge main into fix/calendar-participants
Resolves the two conflicts in components/calendar/event-modal.tsx created
by #967 (RSVP controls), both keep-both:

- #967's canRsvp memo and #986's resolveContactName hook are both kept;
  they are independent and were simply inserted at the same point.
- #967's RsvpBar component is kept, and StatusBadge takes #986's
  signature ({ status, t }) since #986 drops the isOrganizer prop and
  updates both call sites to match.
2026-09-17 21:54:08 +02:00
Linus Rath e2f50cbb88 Merge pull request #967 from wrycu/fix/ui-rsvp
fix: rsvp controls
2026-09-17 21:49:38 +02:00
Linus Rath 168a1c7844 fix: never loop forever when deduping drag-out .eml names #1039 2026-09-17 21:42:16 +02:00
Linus Rath 9bc54a4ef6 fix: keep security headers on dotted app paths and neutralise cid: blobs (GHSA-xvjh-v9c6-qcvc) 2026-09-17 21:08:37 +02:00
Linus Rath 53a6670e60 fix: strip CR/LF from read-receipt header values (GHSA-w38p-hpqv-g89c) 2026-09-17 20:55:39 +02:00
Linus Rath e055ee8201 fix: verify credentials upstream before minting identity cookies (GHSA-wxcm-j4jc-9fxq) 2026-09-17 20:48:42 +02:00
Linus Rath d5c0996519 fix: bind the plugin sandbox to its framing window (GHSA-96cx-gx36-3g79) 2026-09-17 20:31:39 +02:00
Linus Rath b7f53a3a8b fix: bind a purpose into encrypted payloads (GHSA-cqqx-mjcf-mh55) 2026-09-17 20:16:51 +02:00
Linus Rath c6c94239ba Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-09-17 20:02:37 +02:00
Linus Rath e908aee994 fix: reject cross-site requests to /api/auth/* to stop session fixation (GHSA-qvr9-m8cq-7wvg) 2026-09-17 20:01:35 +02:00
Linus Rath 51b2198f3f Merge pull request #1003 from lucamzanon/fix/navigation-protocol-account-limit
fix: detect HTTP/2 from the initial navigation timing so accounts aren't capped at five
2026-09-11 11:59:43 +02:00
dealerweb 1c00812ab7 fix: hide body-embedded cid parts declared as octet-stream from the attachment list
Some senders ship the images their HTML body embeds via cid: as
application/octet-stream with no Content-Disposition and no filename.
The body rendered them fine, but the "hide inline images" check only
recognised parts declared inline with an image/* type, so they also
showed up as nameless "Attachment" chips next to the real files.

Both viewers now scan the HTML they actually render for cid: references
(the same pattern the renderer rewrites to blob URLs) and hide a
referenced part when it is an image or generically typed. An explicit
attachment disposition always keeps its chip, as does a referenced part
with a real non-image type, since that chip is its only download. Plain
text rendering references nothing, so nothing is hidden there. The
shared rule lives in lib/attachment-visibility.ts so the desktop viewer,
the thread view and the decrypted-message path cannot drift apart.
2026-09-10 10:45:45 +02:00
lucamzanon 335347a56f fix: detect HTTP/2 from the initial navigation timing so accounts aren't capped at five
isHttp2Available() only inspected "resource" timing entries, which can be
empty right after a fresh login or session restore, before any subresource
finishes loading. On an HTTP/2 page this made getMaxAccounts() fall back to
the HTTP/1.1 cap of five accounts. Include the "navigation" timing entry,
whose nextHopProtocol is known immediately, so h2/h3 is recognized on first
load. Environments without timing data still return false (no h2 assumed).
2026-09-10 10:14:25 +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 9796b0b14d fix: index newer settings and flat calendar toggles in the settings search 2026-09-09 18:40:15 +02:00
Linus Rath 8d8ef7e2f1 fix: unreadable native <select> option lists in dark themes #999
The browser paints a native <select>'s option list itself, so the
bg-muted / text-foreground classes on the control never reach it. Themes
whose --color-muted is translucent (Aurora Glass dark uses
rgba(255,255,255,0.05)) composited to a light popup with near-white
text, leaving every unselected option almost invisible.

Pin option background/colour to the popover tokens, which every built-in
theme defines as an opaque surface, and set color-scheme: dark on
selects under .dark so Safari (which ignores option colours) renders a
dark popup too. Fixes all ~40 native selects at once.

Adds a stylesheet test that locks the rules and asserts every built-in
theme's --color-popover stays opaque in each variant.
2026-09-09 18:30:59 +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 15a5497a12 feat: freely scrolling month, week and day views #759 2026-09-09 16:38:55 +02:00
Justin Willhite b26c174628 fix(oauth): normalize the discovery base so a session JMAP_SERVER_URL refreshes (#971)
OAuth authorization-server metadata lives at the origin root (RFC 8414), but
getRequiredConfig() derived the discovery URL straight from JMAP_SERVER_URL. When
that is the session URL (for example https://mail.example.com/jmap/session, a
common setup so the client can skip the 307 redirect a preflighted CORS request
cannot follow), discoverOAuth fetched
https://mail.example.com/jmap/session/.well-known/oauth-authorization-server,
got a 404, and token refresh failed with "OAuth token endpoint not found", so the
session was dropped after one access-token lifetime.

Strip a trailing session path (/jmap/session or /.well-known/jmap, plus any query
or fragment) before discovery. A plain server root is unchanged, and only the
session suffix is removed, so a real base path prefix is kept.

Adds oauth-discovery-base.test.ts driving getRequiredConfig; the normalization
cases fail without the fix.
2026-09-08 19:17:18 +00:00
mulatta 1c116fde2d fix: allow sandbox chunk loading with CORS #922 2026-09-09 01:29:25 +09:00
mulatta 5f1d279553 fix: normalize calendar task progress states
CalDAV-originated tasks can retain uppercase, underscore-separated, or single-l status spellings in CalendarEvent responses. Normalize known spellings at JMAP response boundaries so task filters and completion controls see canonical JSCalendar values while unknown future values remain intact.

Work around the upstream conversion defect tracked at https://github.com/stalwartlabs/calcard/issues/20.
2026-09-08 23:19:24 +09:00
Linus Rath 26358c7a63 Merge pull request #991 from rotterp/fix/shared-mailbox-send-identity
fix(composer): send from the address a message was delivered to
2026-09-07 22:38:51 +02:00
Linus Rath d28d8dcd86 fix: report calendar clear failures instead of counting zero #434 2026-09-07 22:34:47 +02:00