diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-30 12:37:31 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-30 12:37:31 +0200 |
| commit | 8a4651e9d223de856ff085b329801998f95db138 (patch) | |
| tree | 6153ffaf46030613fa9024e85b9890c7fa979154 /packages/meshbay-hub/src/meshbay_hub/app.py | |
| parent | 1684fcb64531eaf2e9bb8567ba4bdcbada6469d8 (diff) | |
| download | meshbay-8a4651e9d223de856ff085b329801998f95db138.tar.gz | |
fix(hub): the admin allow-list grants an account, not a username
Each name in admin_usernames is pinned to the first active account seen
holding it (admin_pins), so a name freed by a deletion grants nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/app.py')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/app.py | 51 |
1 files changed, 40 insertions, 11 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/app.py b/packages/meshbay-hub/src/meshbay_hub/app.py index 16a9d4a..ca05fd8 100644 --- a/packages/meshbay-hub/src/meshbay_hub/app.py +++ b/packages/meshbay-hub/src/meshbay_hub/app.py @@ -42,22 +42,52 @@ from meshbay_hub.config import HubConfig from meshbay_hub.db.engine import close_db, init_db -async def _sync_admin_roles(admin_usernames: list[str]) -> None: - """Ensure config-listed admin usernames have role='admin' in the DB.""" +async def _pin_config_admins(admin_usernames: list[str]) -> None: + """Pin each allow-listed admin name to the account holding it, once. + + A name already pinned keeps its account whoever holds the name now, which is + what stops a released name from being registered into the admin role. Pins + of names no longer listed are dropped: removing a name, restarting, then + listing it again is how an operator hands it to a new account. + """ + import logging + from sqlalchemy import select + from meshbay_hub.api.deps import set_admin_pins from meshbay_hub.db.engine import get_session_factory - from meshbay_hub.db.models import User + from meshbay_hub.db.models import AdminPin, User + log = logging.getLogger(__name__) + listed = set(admin_usernames) factory = get_session_factory() async with factory() as session: - result = await session.execute( - select(User).where(User.username.in_(admin_usernames)) - ) - for user in result.scalars().all(): - if user.role != "admin": - user.role = "admin" + pins: dict[str, str] = {} + for pin in (await session.execute(select(AdminPin))).scalars().all(): + if pin.username in listed: + pins[pin.username] = pin.user_id + else: + log.info("Admin allow-list: %s is no longer listed, its pin is dropped", + pin.username) + await session.delete(pin) + + unpinned = listed - pins.keys() + if unpinned: + holders = (await session.execute(select(User).where( + User.username.in_(unpinned), User.status == "active"))).scalars().all() + for user in holders: + session.add(AdminPin(username=user.username, user_id=user.id)) + pins[user.username] = user.id + log.info("Admin allow-list: %s pinned to account %s", + user.username, user.id[:8]) + + if pins: + for user in (await session.execute(select(User).where( + User.id.in_(pins.values()), User.status == "active"))).scalars().all(): + if user.role != "admin": + user.role = "admin" await session.commit() + set_admin_pins(pins) async def _backfill_email_hashes() -> None: @@ -123,8 +153,7 @@ def create_app(cfg: HubConfig | None = None) -> FastAPI: # back to. _hub_settings.set_mail_defaults(cfg.mail) - if cfg.identity.admin_usernames: - await _sync_admin_roles(cfg.identity.admin_usernames) + await _pin_config_admins(cfg.identity.admin_usernames) from meshbay_hub.db.engine import get_session_factory from meshbay_hub.tasks.cleanup import cleanup_loop |