diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static')
4 files changed, 32 insertions, 16 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index c101ec9..72dd123 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -1139,8 +1139,8 @@ function App() { * exactly what a browser does, and is a worse experience rather than a * broken one. */ - const registerThisDevice = useCallback(async (token) => { - if (!platform.device.available) return; + const registerThisDevice = useCallback(async (token, authKey) => { + if (!platform.device.available || !authKey) return; try { const backend = await platform.secrets.backend(); if (backend === 'unavailable') return; @@ -1148,7 +1148,7 @@ function App() { if (!pk) return; await hubFetch('/v1/users/devices', { method: 'POST', token, - body: { pk_auth_ed25519: pk, label: t('device.this_device') }, + body: { pk_auth_ed25519: pk, label: t('device.this_device'), auth_key: authKey }, }); } catch (err) { console.warn('device not registered:', err.message); @@ -1158,11 +1158,12 @@ function App() { const authCtx = { user, login: async (username, password) => { - let token, refreshToken; + let token, refreshToken, authKey; if (window.MeshBayKeys) { const data = await window.MeshBayKeys.loginAndRecover(username, password); token = data.accessToken; refreshToken = data.refreshToken; + authKey = data.authKey; // 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. session.bundleKey = data.bundleKey; @@ -1180,7 +1181,7 @@ function App() { // On a desktop build, remember this device so the next launch does not ask // for the passphrase again. The key is generated and held by the main // process; what travels here is only its public half. - await registerThisDevice(token); + await registerThisDevice(token, authKey); // Before the session lands, so the idle watch starting with it does not // read the last-active time of whoever used this browser before. markActive(true); diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js index 4510848..fe858b2 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js @@ -257,10 +257,11 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs, setError(''); try { // Same derivation as sign-in — the token is already ours, only the key - // that opens node bundles is missing here, and the pepper that goes into - // it is asked for with that token. Persisted so this browser is set up - // from now on. - const { pepper, version } = await window.MeshBayKeys.fetchBundlePepper(token); + // that opens node bundles is missing here. The hub hands the pepper that + // goes into it only against the passphrase proof, never the token alone. + // Persisted so this browser is set up from now on. + const authKey = await window.MeshBayKeys.deriveAuthKey(pass, username); + const { pepper, version } = await window.MeshBayKeys.fetchBundlePepper(token, authKey); session.bundleKey = await window.MeshBayKeys.sessionBundleKey( pass, username, userId, pepper, version); await _storeBundleKey(session.bundleKey); diff --git a/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js b/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js index 9f28b5c..6bd5896 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js @@ -195,14 +195,25 @@ async function nodeBundleKey(sessionKey, nodePkB64) { } /** - * GET the pepper for a session that is already open — a stored session from - * before the pepper existed, a passphrase change. Sign-in carries it already. + * The pepper for a session that is already open but lacks the key derived from + * it — a restored session, a passphrase change. Sign-in carries it already. The + * hub asks for the passphrase proof: a token alone never obtains the pepper. */ -async function fetchBundlePepper(token) { +async function fetchBundlePepper(token, authKey) { const resp = await hubCall('/v1/users/me/bundle-pepper', { - headers: { Authorization: `Bearer ${token}` }, + method: 'POST', + headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, + body: JSON.stringify({ auth_key: authKey }), }); - if (!resp.ok) throw new Error(`bundle pepper: ${resp.status}`); + if (!resp.ok) { + // The hub's own words: a wrong passphrase and a locked account are what a + // person can act on, a bare status is not. + let detail = `bundle pepper: ${resp.status}`; + try { const j = await resp.json(); detail = j.detail || j.error || detail; } catch { /* not JSON */ } + const err = new Error(String(detail)); + err.status = resp.status; + throw err; + } const data = await resp.json(); return { pepper: data.bundle_pepper, version: data.bundle_pepper_version }; } @@ -446,6 +457,9 @@ async function loginAndRecover(username, password) { const result = { accessToken: data.access_token, refreshToken: data.refresh_token, + // Kept for the one call that follows a sign-in and must prove the + // passphrase again: registering this device's key. Not stored. + authKey, // The pepper rides on the sign-in response, so this costs no extra call; // it is folded into the key here and not kept. bundleKey: await sessionBundleKey( diff --git a/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js b/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js index 7c36951..1800d72 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js @@ -147,7 +147,8 @@ export function ProfilePage({ user, onLogout }) { // In the desktop application both are kept by its main process, the new // one set aside until the hub has accepted the change. const K = window.MeshBayKeys; - const { pepper, version } = await K.fetchBundlePepper(user.token); + const oldAuthKey = await K.deriveAuthKey(cpOld, user.username); + const { pepper, version } = await K.fetchBundlePepper(user.token, oldAuthKey); const oldKey = await K.sessionBundleKey( cpOld, user.username, user.userId, pepper, version); const newKey = await K.sessionBundleKey( @@ -160,7 +161,6 @@ export function ProfilePage({ user, onLogout }) { bundleKey: oldKey, newBundleKey: newKey, onProgress: setCpProgress, }); - const oldAuthKey = await window.MeshBayKeys.deriveAuthKey(cpOld, user.username); const newAuthKey = await window.MeshBayKeys.deriveAuthKey(cpNew, user.username); resp = await hubFetch('/v1/users/password', { method: 'POST', token: user.token, |