aboutsummaryrefslogtreecommitdiffstats
path: root/packaging
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-12 13:48:09 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-12 16:36:54 +0200
commit98e27c8f251022320020716a9ef7a5b892ad2b61 (patch)
tree7cfb3f397c3f001768413676a739fe147743c824 /packaging
parente671b931fd594a39fc840916c81b5d4b1f1e3227 (diff)
downloadmeshbay-98e27c8f251022320020716a9ef7a5b892ad2b61.tar.gz
feat(hub): the mail bounds are settings, with a panel to change them
They were constants in two modules, so an operator could not touch them without editing code and redeploying — and the hour a budget runs out is not when anyone wants to do that. `[mail]` in hub.toml carries the defaults; the live values live in `hub_settings`, read at each use. A missing row falls back to what the configuration file says, so an instance that never opens the panel behaves as its file describes. The panel sends only what changed, the hub clamps each value to a stated range and refuses a key it does not know, and the response is what gets rendered — so a clamped value is never shown as stored. `GET /v1/admin/mail` is the other half. There was no way to see any of this: a refusal was a line in the journal, so an instance that had stopped sending sign-up codes looked, from the panel, exactly like one with no sign-ups. It reports the hour's use, what is left for sign-ups, and what is left for recovery — the difference between those two being the reserved share made visible. Labels in all ten catalogues. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T4YmK41VsEURWFdop4EEeT
Diffstat (limited to 'packaging')
-rw-r--r--packaging/conf/hub.toml.example47
1 files changed, 47 insertions, 0 deletions
diff --git a/packaging/conf/hub.toml.example b/packaging/conf/hub.toml.example
index 7f5ac8f..2687a87 100644
--- a/packaging/conf/hub.toml.example
+++ b/packaging/conf/hub.toml.example
@@ -81,3 +81,50 @@ allowed_hosts = []
# restriction for those clients. Leave it off unless you ship the desktop
# client. See docs/captcha.md §6.
allow_unattributed_host = false
+
+
+[mail]
+# What this hub will send, and how much of it. These are the **defaults**: an
+# administrator changes them from the Settings panel, and what they set is
+# stored in the database. A value here is what a missing setting falls back
+# to, so an instance that never touches the panel behaves as this file says.
+#
+# Why the bounds exist at all: the hub reaches its local Postfix with no
+# authentication, and three API paths reach the hub — two of them at an
+# address the caller types. Unbounded, that is an open relay wearing this
+# instance's reputation.
+
+# Everything this instance sends, in one hour. The one bound registration
+# being open cannot buy past: per-account and per-IP limits bound a caller,
+# and a caller is something an attacker makes more of.
+hourly_budget = 200
+
+# Of that budget, the share kept back for the two messages a person is
+# actively waiting on — a passphrase reset and a group invitation. Without it
+# a flood of sign-ups spends the hour and locks out the people who need a
+# message to arrive. Sign-ups and address changes may spend
+# `hourly_budget - hourly_reserved_for_recovery`.
+hourly_reserved_for_recovery = 50
+
+# Per recipient, across every purpose, account and endpoint. This is the bound
+# that describes what a person being flooded actually receives, and the only
+# one that does.
+destination_daily_cap = 10
+destination_cooldown_seconds = 120
+
+# Between two sign-up codes to one pending account. Reaching this branch needs
+# no token and no captcha — the caller chose the username and address a moment
+# earlier — so it is the widest of the three doors and wants its own delay.
+verification_resend_cooldown = 120
+
+# Between two passphrase-reset codes for one account, whoever asks. The code
+# itself lives an hour, so this stays far below its lifetime: someone who did
+# not receive the first waits, rather than being unable to use the second.
+reset_cooldown = 300
+
+# Before an account may propose a *different* address. Long, because this is
+# the only path where a signed-in account chooses who receives a message.
+# Asking again for the address already pending is exempt — it reaches no new
+# recipient, and without the exemption a typo would lock the account out of
+# correcting it for two days.
+email_change_cooldown = 172800 # 48 hours