aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-01 11:47:39 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-01 11:47:39 +0200
commit752b160c7c5e671e0db8f402a52fac27bb85ab06 (patch)
tree61be38fa0f5d38e2bda291b69aa1037f36112f23 /packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
parent15e117673d2303bf476d4f78699e47913ce1aec0 (diff)
downloadmeshbay-752b160c7c5e671e0db8f402a52fac27bb85ab06.tar.gz
fix(hub): a stranger who knows your name locks only browsers you never used
A sign-in from a browser that presented no token is answered with one (known_browser, kept hashed, twenty per account); a later sign-in presenting it counts failures on its own row, which nobody else can spend. Passphrase checks inside an open session (change, e-mail, deletion, device, pepper) count on the account's own row, so a locked name no longer stops its owner there either; /me reports that row. Reset and erasure forget the browsers (F-15). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/keyderive.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/keyderive.js26
1 files changed, 25 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js b/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
index 6bd5896..b1770a7 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
@@ -426,13 +426,36 @@ async function decryptBundle(bundleB64, aesKey, { userId, nodePk }) {
* decrypts it and returns the keys + encrypted bundle for push to node.
* Otherwise returns bundleKey so the caller can fetch from node during handshake.
*/
+// The token the hub gave this browser at an earlier sign-in, per account. It
+// is not a credential — the passphrase is still asked — but a sign-in that
+// presents it has a failure counter of its own, so a stranger who keeps
+// failing on this account's name locks only browsers it has never used.
+// Kept across sign-outs on purpose: forgetting it would be the lockout again.
+const KNOWN_BROWSERS = 'mb_known_browsers';
+
+function _knownBrowser(username) {
+ try { return (JSON.parse(localStorage.getItem(KNOWN_BROWSERS)) || {})[username] || null; }
+ catch { return null; }
+}
+
+function _rememberBrowser(username, token) {
+ if (!token) return;
+ try {
+ const all = JSON.parse(localStorage.getItem(KNOWN_BROWSERS)) || {};
+ all[username] = token;
+ localStorage.setItem(KNOWN_BROWSERS, JSON.stringify(all));
+ } catch { /* storage refused: this browser stays an unknown one */ }
+}
+
async function loginAndRecover(username, password) {
const authKey = await deriveAuthKey(password, username);
+ const known = _knownBrowser(username);
const resp = await hubCall('/v1/users/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ username, auth_key: authKey }),
+ body: JSON.stringify({ username, auth_key: authKey,
+ ...(known ? { known_browser: known } : {}) }),
});
if (!resp.ok) {
@@ -454,6 +477,7 @@ async function loginAndRecover(username, password) {
}
const data = await resp.json();
+ _rememberBrowser(username, data.known_browser);
const result = {
accessToken: data.access_token,
refreshToken: data.refresh_token,