diff --git a/frontend/packages/dashboard/src/matrix-accounts.js b/frontend/packages/dashboard/src/matrix-accounts.js index 92fe2159..26db0a9a 100644 --- a/frontend/packages/dashboard/src/matrix-accounts.js +++ b/frontend/packages/dashboard/src/matrix-accounts.js @@ -12,8 +12,12 @@ // POST /api/matrix-account-login (x-www-form-urlencoded, operator-auth) // fields: agent, account, homeserver, mode=password|token, // user_id?, password?, token? -// -> 2xx { ok: true, user_id } on success -// -> 4xx { error: "" } on failure +// -> 200 JSON { ok: true, user_id } on success +// -> error body in transition: today a bare plain-text body +// (hive-c0re's error_response), migrating to RFC 9457 +// application/problem+json { type, title, detail, … }. The error +// path reads shape-agnostically (text first, then JSON `detail` if +// it parses) so both shapes work regardless of BE/FE merge order. // The token is NEVER echoed back in any response, and this page never // re-renders a submitted secret. // @@ -198,17 +202,43 @@ async function submitLogin(e) { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams(fd), }); - let body = {}; - try { body = await resp.json(); } catch { /* tolerate non-JSON error pages */ } - if (resp.ok && body.ok) { - out.className = 'ma-result ok'; - out.textContent = '✓ logged in as ' + (body.user_id || '(unknown)') + ' — token stored.'; - clearSecrets(formEl); - loadAccounts(agent); + if (resp.ok) { + // Success is 200 + JSON { ok, user_id }. + let body = {}; + try { body = await resp.json(); } catch { /* tolerate odd 2xx body */ } + if (body.ok) { + out.className = 'ma-result ok'; + out.textContent = '✓ logged in as ' + (body.user_id || '(unknown)') + ' — token stored.'; + clearSecrets(formEl); + loadAccounts(agent); + } else { + out.className = 'ma-result err'; + out.textContent = '✗ login failed (unexpected response).'; + clearSecrets(formEl); + } } else { + // The BE error-body shape is in transition: today hive-c0re's + // error_response sends a bare plain-text body (e.g. "matrix-account- + // login: password mode needs user_id + password"); the RFC 9457 rework + // moves it to application/problem+json ({ type, title, detail, … }). + // Read shape-agnostically so the FE handles both with no merge-order + // coupling: pull the body once as text, and if it parses as JSON + // surface `detail` (problem+json) → `error`/`title` fallback, else use + // the raw text. A bare HTTP code is the last resort. + let msg = ''; + try { + const raw = (await resp.text()).trim(); + msg = raw; + if (raw && (raw[0] === '{' || raw[0] === '[')) { + try { + const body = JSON.parse(raw); + msg = body.detail || body.error || body.title || raw; + } catch { /* not JSON after all — keep the raw text */ } + } + } catch { /* fall back to the status code below */ } out.className = 'ma-result err'; - out.textContent = '✗ ' + (body.error || ('login failed (HTTP ' + resp.status + ')')); + out.textContent = '✗ ' + (msg || ('login failed (HTTP ' + resp.status + ')')); clearSecrets(formEl); } } catch (err) {