Skip to content

login: default to browser sign-in, add --device fallback - #1366

Merged
toothbrush merged 15 commits into
mainfrom
login-loopback-browser-flow
Jun 11, 2026
Merged

login: default to browser sign-in, add --device fallback#1366
toothbrush merged 15 commits into
mainfrom
login-loopback-browser-flow

Conversation

@toothbrush

@toothbrush toothbrush commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

https://entire.io/gh/entireio/cli/trails/517

What

entire login now opens your browser by default and finishes automatically — no code to copy.

entire login            # browser sign-in (default when interactive)
entire login --device   # the old device-code flow
Logging in to: https://us.auth.entire.io

Press Enter to open in browser...

Waiting for sign-in... ✓ Login complete.

Falls back to the device-code flow automatically when there's no interactive terminal (CI, piped, SSH without a tty), so headless logins keep working — same shape as gh / gcloud / aws sso.

How

RFC 8252 loopback authorization-code flow with PKCE, via auth-go's new authcode package: bind a 127.0.0.1 listener, open the browser, catch the redirect, exchange the code. Shares the token-validate/save/context-record tail with the device flow.

Verified against prod (us.auth.entire.io)

  • Discovery doc: authorization_endpoint=/authorize, code_challenge_methods_supported=["S256"], authorization_code grant, public-client (none) auth.
  • entire-cli is registered with a http://127.0.0.1/callback loopback redirect, any-port (probed: unknown client and unregistered redirect both 400 at /authorize; two ephemeral ports accepted).

Before merge (stacked) - DONE ✅

Stacked on entireio/auth-go#16 (authcode package). go.mod is pinned to that branch's pseudo-version so this builds today; once #16 merges + tags, re-pin: go get github.com/entireio/auth-go@<tag> && go mod tidy.


Note

High Risk
Changes the default authentication path and OAuth loopback/PKCE handling; mistakes could break login or weaken token handling across environments.

Overview
entire login now defaults to a loopback browser (PKCE authorization-code) sign-in when an interactive terminal is available, with --device to keep the previous device-code flow. Non-interactive environments (CI, pipes, SSH without a tty) still use device-code automatically.

The auth client wires auth-go authcode alongside device flow, adds AuthorizePath on v1/v2 providers, and exposes StartBrowserAuth / BrowserAuthFlow. Login shares token validation and persistence via persistLogin for both paths. Test-only behavior skips blocking on /dev/tty and refuses to spawn a real browser so integration tests can drive the loopback callback from printed URLs.

go.mod pins a newer github.com/entireio/auth-go pseudo-version that includes the authcode package (stacked on auth-go#16).

Reviewed by Cursor Bugbot for commit 5ea776a. Configure here.

Copilot AI review requested due to automatic review settings June 4, 2026 14:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates entire login to default to a browser-based loopback authorization-code (PKCE) flow when an interactive terminal is available, while preserving the existing device-code flow via --device and as an automatic fallback for non-interactive environments.

Changes:

  • Default interactive login to loopback browser auth; add --device to force device-code flow and auto-fallback to device flow when non-interactive.
  • Extend the auth client/provider wiring to support the authorization endpoint and a new authcode (browser) client.
  • Add unit and integration coverage for the new browser login flow.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
go.mod Bumps github.com/entireio/auth-go to a newer pseudo-version that includes the authcode support.
go.sum Adds checksums for the updated auth-go pseudo-version.
cmd/entire/cli/login.go Implements browser login flow, routing logic, and shared token persistence.
cmd/entire/cli/login_test.go Adds unit tests for browser-flow routing and runBrowserLogin behavior.
cmd/entire/cli/integration_test/login_test.go Adds an end-to-end integration test for the browser loopback flow and refactors login process helpers.
cmd/entire/cli/auth/provider.go Adds AuthorizePath to provider configuration for both provider versions.
cmd/entire/cli/auth/client.go Adds an authcode client and a StartBrowserAuth entrypoint with an interface-friendly flow wrapper.

Comment thread cmd/entire/cli/integration_test/login_test.go
Comment thread cmd/entire/cli/integration_test/login_test.go Outdated
@toothbrush

Copy link
Copy Markdown
Contributor Author

@cursor review

@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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 5ea776a. Configure here.

@toothbrush
toothbrush marked this pull request as ready for review June 9, 2026 07:42
@toothbrush
toothbrush requested a review from a team as a code owner June 9, 2026 07:42
toothbrush and others added 10 commits June 10, 2026 09:52
`entire login` now defaults to the loopback authorization-code flow
(auth-go's new authcode package): open a browser, get redirected back
to a local 127.0.0.1 listener, exchange the code — no code to type, no
poll latency. The device-code flow stays available behind `--device`
and is also the automatic fallback when there's no interactive terminal
(CI, piped, SSH without a tty), matching gh / gcloud / aws sso.

- provider: add AuthorizePath (v1 /oauth/authorize, v2 /authorize —
  CONFIRM the v2 path against the auth host's OIDC discovery doc before
  relying on it in production).
- auth.Client: build an authcode.Client alongside the deviceflow client
  and expose StartBrowserAuth + the BrowserAuthFlow shim (parallel to the
  existing deviceflow shim), flattening TokenSet to (access, refresh).
- login.go: shouldUseBrowserLogin() flow selection, runBrowserLogin(),
  and a shared persistLogin() tail reused by both flows. openBrowser is a
  no-op under test (ENTIRE_TEST_TTY) so the browser flow is testable
  without launching a real browser.

Tests: unit coverage for the flow selector and runBrowserLogin error/
messaging paths; an integration test drives the full loopback flow by
playing the browser (GET the callback with the state parsed off the
printed authorize URL). Existing device-flow tests now exercise the
--device / non-interactive fallback path.

Stacked on auth-go's authcode package: before merge, bump the auth-go
dependency to the released tag (go get github.com/entireio/auth-go@<tag>).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pulls in the new authcode package (RFC 8252 loopback + PKCE) that the
login browser flow depends on. Pinned to the branch pseudo-version
(entireio/auth-go#16); re-pin to the released tag once that PR merges.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 72b11bca1f11
Make the default loopback flow read like --device: show "Login URL:",
pause on "Press Enter to open in browser...", then print "Waiting for
sign-in... " with no newline so persistLogin's "Login complete." lands
on the same line. waitForEnter now short-circuits under test
(interactive.UnderTest) like openBrowser, so forcing interactive mode in
tests no longer blocks on a real /dev/tty read.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: eec99cd78cba
The browser flow now prints "Logging in to:  <auth host>" rather than
the full authorize URL (the PKCE challenge + loopback redirect made it
long and unreadable). The full URL is shown only as a fallback when the
browser can't be opened, which is also what a headless host hits.

openBrowser reports failure under test (no usable browser on a CI host,
and we mustn't spawn a real one), so the integration test recovers the
ephemeral loopback callback URL from that fallback line on stdout —
replacing the file-based seam from the previous iteration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 3ae392c3a1eb
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 2c42a3117eae
- Assert the stubbed-Wait error in runBrowserLogin tests instead of
  blank-assigning it (errcheck check-blank).
- nolint:ireturn on StartBrowserAuth and its test fake — the
  BrowserAuthFlow interface return is deliberate for test substitution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: a0b237ab3218
Avoid the ireturn lint suppression by following "accept interfaces,
return structs" (as the device-flow shim already does):

- auth.BrowserAuthFlow is now a concrete struct (was an interface);
  StartBrowserAuth returns *BrowserAuthFlow.
- runBrowserLogin accepts a cli-local browserAuthFlow interface plus the
  base URL, with StartBrowserAuth moved up into newLoginCmd. The fake
  flow satisfies the local interface; no nolint anywhere.

Output: one space after "Logging in to:" and a single blank line before
"Waiting for sign-in..." (the leading newline doubled up with the
post-Enter newline).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: e72efb851121
openBrowser reports failure under test (not a no-op), forcing the
fallback URL path the test scrapes — update the doc comment to match.
Addresses a PR review note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 86eeeb1f9d81
@toothbrush
toothbrush force-pushed the login-loopback-browser-flow branch from 5ea776a to 41a7d48 Compare June 10, 2026 02:42
Soph and others added 4 commits June 10, 2026 21:47
The device flow is bounded by the AS's expires_in (capped at 15m), but
the browser flow waited on the loopback redirect with no deadline — a
closed tab left `entire login` hanging until Ctrl-C. Bound the wait at
5 minutes, starting after the Enter prompt so reading time isn't
counted, and point the timeout error at `--device` as the escape hatch.
Parent-context cancellation (Ctrl-C) still surfaces as cancellation,
not as a timeout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 8979d0646ea0
Over SSH with a TTY, CanPromptInteractively() is true so the browser
flow was chosen — but the loopback listener binds 127.0.0.1 on the
remote host, where the user's local browser can't reach it; even the
printed fallback URL can't complete. Detect SSH sessions via the
SSH_CONNECTION/SSH_CLIENT/SSH_TTY vars sshd sets and route them to the
device-code flow with a one-line explanation, the same way gh and
gcloud do.

Extract the flow choice from the cobra RunE into runLoginAuto, taking
a startBrowser func and the environment facts as plain values, so the
selection and its stderr commentary are unit-testable with fakes. The
browser-flow integration test blanks the SSH_* vars it inherits from
os.Environ() so it keeps exercising the browser path when a developer
runs the suite over SSH.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 4aa664e559dc
If StartBrowserAuth couldn't bind the 127.0.0.1 listener (sandboxing,
firewall policy, exhausted ports), `entire login` errored out even
though the device-code flow would have worked fine. Warn on stderr with
the bind error and continue with the device flow instead of stranding
the user.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 247398f9ecc7
Cleanups from a reuse/simplification/efficiency/altitude review of the
browser-flow hardening commits; no behavior change:

- Collapse runLoginAuto's three trailing bools into a loginFlowFacts
  struct, so call sites are self-documenting instead of relying on
  /* useDevice */-style comments.
- Pass canPrompt into runLogin instead of re-probing /dev/tty via
  interactive.CanPromptInteractively() a second time on the device path.
- Slim the startBrowser adapter closure to a direct return — the
  typed-nil interface on error is harmless since callers check err
  first.
- Move the SSH_* env blanking from the one browser-flow integration
  test into startLoginProcess, so future login subprocess tests are
  SSH-safe by default (same centralization idea as GitIsolatedEnv).
- Share one noopOpenURL test helper instead of seven inline lambdas.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 224fb4e17934
login: harden the browser sign-in fallbacks
@toothbrush
toothbrush merged commit 6607bbb into main Jun 11, 2026
9 checks passed
@toothbrush
toothbrush deleted the login-loopback-browser-flow branch June 11, 2026 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants