From 03bcfcdef2ff9a3d6ed0776f7580a45a5401ef37 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Mon, 21 Sep 2026 01:35:13 +0200 Subject: fix(hub): removing from a group no longer unpins the account The node half called `unpin` after `revoke`, and an unpin takes no group: it deletes the identity and every member row the account holds on this node, and drops the stored keypair bundle with them. Taking somebody out of one group took them out of all of them, silently. `revoke` is group-scoped and is what withdraws the key. Co-Authored-By: Claude Opus 5 --- .../src/meshbay_hub/static/group-settings.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'packages/meshbay-hub/src') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js index 16f6901..d7a1a66 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js @@ -738,6 +738,22 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, * take them back out. The hub half only ever removes access, so it is not * the half to skip when the other is in doubt; what the node said is * reported once the removal has been done, rather than in place of it. + * + * **The node half is `revoke`, and only `revoke`.** It used to unpin as + * well, and an unpin takes no group: `roster.unpin` deletes the identity and + * *every* member row the account holds on this node, and `ops.unpin_member` + * drops the stored keypair bundle with them. So taking somebody out of one + * group took them out of all of them — and a person removed from a group + * they had only been invited to lost the one they had been reading all + * afternoon, six hours before anyone noticed. Nothing on the node said so: + * the loopback path writes no audit entry, so the journal showed a clean + * join and then, hours later, a refusal with nothing in between. Their + * invitation was already spent, so there was no way back that did not start + * with a new code. Membership is per group; a pinned identity is the + * person's key for this whole node, and forgetting it is a separate + * operator decision with its own button (`node-page.js`). The MNP branch + * below never unpinned, which is the tell that this call was the odd one + * out rather than the pair of the other. */ const removeMember = useCallback(async (member) => { const transport = transportRef && transportRef.current; @@ -750,10 +766,6 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, await platform.node.call('POST', `/api/members/${member.user_id}/revoke?group_id=${groupId}`); } catch { /* best effort — node may not host this group */ } - try { - await platform.node.call('POST', - `/api/members/${member.user_id}/unpin`); - } catch { /* best effort */ } } else if (transport && transport.connected && operatorPaired) { const sk = transport.sessionKeys && transport.sessionKeys.skEdB64; const signFn = (sk && window.MeshBayKeys) -- cgit v1.2.3