diff options
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/ops.py | 35 |
1 files changed, 28 insertions, 7 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/ops.py b/packages/meshbay-node/src/meshbay_node/ops.py index 6d53edb..7a8e8ac 100644 --- a/packages/meshbay-node/src/meshbay_node/ops.py +++ b/packages/meshbay-node/src/meshbay_node/ops.py @@ -180,6 +180,34 @@ async def create_invite(state: dict, group_id: str, username: str, *, raise OpError(f"Unknown user {username!r}: {e}", status=404) from e user_id = account["user_id"] + # Hub membership first, and fatal if it fails. + # + # `/v1/groups/mine` joins `GroupMember`, so someone who was never registered + # does not see the group at all and can never redeem the code. Creating the + # invite first and tolerating a failed registration — which is what this did + # — hands the operator a code that cannot work, and says nothing. Worse, an + # unreachable hub raised *after* the roster write, leaving a valid code + # nobody was ever given; every retry left another. + # + # Registering before the roster write means a failure costs nothing: no code + # exists to be orphaned. A membership row without an invite is harmless — + # without the code there is still no group key. + # + # The endpoint is idempotent (`if not mem: db.add(...)`, no 409), so the SPA + # registering the same membership again right after `createInvite` + # (group-settings.js) costs nothing either. + # + # Skipped only when there is no username to register with: the MNP path + # allows an empty one (`username || ''` in transport.js), and there the SPA + # is the one that registers. + if username: + try: + await _hub(state).add_group_member(group_id, username) + except Exception as e: + raise OpError( + f"Could not register {username!r} on the hub, so the invite " + f"could not be redeemed: {e}", status=502) from e + config = state.get("config") ttl = (config.node.invite_ttl_hours if config else 168) * 3600 code = await roster.create_invite( @@ -191,13 +219,6 @@ async def create_invite(state: dict, group_id: str, username: str, *, username=username, ) - hub = _hub(state) - try: - await hub.add_group_member(group_id, username) - except Exception: - log.warning("Could not register %s as hub member of %s", - username, group_id[:8]) - invites = await roster.list_invites() expires = next((i["expires_at"] for i in invites if i["user_id"] == user_id |