diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/config.py')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/config.py | 46 |
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) |