From 375ad7d0435a176ad593a32045a5f0182a36d505 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sun, 20 Sep 2026 18:56:58 +0200 Subject: fix: removing someone who never redeemed their invitation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A member row appears only when a code is consumed, so revoking someone invited to the wrong group was refused for having no row — and the node's refusal aborted the browser's removal before its hub half, leaving them a member everywhere with a live code. Revoking now cancels unredeemed codes for that group, and a node refusal no longer cancels the hub removal. Co-Authored-By: Claude Opus 5 --- .../src/meshbay_hub/static/group-settings.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 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 4d67348..16f6901 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js @@ -727,11 +727,23 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, * wrapped for them; if the hub removal then fails, they are a member on paper * with no key. The other order would leave them able to reach a node that * still serves them. + * + * **A node that refuses does not cancel the hub half.** It used to: the node + * call threw, the whole removal ended there, and the person stayed a member + * everywhere — the list here (the hub answers it), the owner's other + * sessions, the administrator's view — with nothing said about which half + * had failed. Someone invited to the wrong group hit it every time, because + * a node holds no member row for an invitation nobody has redeemed and + * answered "no such member in that group" to the only button offering to + * 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. */ const removeMember = useCallback(async (member) => { const transport = transportRef && transportRef.current; setError(''); setRemoving(member.user_id); + let nodeError = ''; try { if (platform.node.available) { try { @@ -747,14 +759,17 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, const signFn = (sk && window.MeshBayKeys) ? (transcript) => window.MeshBayKeys.signBytes(sk, transcript) : null; - await transport.revokeMember(member.user_id, signFn); + try { + await transport.revokeMember(member.user_id, signFn); + } catch (err) { nodeError = err.message; } } await hubFetch(`/v1/groups/${groupId}/members/${member.username}`, { method: 'DELETE', token, }); loadMembers(); + if (nodeError) setError(nodeError); } catch (err) { - setError(err.message); + setError(nodeError ? `${nodeError} — ${err.message}` : err.message); } finally { setRemoving(''); } -- cgit v1.2.3