diff --git a/containers/stoat/scripts/stoat_auth.sh b/containers/stoat/scripts/stoat_auth.sh index b649522..0aa3caa 100644 --- a/containers/stoat/scripts/stoat_auth.sh +++ b/containers/stoat/scripts/stoat_auth.sh @@ -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)" diff --git a/containers/stoat/scripts/stoat_install_hooks.sh b/containers/stoat/scripts/stoat_install_hooks.sh index badc2ff..2dec0e1 100644 --- a/containers/stoat/scripts/stoat_install_hooks.sh +++ b/containers/stoat/scripts/stoat_install_hooks.sh @@ -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" <