Commit Graph
206 Commits
Author SHA1 Message Date
Linus Rath e66383de5b feat: let admins set an address for phishing reports 2026-11-01 04:33:38 +01:00
Linus Rath 123018f322 feat: add the policy sections and admin pages for the enterprise features 2026-11-01 04:20:02 +01:00
Linus Rath d781e8762e Merge branch 'feat/multi-instance' 2026-11-01 01:43:15 +01:00
Linus Rath eb56ea441f fix: list the User-Agent contact link as a non-request host 2026-11-01 01:22:17 +01:00
Linus Rath ac29a94fc3 fix: cap the plugin packages kept for approval review and sweep broken kept-version folders #1066 2026-11-01 01:04:51 +01:00
Linus Rath dcecd50b7f fix: keep up to 256 rendered branding images per kind, enough for many domains #1066 2026-11-01 01:03:33 +01:00
Linus Rath 38bc06f2d3 fix: answer 503 for plugin lists and marketplace installs while the shared store is down #1066
directory-keys-shared.test.ts: the store-down case now expects the store error instead of an 'invalid' signature, since a store outage no longer counts as a bad signature.
2026-11-01 01:03:13 +01:00
Linus Rath d78ce02f78 fix: refuse a plugin install that can't hold the registry lock and a bundle that doesn't match its entry #1066 2026-11-01 01:01:19 +01:00
Linus Rath 750f3114c1 fix: keep an old plugin's bundle hash when the shared store hiccups while it is fixed #1066 2026-11-01 00:58:33 +01:00
Linus Rath b39e3e27ac fix: cache a plugin signature only when it verifies for the cached bundle #1066 2026-11-01 00:57:21 +01:00
Linus Rath d9f4bdd9be fix: keep serving a plugin unsigned when its first signature can't be stored 2026-11-01 00:55:51 +01:00
Linus Rath 256855b294 fix: refuse to sign plugins on an instance whose signing key differs from the others #1066 2026-11-01 00:54:57 +01:00
Linus Rath 5efccb65e0 fix: show a re-uploaded logo or icon at once instead of after a restart 2026-11-01 00:24:07 +01:00
Linus Rath fc7d5617c2 feat: serve uploaded branding from the shared store on every instance #1066 2026-11-01 00:20:24 +01:00
Linus Rath 51e7999e3c feat: sign plugin bundles with one key on every instance #1066 2026-11-01 00:16:42 +01:00
Linus Rath 5e4b6465d1 Merge branch 'feat/multi-instance' into feat/multi-instance-ext 2026-11-01 00:13:14 +01:00
Linus Rath e3936287fd feat: share plugin approvals and plugin settings between instances #1066 2026-11-01 00:13:09 +01:00
Linus Rath e75834314d fix: show plugin approval requests and decisions to every part of the server at once 2026-11-01 00:13:08 +01:00
Linus Rath 2944d2797f fix: keep every key when several of a plugin's settings are saved at once 2026-11-01 00:12:26 +01:00
Linus Rath 5e73fcbcc1 feat: append lines in batches to the shared log and clear it #1066 2026-11-01 00:08:39 +01:00
Linus Rath 8b40cfca90 fix: update the plugin frame origins in the CSP right after an install, also on other instances #1066 2026-11-01 00:06:52 +01:00
Linus Rath ff08fe50fe fix: say when an admin sign-out or password change couldn't be stored instead of answering ok or wrong password #1066 2026-11-01 00:05:15 +01:00
Linus Rath bcb333d40c feat: share installed plugins and themes between instances #1066 2026-11-01 00:04:15 +01:00
Linus Rath 740a892590 fix: answer 503 for admin and setup saves while the shared store is down #1066
Config, policy, branding, OAuth setup, password and setup wizard writes
used to answer 500 when the shared store couldn't be reached, and the
wizard's token check failed outside any handler. They answer 503 with
Retry-After now, through the helper that already turned a read-only
refusal into 409. The step-session-secret test's wizard mock gains
wizardSessionRefusal, which the setup routes now call.
2026-11-01 00:02:57 +01:00
Linus Rath 46f47b6b8f fix: answer 409 instead of 500 for every admin save a read-only config refuses #1249 2026-10-31 23:59:00 +01:00
Linus Rath 68d64f5018 fix: pair the policy with its own version and keep config reloads and saves from undoing each other #1066
Fixes from the review of the admin part:
- GET /api/admin/policy reads the policy and its version together; an
  identity check waiting on the store let another replica's change pair
  the old policy with the new ETag, and a save with it then won.
- A policy save answers with the stored policy and its ETag, and the
  policy, organisation, plugins and themes pages continue from that
  instead of reading it back from an instance that may not have it yet.
- A stored policy that doesn't parse keeps its version, so a save can
  replace it instead of answering 409 forever.
- A reload of another replica's change throws when this replica's writes
  kept landing while it read, so the change poller tries again; a write
  whose reload raced it reads the document again instead of applying its
  older result.
- Dropping a deleted plugin from the policy rereads policy.json first in
  local mode, as every other write does.
- New updateAdminConfig(key, change) builds a value on the stored one.

The policy route's test mocks gain getPolicyWithVersion.
2026-10-31 23:55:17 +01:00
Linus Rath 3f9970e877 fix: refuse a marketplace install when the recorded directory keyring dates can't be read 2026-10-31 23:48:22 +01:00
Linus Rath c0c972a57f feat: refuse a replayed extension directory keyring on every instance #1066 2026-10-31 23:46:56 +01:00
Linus Rath 698b881f1f refactor: use the shared store's own outage check in the admin code #1066 2026-10-31 23:45:53 +01:00
Linus Rath e63ae40c61 fix: keep one plugin signing key when several parts of the server create it at once
The registry signature test now drops the cached key before each test: the key is cached once per process on globalThis, so resetting the modules no longer clears it.
2026-10-31 23:44:43 +01:00
Linus Rath ade95013f8 feat: keep one admin audit log for every instance #1066 2026-10-31 23:23:00 +01:00
Linus Rath 291b6364c9 feat: count admin login attempts across every instance and refuse sign-in while the shared store is down #1066
The three admin login limiters (5 per address and 50 overall in one
reservation, 5 per known device) keep their numbers and still don't count
refused attempts, but live in the store: memory in this process as before,
or the shared store with several replicas. checkRateLimit and
checkDeviceRateLimit are async now; the login route awaits them and
answers 503 "Sign-in is temporarily unavailable" when the store can't be
reached. The two limiter tests gain await and reset the store's counters,
which vi.resetModules no longer clears.

The admin code tells a store outage by the error's name, since the store
may come from another bundle's copy of the module.
2026-10-31 23:18:40 +01:00
Linus Rath ee5a107649 fix: answer 409 for a stale policy save also when another bundle made the config manager #1066 2026-10-31 23:17:36 +01:00
Linus Rath a048689179 feat: check admin sign-outs and password changes against the shared store on every instance #1066 2026-10-31 23:12:23 +01:00
Linus Rath cde02f5a48 fix: refuse the old admin password everywhere right after it was changed 2026-10-31 23:07:11 +01:00
Linus Rath 7a482a4d0e feat: share admin config and policy between instances and spread changes within seconds #1066 2026-10-31 23:03:57 +01:00
Linus Rath 25408b2ac2 fix: refuse a policy save from a page opened before someone else saved #1066
The admin pages save the whole policy from the draft they loaded. A
save from a page opened before another tab's or another admin's save,
or before a deleted plugin was dropped from the policy, used to put the
older policy back. GET /api/admin/policy now sends the policy version as
an ETag; the policy, organisation, plugins and themes pages send it back
as If-Match, and a save based on an older version answers 409 and keeps
the draft on the page. A PUT without If-Match replaces the policy as
before. Deleting a plugin or theme changes only its id in the stored
policy, and a policy import compares against what it replaces.

Test mocks of the config manager gain getPolicyVersion and updatePolicy,
which the route and the extension cleanup now call.
2026-10-31 22:57:57 +01:00
Linus Rath d23a2869d1 Merge remote-tracking branch 'origin/main' into fix/found-fix-server
# Conflicts:
#	Dockerfile
#	docker-compose.yml
2026-10-31 18:17:39 +01:00
Linus Rath 88351b2333 fix: point the outgoing hosts list at the current usage stats and updates pages 2026-10-31 18:02:01 +01:00
Linus Rath ddc24e4fc7 fix: let a browser an admin signed in from before past the shared admin login limit 2026-10-31 17:55:29 +01:00
Linus Rath e15c335bec fix: pick up admin changes saved by another replica without a restart 2026-10-31 17:54:40 +01:00
Linus Rath 072a4219be fix: drop the connector address setting that nothing used 2026-10-31 17:49:54 +01:00
Linus Rath dcc785690c fix: apply the log level and format set in the setup wizard or admin console 2026-10-31 17:45:06 +01:00
Linus Rath d3fecae85f feat: hide the contacts directory when the server refuses it, and let users or admins turn it off #1457 2026-10-31 16:22:24 +01:00
Linus Rath 5931cd1678 feat: send an oauth resource indicator when one is configured #1461 2026-10-31 16:14:35 +01:00
Linus Rath dee2c07ce9 Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-10-30 11:05:11 +01:00
Linus Rath 8098039d69 feat: show booking cards only for mail from verified senders 2026-10-30 04:41:29 +01:00
Linus Rath 76b0c2fba2 feat: read schema.org booking markup from mail bodies 2026-10-30 04:27:25 +01:00
Linus Rath 66351e0d31 feat: show admins the changelog of every plugin version an update brings or a rollback undoes 2026-10-30 02:02:40 +01:00
Linus Rath 2c19cfe8d9 fix: bundle dev plugins whose dependencies only declare main or touch node builtins 2026-10-30 00:31:52 +01:00