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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
# 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 at creation. 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]
# reCAPTCHA v2 on registration and password reset, so no mail is ever sent
# before a human has been seen. Absent, or either key empty, disables it
# entirely — which is right for development and for a hub nobody can reach.
# See docs/MESHBAY_DESIGN.md §7.7.
site_key = ""
secret_key = ""
# Hostnames a solved captcha may have been solved on, checked against the one
# `siteverify` reports — what Google observed, not what the client claims.
#
# Leave empty while the reCAPTCHA key does its own origin check: it is then
# already done, one layer up. Set it when you turn that check off in the
# reCAPTCHA console, and the two go together — turning the console check off
# without setting this leaves no origin check anywhere.
#
# The desktop client is why it exists. Its interface ships inside the package
# and is served from `app://meshbay`, so the hostname Google sees is not this
# hub's and never can be; with the console check on, the widget shows
# "Invalid domain for site key" and nothing client-side reaches that decision.
#
# allowed_hosts = ["hub.example.org", "localhost"]
allowed_hosts = []
# A solve Google cannot attribute to a domain reports an *empty* hostname —
# the desktop client's `app://` origin does, and so does any other non-web
# client. No `allowed_hosts` entry matches that, hence a flag rather than a
# blank list entry.
#
# What it admits is every non-web client, not only this project's: a file://
# page or somebody else's Electron application look identical from here. The
# captcha still has to be solved per token; what is given up is the origin
# restriction for those clients. Leave it off unless you ship the desktop
# client. See docs/MESHBAY_DESIGN.md §7.7.
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
# 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
|