aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/ops
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-25 17:10:15 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-25 17:10:15 +0200
commit90c69477d5f701158112b3c294eff26312f89da6 (patch)
tree2691ca096da5cf93482bc750c7a03599fbc44dc8 /packages/meshbay-node/src/meshbay_node/ops
parent8bb94a39609f57be2c486f579849eebb04606808 (diff)
downloadmeshbay-main.tar.gz
feat: invitation links no longer bound to an e-mail addressHEADmain
A link is redeemable by whoever opens it first, so it can be sent by any messaging app. The address is optional (mail + label only); a link lives 7 days, fixed. Adds a Share button; see MESHBAY_DESIGN.md §3.4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/ops')
-rw-r--r--packages/meshbay-node/src/meshbay_node/ops/members.py17
1 files changed, 8 insertions, 9 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/ops/members.py b/packages/meshbay-node/src/meshbay_node/ops/members.py
index 2fd3899..b5da9c0 100644
--- a/packages/meshbay-node/src/meshbay_node/ops/members.py
+++ b/packages/meshbay-node/src/meshbay_node/ops/members.py
@@ -159,15 +159,13 @@ async def create_link_invite(state: dict, group_id: str, *,
Nothing is registered on the hub here, unlike `create_invite`: there is no
account to register yet. The hub half is a ticket the inviter's client asks
- the hub for, bound to the invitee's address (docs/MESHBAY_DESIGN.md §7.3).
+ the hub for, redeemable by the first account that opens the link
+ (docs/MESHBAY_DESIGN.md §7.3). Seven days, whatever `invite_ttl_hours` says.
"""
roster = _roster(state)
_group_ctx(state, group_id)
- config = state.get("config")
- ttl = (config.node.invite_ttl_hours if config else 168) * 3600
try:
- code, invite_id, expires = await roster.create_link_invite(
- group_id, created_by, ttl=ttl)
+ code, invite_id, expires = await roster.create_link_invite(group_id, created_by)
except LinkInviteLimit as e:
raise OpError(str(e), status=429) from e
log.info("Invitation link issued: group=%s invite=%s", group_id[:8], invite_id[:8])
@@ -199,11 +197,12 @@ def _invite_url(hub_url: str, group_id: str, ticket: str, node_pk_b64: str, code
return f"{origin}/#/invite?v=1&g={group_id}&t={ticket}&n={n}&c={code}"
-async def create_link_invitation(state: dict, group_id: str, email: str, *,
+async def create_link_invitation(state: dict, group_id: str, email: str = "", *,
created_by: str = "local-cli") -> dict:
"""
A whole invitation link, from the operator's own machine: the node's code,
- then the hub's ticket bound to `email`, then the link.
+ then the hub's ticket, then the link. `email` is optional and only labels
+ the link in the owner's list.
In that order because the ticket names the code's handle. A ticket the hub
refuses takes the code back with it — a code nobody can reach the node with
@@ -211,8 +210,8 @@ async def create_link_invitation(state: dict, group_id: str, email: str, *,
the operator sends the link.
"""
email = (email or "").strip()
- if "@" not in email:
- raise OpError("An invitation link is bound to an e-mail address", status=422)
+ if email and "@" not in email:
+ raise OpError("Not an e-mail address", status=422)
hub = _hub(state)
sk_node = state.get("sk_node")
if sk_node is None: