summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/app.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/app.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/app.js142
1 files changed, 25 insertions, 117 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js
index 300059d..3087e0e 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js
@@ -65,9 +65,10 @@ async function getAllCachedIndexes() {
// ── Auth persistence ─────────────────────────────────────────────────────────
-let _sessionKeys = null;
+// The key that opens a node's keypair bundle, derived once at sign-in. There is
+// no global identity to keep: identity keys belong to a node and are fetched from
+// it (transport.js), so nothing of that kind lives here.
let _bundleKey = null;
-let _pendingBundlePush = null;
// A one-time pairing code the user just typed, consumed by the next connection
// attempt. Deliberately not persisted: it is single-use and short-lived.
let _pendingJoinCode = null;
@@ -108,11 +109,6 @@ async function _clearKeyDB() {
db.close();
} catch {}
}
-function _saveSessionKeys() {
- try {
- if (_sessionKeys) sessionStorage.setItem('meshbay_sk', JSON.stringify(_sessionKeys));
- } catch {}
-}
/**
* Rough passphrase strength, in bits, and what it is up against.
*
@@ -144,35 +140,6 @@ function passwordBits(pw) {
const PASSWORD_MIN_BITS = 60; // refuse below this
const PASSWORD_MIN_LEN = 12;
-/**
- * Re-encrypt a bundle written under the old KDF before it is stored again.
- *
- * PBKDF2 bundles are still readable, but leaving one on a node keeps the weak
- * protection alive for as long as it sits there. Any backup is an opportunity to
- * replace it with the Argon2id form, and it costs nothing the user notices.
- */
-async function _upgradedBundle(bundleEnc) {
- try {
- if (!window.MeshBayKeys || !_sessionKeys || !_bundleKey) return bundleEnc;
- if (window.MeshBayKeys.bundleVersion(bundleEnc) === 2) return bundleEnc;
- const b64 = (s) => Uint8Array.from(atob(s), c => c.charCodeAt(0));
- return await window.MeshBayKeys.encryptBundleWithKey(
- b64(_sessionKeys.skEdB64), b64(_sessionKeys.skXB64), _bundleKey.v2);
- } catch (e) {
- console.warn('[MeshBay] bundle upgrade skipped:', e.message);
- return bundleEnc;
- }
-}
-
-function _restoreSessionKeys() {
- try {
- if (!_sessionKeys) {
- const sk = sessionStorage.getItem('meshbay_sk');
- if (sk) _sessionKeys = JSON.parse(sk);
- }
- } catch {}
-}
-
/** Public X25519 key from our own secret — never read back from the hub. */
async function _pkXFromSk(skPkcs8B64) {
const raw = Uint8Array.from(atob(skPkcs8B64), c => c.charCodeAt(0));
@@ -183,35 +150,6 @@ async function _pkXFromSk(skPkcs8B64) {
return pad ? b64 + '='.repeat(4 - pad) : b64;
}
-/**
- * Recover our identity keys from what this browser already holds.
- *
- * sessionStorage dies with the tab, but the encrypted keypair bundle sits in
- * localStorage from registration and the key that opens it is in IndexedDB from
- * login. Without this, closing the tab looked exactly like never having
- * registered here — "this browser does not hold your keys", while both halves
- * were on disk a few bytes apart.
- */
-async function _recoverLocalKeys(username) {
- if (_sessionKeys || !username) return;
- try {
- if (!_bundleKey) _bundleKey = await _loadBundleKey();
- if (!_bundleKey || !window.MeshBayKeys) return;
- const enc = localStorage.getItem(`meshbay_kp_${username}`);
- if (!enc) return;
- const keys = await window.MeshBayKeys.decryptBundleWithKey(enc, _bundleKey);
- _sessionKeys = {
- skXB64: keys.skX,
- skEdB64: keys.skEd,
- pkXB64: await _pkXFromSk(keys.skX),
- };
- _pendingBundlePush = enc; // still to be backed up to a node
- _saveSessionKeys();
- } catch (e) {
- console.warn('[MeshBay] could not recover local keys:', e);
- }
-}
-
function loadAuth() {
try {
return JSON.parse(localStorage.getItem(AUTH_KEY));
@@ -225,11 +163,8 @@ function saveAuth(auth) {
localStorage.setItem(AUTH_KEY, JSON.stringify(auth));
} else {
localStorage.removeItem(AUTH_KEY);
- _sessionKeys = null;
_bundleKey = null;
- _pendingBundlePush = null;
_clearKeyDB();
- try { sessionStorage.removeItem('meshbay_sk'); } catch {}
}
}
@@ -932,8 +867,6 @@ function GroupPage({ groupId, group, token, username, userId, onRefreshAuth }) {
setError('');
gekRef.current = null;
if (!_bundleKey) _bundleKey = await _loadBundleKey();
- _restoreSessionKeys();
- await _recoverLocalKeys(username);
try {
const nodesData = await hubFetch(`/v1/groups/${groupId}/nodes`, { token });
if (cancelled) return;
@@ -942,14 +875,9 @@ function GroupPage({ groupId, group, token, username, userId, onRefreshAuth }) {
return;
}
- // Session keys for the P2P key exchange. skEdB64 belongs here too: the
- // node identifies us by the Ed25519 identity, and join_request signs both
- // public keys with it — without it we can neither join nor pair.
- const sessionKeys = _sessionKeys ? {
- skXB64: _sessionKeys.skXB64,
- skEdB64: _sessionKeys.skEdB64,
- pkXB64: _sessionKeys.pkXB64,
- } : null;
+ // No keys are carried in: the transport fetches this node's identity
+ // from the node, or creates one there on a first join.
+ const sessionKeys = null;
setStatus('connecting');
const nodeId = nodesData.nodes[0].node_id;
@@ -963,34 +891,15 @@ function GroupPage({ groupId, group, token, username, userId, onRefreshAuth }) {
if (cancelled) return;
setIsNodeAdmin(!!ack.is_node_admin);
- // If transport recovered different session keys from node during handshake
- if (transport.sessionKeys) {
- const recovered = transport.sessionKeys;
- if (!_sessionKeys || recovered.skXB64 !== _sessionKeys.skXB64) {
- _sessionKeys = recovered;
- if (!_sessionKeys.pkXB64) {
- const pubkeys = await hubFetch(
- `/v1/users/${username}/pubkeys`, { token });
- _sessionKeys.pkXB64 = pubkeys.pk_x25519;
- }
- _pendingBundlePush = null;
- try { localStorage.removeItem(`meshbay_kp_${username}`); } catch {}
- _saveSessionKeys();
- }
- }
-
- // Back the encrypted keys up to the node. This is what lets any other
- // browser recover them with the passphrase, which is the ordinary
- // expectation; the protection that matters is the KDF guarding the
- // bundle, not withholding the bundle.
- if (transport.connected && _pendingBundlePush) {
+ // A first join to this node generated an identity for it; leave it with
+ // the node so any other browser can become the same person here with the
+ // passphrase. It is this node's key and no other's.
+ if (transport.connected && transport.newNodeBundle) {
try {
- await transport.storeKeypairBundle(
- await _upgradedBundle(_pendingBundlePush));
- try { localStorage.removeItem(`meshbay_kp_${username}`); } catch {}
- _pendingBundlePush = null;
+ await transport.storeKeypairBundle(transport.newNodeBundle);
+ transport.newNodeBundle = null;
} catch (e) {
- console.warn('[MeshBay] Bundle push to node deferred:', e.message);
+ console.warn('[MeshBay] could not leave our key with the node:', e.message);
}
}
@@ -1134,8 +1043,11 @@ function GroupPage({ groupId, group, token, username, userId, onRefreshAuth }) {
try {
// Signs an explicit transcript built by transport.js, not opaque bytes from
// the node — see MeshBayCrypto.adminTranscript and finding H5.
- const signFn = (_sessionKeys && window.MeshBayKeys)
- ? (transcript) => window.MeshBayKeys.signBytes(_sessionKeys.skEdB64, transcript)
+ // Signed with the identity this node pinned for us — the only one it
+ // will accept, and the only one we hold here.
+ const sk = transport.sessionKeys && transport.sessionKeys.skEdB64;
+ const signFn = (sk && window.MeshBayKeys)
+ ? (transcript) => window.MeshBayKeys.signBytes(sk, transcript)
: null;
await transport.deleteFile(entry.id, signFn);
const indexMsg = await transport.fetchIndex();
@@ -1601,8 +1513,11 @@ function MembersPanel({ groupId, group, token, transportRef, gekRef,
// code it never learns — the code goes to a human, out of band.
const account = await hubFetch(`/v1/users/${username}/pubkeys`, { token });
- const signFn = (_sessionKeys && window.MeshBayKeys)
- ? (transcript) => window.MeshBayKeys.signBytes(_sessionKeys.skEdB64, transcript)
+ // Signed with the identity this node pinned for us — the only one it
+ // will accept, and the only one we hold here.
+ const sk = transport.sessionKeys && transport.sessionKeys.skEdB64;
+ const signFn = (sk && window.MeshBayKeys)
+ ? (transcript) => window.MeshBayKeys.signBytes(sk, transcript)
: null;
const result = await transport.createInvite(
account.user_id, groupId, username, signFn);
@@ -2748,12 +2663,10 @@ function App() {
const data = await window.MeshBayKeys.loginAndRecover(username, password);
token = data.accessToken;
refreshToken = data.refreshToken;
+ // The only thing sign-in produces: the key that opens a node's bundle.
+ // Which identity we use is decided per node, when we get there.
_bundleKey = data.bundleKey;
await _storeBundleKey(_bundleKey);
- if (data.skXB64) {
- _sessionKeys = { skXB64: data.skXB64, skEdB64: data.skEdB64 };
- _pendingBundlePush = data.keypairBundleEnc;
- }
} else {
const data = await hubFetch('/v1/users/login', {
method: 'POST',
@@ -2763,11 +2676,6 @@ function App() {
refreshToken = data.refresh_token;
}
const me = await hubFetch('/v1/users/me', { token });
- if (_sessionKeys) {
- const pubkeys = await hubFetch(`/v1/users/${username}/pubkeys`, { token });
- _sessionKeys.pkXB64 = pubkeys.pk_x25519;
- _saveSessionKeys();
- }
const u = { username, userId: me.user_id, token, refreshToken, role: me.role };
setUser(u);
saveAuth(u);