Skip to content

Remote/headless OAuth authentication (#194)#198

Merged
jeremy merged 2 commits intomainfrom
oauth-remote-device
Mar 5, 2026
Merged

Remote/headless OAuth authentication (#194)#198
jeremy merged 2 commits intomainfrom
oauth-remote-device

Conversation

@jeremy
Copy link
Copy Markdown
Member

@jeremy jeremy commented Mar 5, 2026

Summary

  • Auto-detect SSH sessions and prompt users to paste the callback URL instead of starting a loopback listener that can't be reached from a remote browser
  • Add --remote flag to force paste-mode, --local flag to override SSH auto-detection
  • CLI-only change — no SDK or server-side dependencies

Details

basecamp auth login starts a loopback HTTP listener on 127.0.0.1:8976. On remote/SSH sessions the browser runs on the user's local machine — the redirect hits local 127.0.0.1, never reaches the remote's listener, and auth hangs forever.

Remote mode flow:

  1. Detect SSH via SSH_CONNECTION/SSH_CLIENT/SSH_TTY env vars (or --remote flag)
  2. Print the auth URL for the user to open in any browser
  3. After sign-in, the browser redirects to the loopback URL which shows a connection error
  4. User copies the full URL from the address bar and pastes it back into the CLI
  5. CLI extracts the authorization code and completes the token exchange

Changes:

  • internal/hostutil/hostutil.goIsRemoteSession() SSH detection
  • internal/auth/auth.goLoginOptions.Remote/Local/InputReader, parseCallbackURL, readCallbackInput, Login() branching
  • internal/commands/auth.go--remote/--local flags on login
  • internal/commands/profile.go--remote/--local flags on profile create

Test plan

  • make check passes (fmt, vet, lint, test, e2e)
  • go test -race ./internal/auth/ passes — thread-safe log capture, deterministic channel-based synchronization (no sleeps)
  • e2e: basecamp auth login --help and basecamp login --help show --remote/--local
  • Unit tests cover: parseCallbackURL (9 cases), readCallbackInput timeout/cancel, remote login integration (success, state mismatch, empty input, custom redirect URI), SSH auto-detection, --local override, mutual exclusivity validation, profile create flag presence

Closes #194

Copilot AI review requested due to automatic review settings March 5, 2026 08:02
@github-actions github-actions bot added commands CLI command implementations tests Tests (unit and e2e) auth OAuth authentication enhancement New feature or request labels Mar 5, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 05f63a45ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

Copilot AI left a comment

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 implements remote/headless OAuth authentication for basecamp auth login, solving the long-standing issue (#194) where the CLI hangs indefinitely when used over SSH because the loopback callback listener is unreachable from the remote browser.

Changes:

  • SSH auto-detection + override flags: IsRemoteSession() checks SSH env vars; --remote/--local flags allow explicit control. --remote/--local are mutually exclusive, enforced both via Cobra and in the Login() function itself.
  • Paste-mode flow: In remote mode, the CLI prompts the user to open the auth URL in any browser, then paste the full callback URL back. parseCallbackURL handles whitespace/quote stripping, state validation, and OAuth error responses.
  • Context-aware input: readCallbackInput uses a goroutine + buffered channel to support a 5-minute timeout over any io.Reader.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
internal/hostutil/hostutil.go Adds IsRemoteSession() SSH detection via env vars
internal/hostutil/hostutil_test.go Unit tests for IsRemoteSession()
internal/auth/auth.go Core remote flow: new LoginOptions fields, defaults() auto-detection, Login() branching, parseCallbackURL, readCallbackInput
internal/auth/auth_test.go Comprehensive tests: parseCallbackURL (9 cases), readCallbackInput (timeout/cancel), remote login integration, SSH auto-detect/override
internal/commands/auth.go Adds --remote/--local flags to auth login and login commands
internal/commands/profile.go Adds --remote/--local flags to profile create command
internal/commands/profile_test.go Adds "remote", "local" to expected flags list
e2e/auth.bats E2E tests verifying --remote/--local appear in help output

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jeremy jeremy force-pushed the oauth-remote-device branch from 05f63a4 to e82d65a Compare March 5, 2026 09:26
jeremy added 2 commits March 5, 2026 06:43
On SSH sessions the loopback OAuth callback can't reach the remote
host's listener, so auth hangs forever. This adds a remote mode that
prompts the user to paste the callback URL instead.

- Add hostutil.IsRemoteSession() detecting SSH_CONNECTION/CLIENT/TTY
- Add LoginOptions.Remote/Local/InputReader fields
- Auto-detect SSH in defaults(); --local overrides detection
- Add parseCallbackURL (strips quotes, validates state/error params)
- Add readCallbackInput with context cancellation (distinct messages
  for timeout vs cancel)
- Branch Login() into remote (paste prompt) vs local (listener) paths
Add --remote and --local flags to `basecamp login`, `basecamp auth
login`, and `basecamp profile create`. Cobra enforces mutual
exclusivity at the flag-parsing level.
Copilot AI review requested due to automatic review settings March 5, 2026 14:45
@jeremy jeremy force-pushed the oauth-remote-device branch from e82d65a to b5fbf98 Compare March 5, 2026 14:45
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jeremy jeremy merged commit d129bc8 into main Mar 5, 2026
25 checks passed
@jeremy jeremy deleted the oauth-remote-device branch March 5, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth OAuth authentication commands CLI command implementations enhancement New feature or request tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tool won’t authenticate on remote connection

2 participants