Skip to content

login: default to auth.entire.io and follow its regional handoff - #1945

Merged
Soph merged 8 commits into
mainfrom
glittery-gliding-pretzel
Aug 17, 2026
Merged

Soph merged 8 commits into
mainfrom
glittery-gliding-pretzel

Conversation

@toothbrush

@toothbrush toothbrush commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

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

Why

https://auth.entire.io (entire-core global mode) now dispatches logins to the caller's regional core (https://github.com/entirehq/entiredb/pull/3135): identify-first 302 on /authorize, geo 307 on /device_authorization. That removes the main need for --server — but the apex serves no token endpoint, so the CLI must finish each flow against the region that actually minted the code.

What

  • entire login defaults to https://auth.entire.io (--server stays for special cases).
  • Browser flow reads the RFC 9207 iss from the loopback callback and exchanges the code at that regional host; device flow polls the origin that served the 307'd response.
  • Token iss acceptance: exactly the dialled origin, or a strict https subdomain of it on the same port (us.auth.entire.io under auth.entire.io; auth.entire.io.evil.com, xauth.entire.io, scheme/port downgrades all rejected). The mismatch error only offers the subdomain shape when the dialled origin is https, since a plaintext dev/loopback server can never delegate.
  • Success line prints the actual issuer when it differs from the dialled host. Existing logins unaffected: contexts/refresh already key off the persisted issuer.
  • auth-go pinned to authcode, deviceflow: support region-dispatching authorization servers auth-go#20 by commit (v0.5.3-0.20260814125012-86c083be00da) — swap to tagged v0.5.3 once released.
  • Go 1.26.5 → 1.26.6 (go.mod + mise.toml). Required: that auth-go commit raises its own minimum to 1.26.6. It also clears six stdlib advisories this CLI shares with auth-go — GO-2026-6218 (net/url), GO-2026-6090 + GO-2026-5856 (crypto/tls), GO-2026-6089 + GO-2026-5026 (net/http), GO-2026-5972 (encoding/asn1).

Verification

  • mise run lint: 0 issues; go test ./cmd/entire/cli/ ./cmd/entire/cli/auth/ ./cmd/entire/cli/api/ pass on go1.26.6.
  • New tests: login_issuer_test.go (issuer-rule table incl. hostile cases, plus the error-wording rule), auth/client_dispatch_test.go (httptest apex 307→regional device flow; loopback callback with iss → regional exchange; apex 404s everything else so a missed handoff fails loudly).
  • Pre-deploy behaviour is a clean failure, not a hang: against the not-yet-live apex, entire login --device exits with start login: start device auth: status 404: 404 page not found.
  • Rebased onto main after feat(login): add explicit URL actions #1961 landed the interactive login-URL prompt, which moved flow.Wait onto its own goroutine. Audited every exit path in waitForLoginURLResult: each returns via a waitCh receive, so the channel supplies the happens-before edge for the subsequent flow.Issuer() read. Device flow sets TokenBaseURL before the polling goroutine is spawned.
  • Not verifiable until the server deploys: live apex 302/307 behavior and entire-cli client registration on non-us regions.

🤖 Generated with Claude Code


Note

High Risk
Changes authentication defaults, token endpoint targeting, and issuer delegation policy—security-critical paths with new subdomain trust rules that must not be bypassed by hostile redirects or callback iss values.

Overview
entire login now defaults to https://auth.entire.io instead of a regional host (us.auth.entire.io). The apex only redirects OAuth entry points and has no token endpoint, so the CLI must finish login against the regional core that actually mints tokens.

Device flow: after /device_authorization, the CLI uses ResponseOrigin and UseTokenIssuer so token polls hit the region that answered the redirect, not the apex.

Browser flow: it reads RFC 9207 iss on the loopback callback and redeems the authorization code at that regional host.

Trust rules: adoptIssuer / expanded issMatches allow the dialled apex or a strict HTTPS subdomain on the same port (e.g. us.auth.entire.io under auth.entire.io); lookalike domains, siblings, and HTTP downgrades are rejected. The same rule validates JWT iss before persistence. Success output names the real issuer when it differs from --server.

auth-go is pinned to a newer pre-release for ResponseOrigin and token-base URL overrides. README and upstream-host-resolution docs describe the apex-vs-region model; httptest coverage exercises apex→regional device and browser paths.

Reviewed by Cursor Bugbot for commit 3a24a0b. Configure here.

@toothbrush
toothbrush requested a review from a team as a code owner August 11, 2026 05:38
Copilot AI lite review requested due to automatic review settings August 11, 2026 05:38

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

Updates entire login to default to the apex auth host (https://auth.entire.io) and correctly complete OAuth flows against the regional core that actually issues tokens/codes, while enforcing a strict issuer-delegation policy to prevent hostile handoffs.

Changes:

  • Default login server switched to https://auth.entire.io (dispatcher), with device and browser flows retargeting token exchange/polling to the regional issuer.
  • Added issuer adoption + validation logic (adoptIssuer, enhanced issMatches) to allow only exact-origin or strict-HTTPS-subdomain delegation on the same port.
  • Expanded unit + integration-style tests to cover apex→regional redirects, hostile issuer attempts, and success messaging/persistence behavior.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
README.md Updates local-dev guidance to reflect the new default dispatcher host.
go.mod Bumps github.com/entireio/auth-go to a newer pseudo-version needed for dispatch support.
go.sum Updates module checksums for the auth-go bump.
docs/architecture/upstream-host-resolution.md Documents apex-dispatch vs regional-issuer behavior and the CLI’s issuer trust rules.
cmd/entire/cli/api/base_url.go Changes DefaultAuthBaseURL to https://auth.entire.io and documents dispatcher semantics.
cmd/entire/cli/login.go Implements issuer adoption/retargeting for device + browser flows and validates delegated issuers.
cmd/entire/cli/login_validate_test.go Updates login-server parsing tests for the new default host.
cmd/entire/cli/login_test.go Extends mocks/fakes to support new issuer-retargeting behavior.
cmd/entire/cli/login_issuer_test.go Adds focused tests for issuer matching/adoption, retargeting, and success output.
cmd/entire/cli/auth/provider.go Updates comments to describe fixed-path behavior and apex dispatch constraints.
cmd/entire/cli/auth/client.go Adds token-issuer override support for device flow and exposes callback iss for browser flow.
cmd/entire/cli/auth/client_dispatch_test.go Adds httptest-based end-to-end coverage for apex dispatch behavior in both flows.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cmd/entire/cli/login.go
toothbrush added a commit that referenced this pull request Aug 14, 2026
… can't apply

issMatches always said "expected <origin> or a subdomain of it", but
isDelegatedIssuer only ever accepts a subdomain when both sides are https.
Against a plaintext dev/loopback login server that wording points the reader
at a host that would be rejected too.

Addresses #1945 (comment)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Entire-Checkpoint: 01M0017AGHRB873EM61G0N84AK
@toothbrush
toothbrush force-pushed the glittery-gliding-pretzel branch from 3a24a0b to 47cfb2f Compare August 14, 2026 11:52
toothbrush and others added 5 commits August 14, 2026 15:04
…al handoff

`entire login --server` now defaults to https://auth.entire.io. That host is
a dispatcher, not an issuer: it redirects /authorize and
/device_authorization to the caller's regional core and serves no token
endpoint, no discovery document, and no JWKS. Tokens are always minted by a
region with iss/aud set to that region's host, so the CLI has to discover
the region mid-login and send the token request there.

- Browser flow: the region appends the RFC 9207 `iss` parameter to the
  loopback redirect; runBrowserLogin reads it and redeems the code at that
  host. Posting the exchange to the apex would 404.
- Device flow: the apex 307s POST /device_authorization to the region (307
  preserves method and body); DeviceAuthStart.ResponseOrigin reports which
  host answered and the token poll follows it.

Both handoffs — and the iss claim on the returned token — are gated by one
rule in issMatches: the dialled origin itself, or a strict subdomain of it
over https on the same port. That accepts auth.entire.io -> us.auth.entire.io
and rejects auth.entire.io.evil.com, siblings, port changes, and plaintext
downgrades. A hostile handoff aborts before the authorization code or device
code is sent anywhere.

Nothing downstream changes: RecordLoginContext still keys the context and
keychain slot on the token's own iss, and refresh + RFC 8693 exchange still
target that persisted CoreURL. Login now prints the issuing region when it
differs from the host the user dialled, since that is what later calls hit.

NOTE: go.mod carries a temporary local `replace` for github.com/entireio/auth-go.
The apex support needs auth-go changes (authcode Flow.Issuer /
SetTokenBaseURL, deviceflow DeviceCode.ResponseOrigin / Client.TokenBaseURL);
that must be released and the replace dropped before this can merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the temporary local replace directive with a pseudo-version pin
on entireio/auth-go#20. Swap for a tagged v0.5.3
once released.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 01KZQMJ1BJQDX30J4QJNSKVZT4
… can't apply

issMatches always said "expected <origin> or a subdomain of it", but
isDelegatedIssuer only ever accepts a subdomain when both sides are https.
Against a plaintext dev/loopback login server that wording points the reader
at a host that would be rejected too.

Addresses #1945 (comment)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Entire-Checkpoint: 01M0017AGHRB873EM61G0N84AK
main's interactive URL prompt (#1961) replaced runLogin/runBrowserLogin's
browserOpenFunc parameter with a loginURLInteractor. The issuer tests now
build one via newTestLoginURLInteractor(); with no scripted actions its
readAction blocks until the flow completes, which is what these tests want.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Entire-Checkpoint: 01M0067HPPRSVVQB2TAH6K8YN5
Picks up entireio/auth-go#20 toolchain bump on top
of the regional-dispatch hooks. auth-go now requires Go >= 1.26.6, so `go get`
raised this module's go directive too; mise.toml follows it per the comment
there. The bump clears six stdlib advisories (net/url, crypto/tls x2,
net/http x2, encoding/asn1) that the CLI shared with auth-go.

Still a pseudo-version — swap to tagged v0.5.3 once released.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Entire-Checkpoint: 01M006E3KSMP7GXWZ2SBH9Y7TQ
toothbrush and others added 3 commits August 14, 2026 16:34
Three findings from trail 1006, two fixed and one answered in a comment.

Transport-guard asymmetry (finding 3): BrowserAuthFlow.UseTokenIssuer
delegated straight to auth-go's SetTokenBaseURL, which honours
AllowInsecureHTTP for any host, while the device flow's Client.UseTokenIssuer
refuses to let --insecure-http-auth extend to a runtime-named non-loopback
host. Unreachable today because adoptIssuer demands https on both sides, but
the two paths must not disagree about policy or loosening adoptIssuer would
loosen only one. The browser shim now applies the same rule.

Unasserted test claim (finding 4): TestPersistLogin_DispatchedLoginNamesTheRegion
said it verified the region is recorded as the context's CoreURL, but checked
only the stdout line. It now asserts the persisted CoreURL, which is the half
that makes refresh work.

Token iss validated against the dialled apex, not the adopted region
(finding 2): left as-is, deliberately, and documented on persistLogin. The
suggested tightening — require iss == the adopted issuer — would break correct
cross-region logins: the apex geo-routes POST /device_authorization, so the
region that answers a handoff is not necessarily the region that mints, and
the dialled apex is the only origin both are guaranteed to sit under. Pinning
the token request answers "where may this code go?"; the iss check answers "is
this issuer acceptable at all?".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Entire-Checkpoint: 01M00B533P53MDR7RHFYSSM3R8
Reverses the reasoning in the previous commit, which declined trail finding 2
on the grounds that the minting region may legitimately differ from the region
that answered the handoff. It may not: iss names the *signer*, and a verifier
resolves JWKS from it, so a token can only truthfully claim the region that
actually minted it — which is the region the flow was handed off to. A token
arriving from us.auth.entire.io while claiming eu.auth.entire.io is
misconfiguration, and validating against the dialled apex alone waved it
through because both sit under it, leaving a context whose every later refresh
would fail against a region that never issued the token.

persistLogin now takes the adopted issuer alongside the dialled host and
validates iss against the former when a handoff occurred, the latter when it
did not. The success line still compares against the dialled host, so it keeps
naming the region only when it differs from what the user asked for.

Cross-region minting stays supported and needs no leniency here: the apex
geo-routes POST /device_authorization, so an AU-homed account can be served
and signed by EU. That is expressed by iss=EU plus home_jurisdiction=AU, not
by EU claiming to be AU.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Entire-Checkpoint: 01M00BPKYR96N5K525135K1VHK
@Soph
Soph merged commit dbfa28f into main Aug 17, 2026
11 checks passed
@Soph
Soph deleted the glittery-gliding-pretzel branch August 17, 2026 17:14
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