Registration closes itself once the instance has an owner
CI & Build / Build now, or wait for Android? (push) Successful in 2s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 11s
CI & Build / integration (push) Successful in 17s
CI & Build / Build & push image (push) Successful in 28s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 11s
CI & Build / integration (push) Successful in 17s
CI & Build / Build & push image (push) Successful in 28s
Operator: *"registration should be open only for the first user and they get granted admin privileges. then registration is closed."* The old shape had a window in it. The first account was always allowed and became admin; every account after that was gated by `allow_registration` — which defaulted to ON. So the door stayed open between "my account exists" and "I remembered to turn it off in Settings", and on a public host that gap is the entire exposure: it starts the moment DNS resolves and lasts until someone remembers. Now the door shuts as a CONSEQUENCE of the admin account existing, in the same transaction that creates it. Not "defaults closed" — that would still need the first person to get in somehow. There is no window to remember, because there is no window. Re-opening it is a deliberate act in Settings → Access: turn it on, have the person register, turn it off. Crude, and it is the only mechanism there is — **there is no invite system**, not even a stub. That is real work (a token table, admin create/revoke, a redemption flow, expiry) and is filed as later work rather than smuggled into a release. An integration test covers it, because it is the interaction between two writes in one transaction: first register → 201 and `is_admin: true`; the setting is then false; a second register → 403; re-open deliberately and a third → 201, not admin. **This does not retroactively close an instance that already has users.** The close fires on first-account creation, so a server whose admin predates this keeps whatever the setting was — which was on. `docs/public-hosting.md` now says so explicitly, and step 1 of the checklist is "check" rather than "do" for exactly that reason.
This commit is contained in:
+15
-1
@@ -18,7 +18,7 @@ from .ratelimit import (
|
||||
sign_in_by_address,
|
||||
)
|
||||
from .security import dummy_verify, generate_token, hash_password, hash_token, verify_password
|
||||
from .settings import get_setting
|
||||
from .settings import get_setting, set_settings
|
||||
|
||||
bp = Blueprint("auth", __name__, url_prefix="/api/auth")
|
||||
|
||||
@@ -197,6 +197,20 @@ async def register():
|
||||
is_admin=is_first,
|
||||
)
|
||||
db.add(user)
|
||||
if is_first:
|
||||
# Registration CLOSES the moment the instance has an owner.
|
||||
#
|
||||
# Not "defaults closed" — that would still need the first person to get in
|
||||
# somehow. Closed as a CONSEQUENCE of the admin account existing, which is
|
||||
# the only formulation with no open window in it. Leaving the setting on
|
||||
# meant the gap between "my account exists" and "I remembered to turn it
|
||||
# off in Settings" was wide open, and on a public host that gap is the
|
||||
# entire exposure — it starts the moment DNS resolves.
|
||||
#
|
||||
# An admin who wants a second person turns it back on in Settings → Access,
|
||||
# adds them, and turns it off. Crude until invites exist, but it is a
|
||||
# deliberate act rather than a default nobody chose.
|
||||
await set_settings(db, {"allow_registration": False})
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
session[SESSION_KEY] = str(user.id)
|
||||
|
||||
@@ -32,7 +32,8 @@ REGISTRY: list[SettingDef] = [
|
||||
"bool",
|
||||
True,
|
||||
"Allow new registrations",
|
||||
"When off, only existing users can sign in. The first account is always allowed.",
|
||||
"When off, only existing users can sign in. Closes itself once the first "
|
||||
"account exists — turn it back on only while you're adding someone.",
|
||||
"Access",
|
||||
),
|
||||
SettingDef(
|
||||
|
||||
Reference in New Issue
Block a user