Skip to content

hai login: browser sign-in for every account - #229

Open
H-maximedelpit wants to merge 1 commit into
mainfrom
browser-login-all-accounts
Open

H-maximedelpit wants to merge 1 commit into
mainfrom
browser-login-all-accounts

Conversation

@H-maximedelpit

@H-maximedelpit H-maximedelpit commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Warning notice. Spend several hours with Fable implementing, debating & reviewing the code. It deals with auth & some security improvement so please read carefully and don't hesitate to reject and take the lead please. cc @laura-hai @ivanvalentini-h @abonneth

Purpose

hai login only works for Google accounts: it sends the browser to the portal with provider=google, so the CLI, the quickstart, the onboarding emails and the Agents API docs all reject email + password users. With hcompai/portal-h#865 and hcompai/platform-frontend#779, the CLI can let the platform login page handle any account. This PR is the recommended path; #228 is the exclusive terminal-password alternative.

What it does

  • The authorize URL drops provider=google. The portal sends the browser to the platform login page, where Google and email + password both finish back on the CLI's loopback listener. Loopback, PKCE and /desktop/exchange are unchanged.
  • After minting, prints "Signed in as in organization ", names only, never ids. This is the mitigation for the inherited login-CSRF scenario (someone else's account ending up in your terminal) that the security review flagged.
  • Revokes the web session the sign-in created once the key exists; the API key is the credential.
  • hai login --key still exists for machines without a browser.

What it does not do

  • No change to how the SDK reads its key (HAI_API_KEY only); the stored-key fallback branches are parked unmerged by decision.
  • No end-to-end run yet: it needs the portal and frontend PRs deployed. Unit tests cover the URL shape, the identity line and the session revoke with a mocked portal.

Alternatives rejected

  • Detecting the portal's non_oauth_user error and falling back to a key-paste prompt: works today without portal changes, but still leaves non-Google users one step short of a one-click login. Dropped in favour of the portal change.
  • Terminal email + password: kept as hai login: email and password sign-in #228, exclusive with this PR.

Dependencies and merge order

  1. hcompai/portal-h#865 deployed (migration 0063).
  2. hcompai/platform-frontend#779 deployed.
  3. Rebase this on CLI warns when the key comes from ./.env #227 (both touch app.py), merge, release. The new CLI gets a 400 from an old portal, so do not release early; the current CLI keeps working throughout.
  4. Then the onboarding email snippets switch to plain hai login.

🤖 Generated with Claude Code


Note

High Risk
Changes the CLI authentication flow (authorize URL, session lifecycle, and post-login identity display) in security-sensitive login and key-minting code; depends on coordinated portal/frontend deploys.

Overview
hai login browser sign-in no longer forces Google: the authorize URL drops provider=google so the portal can send users to the platform login page (Google or email/password) while loopback, PKCE, and /desktop/exchange stay the same.

After a successful mint, login_and_mint returns a SignedIn record (key, email, optional organization name). The CLI prints Signed in as <email> in organization <name> so a key landed in the wrong account is obvious, then saves the key. It also DELETEs the temporary web session once the API key exists (errors on revoke are ignored).

Docs and KEY_FALLBACK are updated for multi-method browser login and hai login --key without a browser. New unit tests cover authorize URL shape, mint/revoke ordering, and the identity line.

Reviewed by Cursor Bugbot for commit 89b1346. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 89b1346. Configure here.

return _mint_key(client, portal, org_id, label)["key"]
body = token.json()
client.headers["Authorization"] = f"Bearer {body['access_token']}"
return _mint_for_signed_in_user(client, portal, label, session_id=body.get("session_id"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Session revoke never receives session id

High Severity

The exchange POST never sends X-SDK-Auth, so portal session_id stays on the cookie and body.get("session_id") is empty. The later session DELETE is skipped, and the web session created by sign-in remains valid even though the API key is supposed to be the only leftover credential.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 89b1346. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quoting Fable:

Re "Session revoke never receives session id": checked against portal-h domains/auth/controller.py, desktop_exchange returns DesktopExchangeOutput(access_token, refresh_token, session_id) as the JSON body; it is not the cookie-mode login response, so X-SDK-Auth does not apply and body.get("session_id") is populated. The revoke runs. Covered by test_minting_names_the_identity_and_revokes_the_web_session. No change.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant