Commit Graph
115 Commits
Author SHA1 Message Date
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 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 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 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 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 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 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
Linus Rath 2b32a0110b fix: report a missing archive mailbox instead of failing silently #578 2026-09-07 22:34:47 +02:00
Linus Rath 0e7095e509 Merge pull request #872 from vj1235432/fix/quick-search-stale-responses
fix(search): guard quick search against stale responses
2026-09-07 17:56:54 +02:00
Linus Rath 7652d974e1 Merge pull request #860 from dealerweb/fix/logout-test-force-url
test: match the forced token refresh URL in the logout tests
2026-09-07 17:43:22 +02:00
Sebastian Sanitz 6b30e91c9f fix: omit progressUpdated from task completion payload (#958)
Stalwart Mail Server strictly validates JSCalendar task properties on CalendarEvent/set and rejects updates containing top-level progressUpdated with invalidProperties (RFC 8984 §5.2.2 only defined progressUpdated on Participant objects, obsoleted in JSCalendar 2.0).

- Omit progressUpdated from toggleTaskComplete in stores/task-store.ts
- Remove progressUpdated from CALENDAR_TASK_PROPERTIES in lib/jmap/client.ts
- Defensively strip progressUpdated in updateCalendarEvent and createCalendarTask
- Make progressUpdated optional in CalendarTask interface
- Add unit tests for task-store and JMAP client task methods
2026-09-06 17:47:00 +02:00
Stefan Hildebrandt 242fe6aa2d fix(mail): unified mailbox missed accounts that had not connected yet
After a reload the unified mailbox showed only the active account's mail;
other accounts appeared only once visited. Intermittent (~1 in 14), and
not a timeout - nothing on this path uses setTimeout/AbortSignal/race.

checkAuth awaits only the active account and restores the rest in a
fire-and-forget background loop, registering each client only after
connect() resolves. buildUnifiedAccountClients drops accounts with no
live client (`if (!c) continue`). And isConnected was persisted, so it
rehydrated stale-true: the account passed the isConnected filter while
failing the client lookup, and connectedAccountsSignature already looked
complete at mount so nothing re-fired - and the effects keyed on it only
recompute counts, never the list.

Clear isConnected/hasError on rehydrate so both the filter and the
signature describe this session, and refetch the open unified/cross list
when an account joins the connected set (growth only, not on first
render, not while a search owns the list). Extracted
connectedAccountsGrew so the membership check is testable without
rendering MailApp.

Verified the reset cannot block reconnection: checkAuth reads the full
account list and never filters on isConnected.

Fixes #950
2026-09-02 22:15:50 +02:00
Linus Rath 0c7437f67a feat: open global search hits through the owning login on every surface #847 2026-09-01 20:55:13 +02:00
Linus Rath 0db6bfdc64 feat: global search core: query parser, ranking, cross-account providers #641 2026-09-01 20:55:12 +02:00
Linus Rath 0b39e43a4d feat: surface ShareNotification changes as toasts 2026-08-29 17:59:04 +02:00
Linus Rath 5805a28e8c feat: resolve pushes with Email/changes and Mailbox/changes deltas 2026-08-29 17:42:21 +02:00
Linus Rath 1807b2df6b feat: allow setting an address book as default (#924) 2026-08-29 17:15:50 +02:00
Linus Rath d800689924 feat: use Stalwart synthetic ids for recurring occurrences #140 2026-08-29 11:03:48 +02:00
Linus Rath 99daa53c8c feat: search suggestions with recent searches and contact autocomplete #845 2026-08-25 20:09:18 +02:00
Linus Rath 8cd5c00b06 fix: keep the folder tree when a refresh burst hits maxConcurrentRequests #780 2026-08-25 19:09:21 +02:00
Linus Rath f189e0bab6 fix: refresh account display name from the Stalwart principal on login/restore/switch #900
Stalwart seeds the default identity name from the principal "Full name" only
once, and addAccount never rewrote displayName after first login, so an admin
rename never showed up in Bulwark. Read the live name via x:Account/get
through the passthrough (falling back to the identity name elsewhere) and
prefer the synced registry name in account settings.
2026-08-25 18:45:44 +02:00
Linus Rath 3e5ed4f3c4 feat: folder tabs via drag & drop + sidebar in fullscreen email tabs 2026-08-20 15:06:45 +02:00
vj1235432andClaude Opus 5 88712994de fix(search): guard quick search against stale responses
Quick search fired one request per keystroke with no AbortController and no
staleness check, so whichever response landed last won regardless of which
query it belonged to. Typing an address slowly showed hits belonging to a
short prefix while the box held the full string; retyping the same string in
one go correctly showed none, which reads as "search works sometimes".

advancedSearch already guarded every write with controller.signal.aborted.
This gives searchEmails the same treatment, reusing the existing
searchAbortController so the two search paths supersede each other rather
than race. The catch branch is guarded too: without it a superseded request
that rejects clears results belonging to the current query and surfaces a
misleading error.

Adds stores/__tests__/email-store-search-race.test.ts with three cases, all
of which fail on main and pass with this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 00:48:54 +04:00
dealerweb 7aa3825be7 test: match the forced token refresh URL in the logout tests
The shorter auth waterfall makes refreshAccessToken skip the server-side
token cache by default, appending force=true to the PUT. The logout
redirect tests still mocked the bare slot URL, so every refresh hit the
mock's unexpected-call guard and three tests failed. Match the PUT on
the forced URL; the DELETE paths are unchanged.
2026-08-18 15:28:02 +02:00
Linus Rath 58ddd01cf7 Merge pull request #792 from dev-hive-kazniisa/fix/shared-mailbox-folder-management
fix(email-store): route shared-folder management to the owner account
2026-08-17 12:18:24 +02:00
KazNIISA IT ad0cc108b4 fix(email-store): scope the owner-client lookup to one server
Review follow-up on the shared-folder mutation routing.

The owner lookup replaced the resolved client with any connected client whose
JMAP account id matched. Account ids are opaque per-server values, and personal
mailboxes carry an accountId too, so this ran on every folder mutation and could
hand a mutation to a client of a different server that happens to issue the same
id - where the bare mailbox id may exist and belong to someone else. Keep the
resolved client when it already is the owner, and require a candidate to match
the server URL as well as the account id.

Recover the owner account and the bare mailbox id from the namespaced store id
when no mailbox object is available. Previously the raw `accountId:id` went to
the server as a mailbox id with the owner dropped, so the mutation targeted the
active personal account; in setMailboxRole that cleared the personal role before
failing.

Drop the shared account header context menu when the account carries no usable
id. buildMailboxTree groups shared mailboxes without an accountId under the
placeholder "unknown", which cannot be a mutation target.
2026-08-17 09:10:11 +05:00
Linus Rath 644a9a0793 fix: search folder filter defaults to all folders and persists #788 2026-08-16 22:32:14 +02:00
Linus Rath ffff165a4b Merge pull request #800 from rotterp/fix/shared-folder-account-context
fix: a directly-selected shared folder has no account context (tags, compose identity)
2026-08-16 21:27:18 +02:00
Linus Rath 47cf0ed90d feat: rework pro split-screen shell, per-pane tab strips, drag & drop, pane-scoped overlays 2026-08-16 03:03:40 +02:00
Linus Rath e6597542fd fix: include email templates in cross-device settings sync #825 2026-08-16 02:09:38 +02:00
Stefan Hildebrandt b63b227645 fix(email): route unified/cross virtual ids through the fan-out in fetchEmails (#791)
fetchEmails sent a unified/cross view's client-only virtual mailbox id
(__cross_all__ / __unified_*) straight to getEmails as `inMailbox`. The server
rejects it (Stalwart: "filter.inMailbox = '__cross_all__'") and the foreground
catch wiped the list to "no messages found" - notably after actions (mark read,
delete, move) in the aggregated views.

Mirror the loadMoreEmails / refreshCurrentMailbox guard: when the target id is
virtual, fan out via fetchCrossViewEmails / fetchUnifiedEmails (search-aware) at
page 0 instead of querying the virtual id. If the view state isn't resolved yet,
stop loading without sending the id or wiping the list. Real folders are
unaffected.

+5 unit tests (email-store-fetch-unified): cross/unified fan-out, explicit
virtual-id arg, unresolved-view no-op, and the real-folder path still using
getEmails. tsc + eslint clean; all store suites green (389).
2026-08-13 21:27:24 +02:00
Patrick RotterandClaude Opus 5 3560cf258b fix: route keyword writes to the selected shared account
Tags, pins and $answered/$forwarded/$mdnsent set on a message in a
shared/group mailbox opened directly from the "Shared" sidebar section
did not persist: they showed until the next reload and were then gone.

The call sites resolved the target account as
`isUnifiedView ? email.sourceAccountId : undefined`. Outside the unified
view that is always `undefined`, so the write went to the reaching
client's own account, which the server answers with `notUpdated` /
`updated: null` and no error - the same silent no-op that
`email-store-shared-folder-actions.test.ts` already covers for the batch
actions. Emails fetched from a shared folder this way are undecorated
(no `sourceAccountId`); the owner is only recoverable from the selected
mailbox.

`toggleStar` never had the bug because it routes through
`resolveEmailActionContext`, which resolves the owner from the email's
source in an aggregate view and from the selected mailbox otherwise.
That resolver was reimplemented inline at each keyword call site, and
the inline copies only ever covered the aggregate half - so starring a
shared message persisted while tagging the same message did not.

Move the two keyword writes into store actions built on that resolver
and route every call site through them, including the read-receipt and
Pro-compose paths that previously passed no accountId at all.

The store actions resolve only for an email the store actually knows.
A Pro tab fetches its own email and never enters `emails` or
`selectedEmail`; for those the selected mailbox says nothing about the
message's owner, so guessing from it would send an own-account write to
a shared account whenever a shared folder happened to be open. An
unknown email keeps the previous behaviour instead.

Behaviour on own-account and unified-view messages is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 10:42:06 +02:00
Linus Rath 70383dea18 Merge pull request #796 from lucas-gaitzsch/fix/scheduled-send-polish
fix: polish scheduled send follow-ups
2026-08-12 18:12:28 +02:00
Lucas Gaitzsch 17f18e378e fix: polish scheduled send follow-ups 2026-08-12 17:46:51 +02:00
Ctaoist f1fdb24c8c feat: emit mailbox refresh hook 2026-08-12 22:08:09 +08:00
KazNIISA IT c516ba98d0 fix(email-store): route shared-folder management to the owner account
Folder management performed on a shared/group account was sent to the
logged-in user's primary JMAP account instead of the mailbox owner's
account. Subfolder creation therefore failed with invalidProperties for
parentId, root folders could be created silently in the personal account,
and rename, delete, role, and reorder targeted the wrong mailbox tree.

Shared mailboxes use namespaced store ids to avoid cross-account
collisions, while Mailbox/set requires the owner's accountId and the bare
server mailbox id. Resolve that mutation context centrally: recover the
mailbox originalId and owner account, use a directly connected owner
client when one exists, otherwise reach delegated/group accounts through
the active client with an explicit accountId.

Thread the account target through create, update, and delete JMAP methods
and all folder-management actions. Scope role replacement to the same
owner account, and expose explicit root-folder creation from shared
account headers without making personal root creation depend on the
currently selected folder.

Add unit coverage for JMAP request scoping, shared store routing, role
isolation, direct-client handling, and reorder. Add a Stalwart integration
scenario that creates root and nested group folders, renames them, and
deletes them through the UI.
2026-08-12 18:11:43 +05:00