Commit Graph
1874 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 6241ed61d6 fix: stop folder rules from pulling spam out of Junk
Stalwart only moves spam to Junk when nothing else decided where the
message goes. Any Bulwark rule that moved or copied mail to a folder
therefore delivered spam there too, e.g. a "subject contains invoice"
rule collected phishing. From 0.16.22 this also applies to the "Keep"
allow-list action, which older versions still send to Junk.

Move and copy rules now add
`not spamtest :percent :value "ge" :comparator "i;ascii-numeric" "50"`
to their condition when the server supports `spamtestplus` and
`relational`. 50 % is Stalwart's spam threshold on the graded scale
(0.16.19+), and older versions report 0 or 100. A new per-rule option
"Also move messages marked as spam" drops the guard, and "Keep" rules
are left unguarded as the explicit allow-list.
2026-09-22 20:14:42 +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
Linus Rath ba081bd634 chore: drop unused dependencies and declare direct imports
Remove packages nothing imports any more (`@tiptap/extension-image`,
`asn1js`, `pkijs`, `pvtsutils`, `sonner`, `webcrypto-liner`) and the
`elliptic` override, which only applied through `webcrypto-liner`.

`sharp`, `undici`, `@tiptap/extension-paragraph` and
`@tiptap/extension-heading` were imported directly but only resolved as
transitive dependencies of next, jsdom and starter-kit. List them
explicitly and point the `undici` override at `$undici` so both stay in
step.
2026-09-22 19:41:36 +02:00
Linus Rath 06a2e37cd9 fix: load app styles on the global error page 2026-09-22 19:39:39 +02:00
Linus Rath 3c59e811a5 chore: remove unused files and dependencies 2026-09-22 19:36:33 +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 9e2c1dac85 chore: remove unused e2e smoke tests and leftover assets 2026-09-21 23:02:40 +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 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 7d3f3c2f4d chore: release 1.11.0-beta.2 (Bulwark Lite pre-release) 1.11.0-beta.2 2026-09-21 20:54:05 +02:00
Linus Rath ec67ab994b feat: add missing mail action toasts and show sonner toasts again 2026-09-21 20:52:02 +02:00
Linus Rath b03a0c1dd9 feat: show "email sent" toast after immediate sends 2026-09-21 19:36:01 +02:00
Linus Rath 49af83b37a fix: keep staged attachment until upload finishes 2026-09-21 19:18:54 +02:00
Linus Rath 77ef60caae feat: resolve deep links to the local instance 2026-09-20 19:59:37 +02:00
Linus Rath c6bb4aace9 fix(deploy): go back to an image id, not the bulwark-demo:previous tag
Rolling back ran the container from the local tag, so the demo recorded
`bulwark-demo:previous` as its image — and that tag is moved by the very
next deploy. Run the id instead; the tag stays only to keep the image from
being pruned.
2026-09-20 03:03:19 +02:00
Linus Rath c76969522d ci: deploy demo.bulwarkmail.org from here instead of Watchtower
The demo container followed ghcr.io/bulwarkmail/webmail:latest through
Watchtower, so a release reached it silently and there was no way to see,
trigger or undo it. It is now a deployment of this repo:

- .github/workflows/deploy-demo.yml runs after "Publish Docker Image on
  Release" succeeds, or on demand with a tag
  (`gh workflow run deploy-demo.yml -f tag=1.10.0`, or action=rollback).
  It only talks to the server; nothing is built.
- deploy/demo-deploy.sh is the forced command behind the deploy key
  (/usr/local/bin/bulwark-demo-deploy): pulls the tag, recreates the
  container with the demo mounts, checks /api/health and goes back to the
  image it replaced if that fails. A pre-release moves no :latest, so the
  deploy after one finds the same image and does nothing.

Watchtower has been recreated without bulwark-demo; it still updates the
two rbm.systems containers.
2026-09-20 02:57:52 +02:00
Linus Rath 4790101a3b fix: update resourceUrl format in README for release tags 2026-09-19 20:19:10 +02:00
Linus Rath 7b07d25386 chore: release 1.11.0-beta.1 (Bulwark Lite pre-release)
Keep the latest Docker tags on the stable release when a GitHub release is marked as a pre-release, so auto-updating deployments on :latest do not pick up beta builds.
1.11.0-beta.1
2026-09-19 15:04:49 +02:00
Linus Rath 85a3f57bcf docs: add banner and brand badges to README 2026-09-19 14:30:14 +02:00
Linus Rath 043343e3ec fix: correct author name in package.json 2026-09-19 14:15:04 +02:00
Linus Rath f23d87ad60 fix: update author email in package.json 2026-09-19 14:13:50 +02:00
Linus Rath d7548a4bab refactor: migrate icon set from Lucide to Tabler Icons 2026-09-19 13:32:41 +02:00
Linus Rath 5fb29a1174 fix: update .dockerignore to include scripts/lite directory 2026-09-19 13:14:56 +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 321188f08a chore: fix npm audit findings 1.10.0 2026-09-17 23:15:27 +02:00
Linus Rath f12d056522 chore: update version to 1.10.0 2026-09-17 22:53:58 +02:00
Linus Rath 62465e1e80 fix: render "Keep" action as fileinto "INBOX" #1027 2026-09-17 22:42:28 +02:00
Linus Rath f6ad8a5942 feat: add per-message HTML/plain-text toggle #1022 2026-09-17 22:34:20 +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 bd825fb19f Merge pull request #1034 from shukiv/fix/viewer-image-own-max-width
fix(viewer): stop stretching images that carry their own max-width
2026-09-17 22:03:32 +02:00