aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/app.js46
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/transport.js7
2 files changed, 43 insertions, 10 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js
index 1ef878a..6fefe0f 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js
@@ -765,7 +765,7 @@ async function pipelinedDownload(transport, gekKey, fileId, totalChunks, onChunk
return results;
}
-function GroupPage({ groupId, group, token, username, userId }) {
+function GroupPage({ groupId, group, token, username, userId, onRefreshAuth }) {
const [status, setStatus] = useState('idle');
const [entries, setEntries] = useState([]);
const [cached, setCached] = useState(false);
@@ -786,6 +786,9 @@ function GroupPage({ groupId, group, token, username, userId }) {
const [retryKey, setRetryKey] = useState(0);
const transportRef = useRef(null);
const gekRef = useRef(null);
+ // One refresh per mount: if a fresh token still says we are not a member, we
+ // really are not, and retrying forever would hide that.
+ const refreshedRef = useRef(false);
const submitJoinCode = useCallback((e) => {
e.preventDefault();
@@ -902,14 +905,24 @@ function GroupPage({ groupId, group, token, username, userId }) {
cacheGroupIndex(groupId, group ? group.name : groupId, freshEntries);
} catch (err) {
- if (!cancelled) {
- // The node has never seen this browser for this account: it needs a
- // one-time code from the operator before it will hand over the group
- // key. Not an error to shout about — a step in joining.
- if (err.reason === 'code_required') setNeedsCode(true);
- setError(err.message);
- setStatus('error');
+ if (cancelled) return;
+
+ // Our token predates being added to this group. Refresh once and retry
+ // rather than telling someone who was just invited that they are not a
+ // member — which is what the node honestly sees, and is useless to them.
+ if (err.reason === 'not_a_member' && !refreshedRef.current && onRefreshAuth) {
+ refreshedRef.current = true;
+ try {
+ if (await onRefreshAuth()) return; // new token → effect re-runs
+ } catch { /* fall through to the message below */ }
}
+
+ // The node has never seen this browser for this account: it needs a
+ // one-time code from the operator before it will hand over the group
+ // key. Not an error to shout about — a step in joining.
+ if (err.reason === 'code_required') setNeedsCode(true);
+ setError(err.message);
+ setStatus('error');
}
};
@@ -2653,6 +2666,20 @@ function App() {
},
};
+ // Group membership is baked into the access token at login and the hub does not
+ // push updates, so someone invited after they signed in carries a token that
+ // says they are in nothing. Refreshing re-reads membership from the database.
+ const refreshAuth = useCallback(async () => {
+ if (!user || !user.refreshToken) return null;
+ const data = await hubFetch('/v1/users/token/refresh', {
+ method: 'POST', body: { refresh_token: user.refreshToken },
+ });
+ const u = { ...user, token: data.access_token };
+ setUser(u);
+ saveAuth(u);
+ return data.access_token;
+ }, [user]);
+
let page;
if (route === '/login' || route === '/register') {
page = route === '/register'
@@ -2677,7 +2704,8 @@ function App() {
const group = groups.find(g => g.id === groupId);
page = html`<${GroupPage}
groupId=${groupId} group=${group} token=${user.token}
- username=${user.username} userId=${user.userId} />`;
+ username=${user.username} userId=${user.userId}
+ onRefreshAuth=${refreshAuth} />`;
} else if (route === '/admin') {
page = (user.role === 'moderator' || user.role === 'admin')
? html`<${AdminPage} token=${user.token} />`
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
index 271d11f..c200674 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
@@ -314,8 +314,13 @@ class MeshBayTransport {
// A node that answers a handshake with anything other than a challenge is not
// running the mutual protocol. Accepting a bare handshake_ack here would let a
// peer skip proving GEK possession entirely (C3/C6).
- throw new Error(
+ const rejected = new Error(
'MNP handshake rejected: ' + (reply.detail || `unexpected ${reply.type}`));
+ // `not_a_member` usually means our token predates being added to the group;
+ // the caller refreshes it and tries again rather than showing that to someone
+ // who was invited thirty seconds ago.
+ rejected.reason = reply.code || '';
+ throw rejected;
}
/**