aboutsummaryrefslogtreecommitdiffstats
path: root/packaging/conf/hub.toml.example
blob: 8e249a53a2c2984d96fd22ad9dfc8ee0d6c77ffe (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# MeshBay Hub — example configuration.
#
# Copy to /etc/meshbay/hub.toml and edit. The package deliberately does not
# install a working config: it would either ship a placeholder secret that
# somebody runs in production, or overwrite yours on upgrade.
#
# Read from the first of these that exists:
#   /etc/meshbay/hub.toml          <- where a packaged hub looks
#   ~/.config/meshbay/hub.toml     <- a development hub, run as yourself
#
# Every value below can also come from the environment, which is what the
# systemd unit's EnvironmentFile (/etc/meshbay/hub.env) is for. Secrets belong
# there rather than in an `Environment=` line: `systemctl cat` shows a unit to
# any user on the machine.

[hub]
# This hub's identity, as members and nodes know it. Changing it after anyone
# has joined invalidates what they trust.
id = "hub.example.org"

# Ed25519 private key. Generate with:
#   meshbay-hub --help          (see the key subcommands)
# The packaged service runs as `meshbay`, so:
#   chown meshbay:meshbay /etc/meshbay/hub_private.pem && chmod 600 it
private_key_path = "/etc/meshbay/hub_private.pem"

# Accounts granted the admin role. Each name is pinned to the first active
# account seen holding it, so a name freed by an account deletion and registered
# again by somebody else is not an admin. To hand a listed name to a new account:
# remove it, restart, list it again, restart. Everything else is set in the UI.
admin_usernames = []

[database]
# PostgreSQL in production. The default without this key is an in-memory
# SQLite, which is a test fixture and loses everything on restart.
# Prefer MESHBAY_DATABASE_URL in /etc/meshbay/hub.env — it carries a password.
url = "postgresql+asyncpg://meshbay:CHANGEME@localhost/meshbay_hub"

[server]
# Loopback: TLS is Caddy's job, and the hub should not be reachable directly.
host    = "127.0.0.1"
port    = 8000
workers = 1

[jwt]
# The access token is not the session — the refresh token is, and the SPA
# renews against it long before this runs out. What this bounds is a token
# that leaks.
access_token_ttl  = 14400      # 4 h
# How long a session lasts (idle, maximum, browser sign-out) is set in the
# admin panel, not here.

[captcha]
# A proof of work on registration and password reset, issued and checked by
# this hub alone: no third party, no cookie, nothing for a person to do — the
# page solves it while the form is filled in. On by default; turn it off only
# for development or a hub nobody can reach. See docs/MESHBAY_DESIGN.md §7.7.
enabled = true

# The work asked of the page: half of this many SHA-256 on average, all of it
# at worst. The default takes about three seconds on a slow phone.
max_number = 500000


[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. The daily cap counts every purpose, account and endpoint; the
# cooldown is shared within two families — invitations, and an account's own
# steps (sign-up code, reset, address change) — so an invitation does not hold
# back the sign-up code its invitee asks for a minute later. 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

# Invitation-link mails one account may have this hub send in a day. A link
# goes to an address that may have no account here, at the request of anyone
# who owns a group — so, unlike the others, this one counts the sender.
invite_link_daily_cap = 10