diff options
Diffstat (limited to 'packages/meshbay-hub/src')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/api/federation.py | 49 | ||||
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/api/hub.py | 7 |
2 files changed, 55 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/federation.py b/packages/meshbay-hub/src/meshbay_hub/api/federation.py index 327102e..9e252c6 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/federation.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/federation.py @@ -40,7 +40,54 @@ from meshbay_hub.db.models import FederatedGroup, Group, HubPeer, User log = logging.getLogger(__name__) -router = APIRouter(prefix="/mhp", tags=["federation"]) +# ── Federation is closed ────────────────────────────────────────────────────── +# +# **Every MHP endpoint refuses, and this flag is the only thing that decides it.** +# +# Not because the protocol is wrong, but because nothing has ever run it. Two +# hubs have never completed a single authenticated request between them: until +# 2026-09-12 `_issue_mhp_token` bound the hub's signing key at import — before +# `load_hub_keypair` runs — so this hub signed with `None` and called itself +# `meshbay.org` whatever it was named, while the verifier named no audience for +# the `aud` the issuer sets, which PyJWT refuses outright (**AV14**). Both were +# found by reading, and they were found because a test had written down, in its +# own words, that the real issuer "cannot be used from a test" and had signed +# its own tokens instead. What else is in here of that shape is not known, and +# the way to know is to stand up a second hub — not to leave the door open +# meanwhile. +# +# The surface being closed is worth naming: four of the six routes carry no +# authentication of their own (the MHP token *is* the authentication), two of +# those write — a directory push and a revocation — and every one of them is +# reachable by anyone who can reach the hub. +# +# **A constant and not a setting, deliberately.** A row in `hub_settings` and a +# switch in the admin panel would invite an operator to turn on a feature that +# has never worked between two machines. This takes an edit and a deploy, by +# somebody who has read this. The refusal is a stated 503 rather than a 404 +# because a peer hub deserves a reason it can act on, which is §5.6's rule for +# the wire one level up. +# +# **To re-open it:** set this True, stand up a second hub, and run the exchange +# both ways. `test_federation.py` covers the protocol and proves nothing about +# two machines; §15.2 carries federation as not built until that has happened. +FEDERATION_ENABLED = False + + +def _federation_open() -> None: + """Refuse every route on this router while federation is closed. + + A router dependency rather than a line in each handler: it covers the six + routes that exist and every one anybody adds later. A gate you have to + remember to write is the shape **C6** is the standing lesson about. + """ + if not FEDERATION_ENABLED: + raise HTTPException(status_code=503, + detail="Federation is not enabled on this hub") + + +router = APIRouter(prefix="/mhp", tags=["federation"], + dependencies=[Depends(_federation_open)]) # One push may not dump the world, and one peer may not fill the table. MAX_FEDERATED_GROUPS_PER_PUSH = 500 diff --git a/packages/meshbay-hub/src/meshbay_hub/api/hub.py b/packages/meshbay-hub/src/meshbay_hub/api/hub.py index a48313d..5a3eb4b 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/hub.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/hub.py @@ -5,6 +5,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from meshbay_common import MNP_VERSION, MHP_VERSION from meshbay_hub import __version__, hub_settings +from meshbay_hub.api import federation from meshbay_hub.auth import hub_public_key_pem from meshbay_hub.config import HubConfig from meshbay_hub.db.engine import get_db, get_engine @@ -32,6 +33,12 @@ async def hub_info(db: AsyncSession = Depends(get_db)): # reachable before the group list loads. The hub enforces it regardless # of what any client does with this flag. "allow_public_groups": await hub_settings.public_groups_allowed(db), + # Stated, because `mhp_version` above is otherwise a claim this hub does + # not honour: every MHP route refuses while federation is closed (see + # `api/federation.py`). A peer finds out at `/mhp/info` either way, + # which answers 503 — this is the same answer somewhere an operator can + # read it without opening the source. + "federation": federation.FEDERATION_ENABLED, "captcha_site_key": _cfg.captcha.site_key if _cfg and _cfg.captcha.enabled else "", } |