aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/config.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/config.py')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/config.py46
1 files changed, 46 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/config.py b/packages/meshbay-hub/src/meshbay_hub/config.py
index e61e69e..827538c 100644
--- a/packages/meshbay-hub/src/meshbay_hub/config.py
+++ b/packages/meshbay-hub/src/meshbay_hub/config.py
@@ -93,12 +93,50 @@ class CaptchaConfig:
@dataclass
+class MailConfig:
+ """What the hub will send, and how much of it.
+
+ Defaults, not the live values: an admin changes these from the panel and
+ the change is stored in `hub_settings`, so what is written here is the
+ instance's starting point and what it falls back to if a row is missing.
+
+ The two that matter are per **recipient** and per **instance**. A limit
+ counted per account or per IP bounds a caller, and registration is open, so
+ a caller is something an attacker buys more of.
+ """
+
+ # Between two messages to one address, across every purpose and account.
+ destination_cooldown_seconds: int = 120
+ # And how many that address may receive in a day.
+ destination_daily_cap: int = 10
+ # Everything this instance sends, per hour.
+ hourly_budget: int = 200
+ # Of that budget, the share kept back for the two purposes a person is
+ # waiting on: a passphrase reset and a group invitation. Without it a flood
+ # of sign-ups spends the hour's allowance and locks out the people who
+ # actually need a message to arrive.
+ hourly_reserved_for_recovery: int = 50
+
+ # Between two sign-up codes to one pending account.
+ verification_resend_cooldown: int = 120
+ # Between two passphrase-reset codes for one account, whoever asks. The
+ # code lives an hour, so this stays far below its lifetime.
+ reset_cooldown: int = 300
+ # Between two *different* addresses proposed by one account. Re-asking for
+ # a code for the address already pending is exempt — it reaches no new
+ # recipient, and without the exemption a typo locks the account out for the
+ # whole window.
+ email_change_cooldown: int = 172800 # 48 hours
+
+
+@dataclass
class HubConfig:
db: DatabaseConfig = field(default_factory=DatabaseConfig)
server: ServerConfig = field(default_factory=ServerConfig)
identity: HubIdentityConfig = field(default_factory=HubIdentityConfig)
jwt: JWTConfig = field(default_factory=JWTConfig)
captcha: CaptchaConfig = field(default_factory=CaptchaConfig)
+ mail: MailConfig = field(default_factory=MailConfig)
def load_config(path: Path | None = None) -> HubConfig:
@@ -124,6 +162,14 @@ def load_config(path: Path | None = None) -> HubConfig:
if jwt := raw.get("jwt", {}):
cfg.jwt.access_token_ttl = jwt.get("access_token_ttl", cfg.jwt.access_token_ttl)
cfg.jwt.refresh_token_ttl = jwt.get("refresh_token_ttl", cfg.jwt.refresh_token_ttl)
+ if ml := raw.get("mail", {}):
+ for name in (
+ "destination_cooldown_seconds", "destination_daily_cap",
+ "hourly_budget", "hourly_reserved_for_recovery",
+ "verification_resend_cooldown", "reset_cooldown",
+ "email_change_cooldown",
+ ):
+ setattr(cfg.mail, name, ml.get(name, getattr(cfg.mail, name)))
if cap := raw.get("captcha", {}):
cfg.captcha.site_key = cap.get("site_key", cfg.captcha.site_key)
cfg.captcha.secret_key = cap.get("secret_key", cfg.captcha.secret_key)