Close Stoat registration by default, behind CFG_STOAT_INVITE_ONLY
Provisioning the owner account did not stop anyone else signing up. Stoat ships open, with no captcha and no email verification, so a reachable instance still accepted walk-in registrations. Made a config option rather than hardcoded, because running an open community server is legitimate — but defaulting CLOSED, which is the opposite of the other registration toggle in the tree. Vaultwarden's SIGNUPS_ALLOWED defaults true for a reason that does not apply here: it has to let you register to get in at all, whereas Stoat's owner account is now created for you. Matrix's ENABLE_REGISTRATION already defaults false for the same shape of app. The section name is load-bearing and not guessable. invite_only under [features] or [api.security.authifier] is accepted in silence and does nothing — the API keeps reporting invite_only=false — so it goes under [api.registration], which was found by testing all three against a running instance. Anything but an explicit "false" closes registration, so a blank or misspelled value fails safe. Closing it broke LibrePortal's own tooling, which is the part worth noting: the API answers MissingInvite to create_account too. So account creation now mints a single-use invite and retries when it sees that. Reactive rather than reading the config, so it follows the instance's actual state — someone who edits Revolt.toml by hand gets the same behaviour. Stoat stamps the invite used/claimed_by as it consumes it, and a failed create deletes it, so no reusable invite is left behind; verified that the collection holds zero unused invites after two creations. Verified end to end: a fresh install reports invite_only=true, a walk-in signup is refused with MissingInvite, and the Create User Account tool still succeeds. Flipping the config to false and reinstalling flips the API to open, and the provisioning guard correctly reports "already has accounts" instead of trying to claim a second owner. The value is baked into Revolt.toml at install, so changing it needs a reinstall rather than a reload — now said in the config comment. Unrelated flake seen once during testing and not reproduced: an install left stoat-rabbit with no IP row, so the compose died on a literal IP_DATA_3. A straight uninstall/reinstall allocated all 16 cleanly. Untouched here — it is in the IP allocator, not this change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
a1290b47a3
commit
29fa8e8a60
@ -65,6 +65,34 @@ _stoatCreateAccount() {
|
||||
body="{\"email\":$(_stoatJson "$email"),\"password\":$(_stoatJson "$pass")}"
|
||||
out=$(runFileOp curl -sS --max-time 20 -X POST "${api}/auth/account/create" \
|
||||
-H 'Content-Type: application/json' -d "$body" 2>&1)
|
||||
|
||||
# On a closed instance the API answers MissingInvite. Rather than refuse, mint
|
||||
# one: an admin creating an account here IS the authorisation, and requiring
|
||||
# them to go and generate an invite by hand first would make CFG_STOAT_INVITE_ONLY
|
||||
# a switch that breaks LibrePortal's own tooling.
|
||||
#
|
||||
# Reactive rather than reading the config, so this follows the instance's ACTUAL
|
||||
# state — someone who edits Revolt.toml by hand, or flips it after install, gets
|
||||
# the same behaviour without LibrePortal having to be told.
|
||||
#
|
||||
# The invite is single-use and consumed by this create: Stoat stamps it
|
||||
# used/claimed_by, so it cannot become a spare key left under the mat.
|
||||
if [[ "$out" == *MissingInvite* ]]; then
|
||||
local code
|
||||
code=$(_stoatMongo 'const c = "LP" + Math.random().toString(36).slice(2, 10).toUpperCase();
|
||||
db.account_invites.insertOne({_id: c, used: false});
|
||||
print(c);' | tr -dc 'A-Z0-9')
|
||||
if [[ -z "$code" ]]; then
|
||||
isError "Stoat is invite-only and an invite could not be created."
|
||||
return 1
|
||||
fi
|
||||
body="{\"email\":$(_stoatJson "$email"),\"password\":$(_stoatJson "$pass"),\"invite\":$(_stoatJson "$code")}"
|
||||
out=$(runFileOp curl -sS --max-time 20 -X POST "${api}/auth/account/create" \
|
||||
-H 'Content-Type: application/json' -d "$body" 2>&1)
|
||||
# Do not leave an unused invite behind if the create still failed.
|
||||
[[ -n "$out" ]] && _stoatMongo "db.account_invites.deleteOne({_id: $(_stoatJson "$code"), used: false})" >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
# A successful create returns 204 with no body; anything printed is an error.
|
||||
if [[ -n "$out" && "$out" != *'"result"'* ]]; then
|
||||
isError "Stoat account create failed: $(printf '%s' "$out" | tr -d '\n' | head -c 200)"
|
||||
|
||||
@ -103,6 +103,11 @@ EOF
|
||||
|
||||
printf '{"api":"%s/api"}' "$base" | runFileWrite "$app_dir/stoat.json"
|
||||
|
||||
# Anything but an explicit "false" closes registration: a blank or misspelled
|
||||
# value should fail safe, not silently open the instance to the internet.
|
||||
local invite_only="true"
|
||||
[[ "${CFG_STOAT_INVITE_ONLY:-true}" == "false" ]] && invite_only="false"
|
||||
|
||||
runFileWrite "$app_dir/Revolt.toml" <<EOF
|
||||
# Generated by LibrePortal at install time. Secrets live in secrets.env, not
|
||||
# here. Reinstalling the app rewrites this file — put custom configuration in a
|
||||
@ -133,6 +138,14 @@ host = "rabbit"
|
||||
port = 5672
|
||||
username = "stoat"
|
||||
password = "${rabbit_pass}"
|
||||
|
||||
# Whether a stranger who reaches this URL can sign themselves up. Upstream
|
||||
# defaults to open, with no captcha and no email verification to slow it down.
|
||||
# The section name is load-bearing and not guessable: invite_only under
|
||||
# [features] or [api.security.authifier] is accepted silently and does nothing —
|
||||
# the API keeps reporting invite_only=false — so only [api.registration] here.
|
||||
[api.registration]
|
||||
invite_only = ${invite_only}
|
||||
EOF
|
||||
|
||||
if [[ -n "$video_enabled" ]]; then
|
||||
@ -417,9 +430,15 @@ stoat_install_post()
|
||||
# account does not close registration. Stoat ships invite_only=false with
|
||||
# no captcha and no email verification, so anyone who can reach the URL
|
||||
# can still make their own account.
|
||||
echo " Registration is still OPEN — anyone who can reach that URL can"
|
||||
echo " sign up. Keep the port off the internet, or set invite_only in"
|
||||
echo " Revolt.toml, if that is not what you want."
|
||||
if [[ "${CFG_STOAT_INVITE_ONLY:-true}" == "false" ]]; then
|
||||
echo " Registration is OPEN — anyone who can reach that URL can sign"
|
||||
echo " up, with no captcha and no email check. Set"
|
||||
echo " CFG_STOAT_INVITE_ONLY=true to close it."
|
||||
else
|
||||
echo " Registration is closed. Add people with the Create User"
|
||||
echo " Account tool on this app's Tools tab, which issues its own"
|
||||
echo " single-use invite."
|
||||
fi
|
||||
else
|
||||
echo " Open ${base} and create an account."
|
||||
echo ""
|
||||
|
||||
@ -80,6 +80,21 @@ CFG_STOAT_MINIO_PASSWORD_1=RANDOMIZEDPASSWORD2
|
||||
CFG_STOAT_ADMIN_EMAIL=admin@stoat.local
|
||||
CFG_STOAT_ADMIN_USERNAME=administrator
|
||||
CFG_STOAT_ADMIN_PASSWORD_1=RANDOMIZEDPASSWORD3
|
||||
# INVITE_ONLY = if true, nobody can sign up from the web UI; accounts are added
|
||||
# with the Create User Account tool.
|
||||
#
|
||||
# Defaults CLOSED, unlike Vaultwarden's SIGNUPS_ALLOWED=true, because the reason
|
||||
# that one is open does not apply here: Vaultwarden has to let you register to
|
||||
# get in at all, whereas the owner account above is created for you. Matrix takes
|
||||
# the same closed default for the same shape of app — an open chat server with no
|
||||
# captcha and no email verification is a standing invitation once it is reachable.
|
||||
# Closed is not a dead end: the Create User Account tool mints its own single-use
|
||||
# invite, so it keeps working either way.
|
||||
#
|
||||
# Written into Revolt.toml at install, so changing it needs an Install/Reinstall
|
||||
# to take effect — a Reload only restarts containers and leaves the generated
|
||||
# config as it was.
|
||||
CFG_STOAT_INVITE_ONLY=true
|
||||
#
|
||||
# =============================================================================
|
||||
# METADATA
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user