- time out status polling after 2 min so an abandoned browser flow
doesn't leave the button stuck on "Waiting for browser…"
- handle non-JSON / non-object payloads from the exchange and account
endpoints instead of 500ing after the state is consumed
- make singleton row creation race-safe (IntegrityError -> re-query)
- clear device_name on disconnect along with the rest of the metadata
- serve the dashboard URL from /cloud/status so the Manage link follows
VOICEBOX_CLOUD_URL instead of hardcoding production
- keep a "Disconnecting…" label on the disconnect button while pending
Connects the desktop app to Voicebox Cloud without the user ever handling an
API key. One button in Settings → General opens the system browser to
voicebox.sh, the user authorizes while signed in, and the credential lands
back in the app automatically.
Backend (FastAPI):
- /cloud/login/start opens the browser to the cloud authorize page with a
state we mint; the existing loopback server catches the redirect at
/cloud/callback and exchanges the one-time code (server-to-server, over TLS)
for a voicebox_ API key, verifies it against the API, and stores it.
- /cloud/status and /cloud/disconnect back the settings UI.
- state round-trip guards against login-CSRF; the key never crosses a browser
URL and is never exposed to the frontend (status returns a prefix only).
- CloudSettings singleton row; config gains VOICEBOX_CLOUD_URL /
VOICEBOX_CLOUD_API_URL (default the prod hosts, overridable for dev).
Frontend (React):
- CloudSection in Settings → General: "Log in with browser", polls status,
shows the connected device + a dashboard link. API keys are the advanced
path only, surfaced in the web dashboard.
The key is stored in the local app DB for now; OS keychain is a marked
follow-up.