aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/group-settings.js20
-rw-r--r--packages/meshbay-hub/tests/test_member_removal.py21
2 files changed, 37 insertions, 4 deletions
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)
diff --git a/packages/meshbay-hub/tests/test_member_removal.py b/packages/meshbay-hub/tests/test_member_removal.py
index 609ef6a..7af73a0 100644
--- a/packages/meshbay-hub/tests/test_member_removal.py
+++ b/packages/meshbay-hub/tests/test_member_removal.py
@@ -53,6 +53,27 @@ def test_the_hub_half_runs_even_when_the_node_refuses():
"member on the hub, in this list and in every other session")
+def test_removing_from_one_group_does_not_unpin_the_account():
+ """
+ `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 calling it from a per-group removal
+ took the person out of every other group on the node as well. Silently:
+ the loopback path writes no audit entry, so the node's journal showed a
+ clean join and then, hours later, a refusal with nothing in between. And
+ unrecoverably for anyone whose invitation was already spent, since the way
+ back starts with a code the operator has to issue again.
+
+ Forgetting a pinned key is a separate operator decision with its own button
+ on the node page. If one is ever wanted here, it has to be scoped to the
+ group first.
+ """
+ body = _remove_member_body()
+ assert "/unpin" not in body, (
+ "a per-group removal unpins the account node-wide: every other group "
+ "this person holds on this node goes with it")
+
+
def test_a_refusal_is_still_reported():
"""
Swallowing it would be the opposite mistake — the group key is what the