-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add interactive TUI-based OAuth flows with environment detection #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ This mirrors the authentication strategy used by **GitHub CLI**, **Azure CLI**, | |
| - [Why This CLI?](#why-this-cli) | ||
| - [Quick Start](#quick-start) | ||
| - [How It Works](#how-it-works) | ||
| - [Interactive Terminal UI](#interactive-terminal-ui) | ||
| - [Configuration](#configuration) | ||
| - [Authentication Flows](#authentication-flows) | ||
| - [Token Storage](#token-storage) | ||
|
|
@@ -154,6 +155,90 @@ On each run the CLI follows this order: | |
|
|
||
| --- | ||
|
|
||
| ## Interactive Terminal UI | ||
|
|
||
| Authgate CLI features a rich **interactive Terminal User Interface (TUI)** built with [Bubble Tea](https://github.com/charmbracelet/bubbletea), providing visual feedback during OAuth authentication flows. | ||
|
|
||
| ### Features | ||
|
|
||
| The TUI provides: | ||
|
|
||
| - **Visual Progress Indicators**: Step-by-step progress with animated spinners | ||
| - **Real-time Timers**: Countdown for browser flow, elapsed time for device flow | ||
| - **Progress Bars**: Visual representation of callback timeout | ||
| - **Polling Status**: Live updates showing device flow polling count and intervals | ||
| - **Backoff Warnings**: Clear notifications when server requests slower polling | ||
| - **Clean Layout**: Bordered boxes, color-coded messages, and structured information | ||
|
|
||
| ### Browser Flow (Authorization Code + PKCE) | ||
|
|
||
| ``` | ||
| ╭─────────────────────────────────────────────────╮ | ||
| │ Authorization Code Flow with PKCE │ | ||
| ├─────────────────────────────────────────────────┤ | ||
| │ │ | ||
| │ ● Step 1/3: Opening browser ✓ │ | ||
| │ ● Step 2/3: Waiting for callback ◐ │ | ||
| │ ○ Step 3/3: Exchanging tokens │ | ||
| │ │ | ||
| │ Time remaining: 1:23 / 2:00 │ | ||
| │ ▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░ 48% │ | ||
| │ │ | ||
| │ ◐ Please complete authorization in your browser │ | ||
| │ │ | ||
| │ Press Ctrl+C to cancel │ | ||
| ╰─────────────────────────────────────────────────╯ | ||
| ``` | ||
|
|
||
| ### Device Flow (Device Authorization Grant) | ||
|
|
||
| ``` | ||
| ╭─────────────────────────────────────────────────╮ | ||
| │ Device Authorization Grant Flow │ | ||
| ├─────────────────────────────────────────────────┤ | ||
| │ │ | ||
| │ ╔═══════════════════════════════════════════╗ │ | ||
| │ ║ Device Authorization ║ │ | ||
| │ ║ ║ │ | ||
| │ ║ Visit: https://auth.example.com/device ║ │ | ||
| │ ║ ?user_code=ABCD-EFGH ║ │ | ||
| │ ║ ║ │ | ||
| │ ║ Or go to: https://auth.example.com ║ │ | ||
| │ ║ And enter: ABCD-EFGH ║ │ | ||
| │ ╚═══════════════════════════════════════════╝ │ | ||
| │ │ | ||
| │ ◐ Waiting for authorization... (poll #8, 5s) │ | ||
| │ │ | ||
| │ Elapsed: 0:43 │ | ||
| │ │ | ||
| │ Press Ctrl+C to cancel │ | ||
| ╰─────────────────────────────────────────────────╯ | ||
| ``` | ||
|
|
||
| ### UI Mode Selection | ||
|
|
||
| The CLI automatically chooses the appropriate UI mode: | ||
|
|
||
| **Interactive TUI Mode** (default): | ||
|
|
||
| - Normal terminal with sufficient size (≥60x20) | ||
| - TTY detected | ||
| - TERM environment variable set (not "dumb") | ||
|
|
||
| **Simple Printf Mode** (automatic fallback): | ||
|
|
||
| - CI environments (GitHub Actions, GitLab CI, CircleCI, etc.) | ||
| - Output piped to file or another command | ||
| - Terminal too small (< 60 columns or < 20 rows) | ||
| - `TERM=dumb` or TERM unset | ||
| - SSH session without display forwarding | ||
|
|
||
| ### Note on UI Selection | ||
|
|
||
| The CLI automatically detects the environment and selects the appropriate UI mode. No configuration or flags are needed - it just works. | ||
|
|
||
|
Comment on lines
+236
to
+239
|
||
| --- | ||
|
|
||
| ## Configuration | ||
|
|
||
| Configuration is resolved in priority order: **CLI flag → environment variable → default**. | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,8 @@ import ( | |||||||||||||||||||||||||||||||||||||||||
| "net/url" | ||||||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| "github.com/go-authgate/cli/tui" | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // performBrowserFlow runs the Authorization Code Flow with PKCE. | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -162,3 +164,133 @@ func exchangeCode(ctx context.Context, code, codeVerifier string) (*TokenStorage | |||||||||||||||||||||||||||||||||||||||||
| ClientID: clientID, | ||||||||||||||||||||||||||||||||||||||||||
| }, nil | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // performBrowserFlowWithUpdates runs the Authorization Code Flow with PKCE | ||||||||||||||||||||||||||||||||||||||||||
| // and sends progress updates through the provided channel. | ||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||
| // Returns: | ||||||||||||||||||||||||||||||||||||||||||
| // - (storage, true, nil) on success | ||||||||||||||||||||||||||||||||||||||||||
| // - (nil, false, nil) when openBrowser() fails — caller should fall back to Device Code Flow | ||||||||||||||||||||||||||||||||||||||||||
| // - (nil, false, err) on a hard error (CSRF mismatch, token exchange failure, etc.) | ||||||||||||||||||||||||||||||||||||||||||
| func performBrowserFlowWithUpdates( | ||||||||||||||||||||||||||||||||||||||||||
| ctx context.Context, | ||||||||||||||||||||||||||||||||||||||||||
| updates chan<- tui.FlowUpdate, | ||||||||||||||||||||||||||||||||||||||||||
| ) (*tui.TokenStorage, bool, error) { | ||||||||||||||||||||||||||||||||||||||||||
| updates <- tui.FlowUpdate{ | ||||||||||||||||||||||||||||||||||||||||||
| Type: tui.StepStart, | ||||||||||||||||||||||||||||||||||||||||||
| Step: 1, | ||||||||||||||||||||||||||||||||||||||||||
| TotalSteps: 3, | ||||||||||||||||||||||||||||||||||||||||||
| Message: "Generating PKCE parameters", | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| state, err := generateState() | ||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||
| return nil, false, fmt.Errorf("failed to generate state: %w", err) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| pkce, err := GeneratePKCE() | ||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+187
to
+192
|
||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | |
| return nil, false, fmt.Errorf("failed to generate state: %w", err) | |
| } | |
| pkce, err := GeneratePKCE() | |
| if err != nil { | |
| if err != nil { | |
| updates <- tui.FlowUpdate{ | |
| Type: tui.StepError, | |
| Message: fmt.Sprintf("Failed to generate state: %v", err), | |
| } | |
| return nil, false, fmt.Errorf("failed to generate state: %w", err) | |
| } | |
| pkce, err := GeneratePKCE() | |
| if err != nil { | |
| updates <- tui.FlowUpdate{ | |
| Type: tui.StepError, | |
| Message: fmt.Sprintf("Failed to generate PKCE: %v", err), | |
| } |
Copilot
AI
Feb 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The timer goroutine can block sending to the updates channel; if the manager closes updates while this goroutine is mid-send, it can panic (send on closed channel) or leak. Consider making the send non-blocking (select on ctx/done), or have the manager own timer ticks instead of spawning an extra sender inside the flow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The UI auto-selection bullets include “SSH session without display forwarding”, but shouldUseSimpleUI() doesn’t check SSH/DISPLAY; that condition currently affects browser flow availability (checkBrowserAvailability), not UI mode. Consider either implementing this check in shouldUseSimpleUI or adjusting the documentation to match the actual behavior.