Skip to content

feat(auth): add --tunnel flag for OAuth login on headless machines#36

Open
danikhan632 wants to merge 3 commits into
lox:mainfrom
danikhan632:feat/auth-tunnel-login
Open

feat(auth): add --tunnel flag for OAuth login on headless machines#36
danikhan632 wants to merge 3 commits into
lox:mainfrom
danikhan632:feat/auth-tunnel-login

Conversation

@danikhan632
Copy link
Copy Markdown

Summary

  • Adds notion-cli auth login --tunnel which uses localtunnel to expose the OAuth callback server via a public HTTPS URL
  • Enables OAuth login from headless machines or remote servers that don't have a local browser — just copy the auth URL to any browser on any device
  • Implements a pure Go localtunnel client with zero new dependencies

Motivation

The current OAuth flow binds the callback server to 127.0.0.1, which requires a local browser to complete the redirect. This doesn't work when SSH'd into a remote server or running in a headless environment without a browser. The --tunnel flag transparently tunnels the callback so the redirect works from any browser on any machine.

How it works

  1. Local callback server starts on a random port (same as today)
  2. --tunnel opens a localtunnel that maps a public https://xxx.loca.lt URL to that local port
  3. The tunnel URL is registered as the OAuth redirect URI
  4. User copies the auth URL into any browser (even on a different machine), authenticates with Notion
  5. Notion redirects to the tunnel URL → proxied back to the local callback server
  6. Token is saved locally as usual

Usage

notion-cli auth login --tunnel

Without --tunnel, behavior is unchanged.

Test plan

  • go vet ./... passes
  • All existing tests pass (no regressions)
  • New tunnel package tests pass (mock server, HTTP proxying, graceful shutdown, error cases)
  • Manual: notion-cli auth login --tunnel from a remote machine
  • Manual: notion-cli auth login still works as before

🤖 Generated with Claude Code

…ines

Adds `notion-cli auth login --tunnel` which uses localtunnel to expose
the OAuth callback server via a public HTTPS URL. This enables OAuth
login from headless machines or remote servers that don't have a local
browser available.

Implements a pure Go localtunnel client with no new dependencies.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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: fc205bc018

ℹ️ 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".

Comment thread internal/mcp/oauth.go Outdated
return fmt.Errorf("start tunnel: %w", err)
}
defer tun.Close()
redirectURI = tun.URL + callbackPath
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make tunneled OAuth callback unguessable

When tunnel mode is enabled, the redirect is exposed as a public fixed path (<tunnel-url>/callback), so any unsolicited hit that arrives first can be consumed by the callback handler and cause the login flow to fail with state/code errors before the real Notion redirect arrives. This was much less likely in the loopback-only flow, but with a public tunnel URL it becomes a practical denial-of-login vector; use a per-attempt unguessable callback path (or ignore mismatched state and keep waiting) for --tunnel.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. Fixed in d188c16 — the callback path is now /callback/<random-nonce> when tunnel mode is active, so unsolicited requests to the public URL get a 404 instead of consuming the callback.

When the callback server is exposed via a public tunnel URL, the fixed
/callback path is guessable. Generate a random per-attempt nonce so the
callback path becomes /callback/<nonce>, preventing unsolicited requests
from consuming the callback before the real OAuth redirect arrives.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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: d188c16a2f

ℹ️ 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".

Comment thread internal/mcp/oauth.go
Comment on lines +89 to +93
if opts.Tunnel {
fmt.Println("Starting tunnel...")
tun, err := tunnel.Start(ctx, port)
if err != nil {
return fmt.Errorf("start tunnel: %w", err)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Check existing auth before opening the tunnel

Starting the tunnel before mcpClient.Initialize makes auth login --tunnel depend on localtunnel availability even when no OAuth flow is needed. If a user is already authenticated (the Initialize call would succeed), this path now fails early at tunnel.Start(...) on offline/restricted networks instead of returning Already authenticated!, which is a regression introduced by this ordering.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in a28e9d5 — added an isAuthenticated pre-check at the top of RunOAuthFlow that tries a quick Initialize before starting any listener or tunnel. If credentials are already valid, it returns immediately without touching localtunnel.

Moves the authentication check before tunnel/listener setup so that
`auth login --tunnel` doesn't depend on localtunnel availability when
the user already has valid credentials.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@danikhan632
Copy link
Copy Markdown
Author

@lox Can I get a review on this PR?

@lox
Copy link
Copy Markdown
Owner

lox commented Apr 27, 2026

Assuming https://github.com/localtunnel/go-localtunnel is out of date?

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.

2 participants