oauth

package
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 27, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package oauth implements the user-facing OAuth 2.1 login flows the stdio server uses to obtain a GitHub token without a pre-provisioned Personal Access Token.

It supports both GitHub OAuth Apps and GitHub Apps (user-to-server). The only practical difference is that GitHub App user tokens expire and carry a refresh token; this package always returns a refreshing golang.org/x/oauth2.TokenSource so callers never have to special-case the app type.

The package depends only on golang.org/x/oauth2 and the standard library. MCP concerns (sessions, elicitation) are abstracted behind the Prompter interface so the flows can be tested without a live client.

Index

Constants

View Source
const DefaultAuthTimeout = 5 * time.Minute

DefaultAuthTimeout bounds how long a single authorization attempt waits for the user to complete the browser or device flow.

Variables

View Source
var ErrPromptDeclined = errors.New("authorization declined by user")

ErrPromptDeclined is returned by a Prompter when the user actively cancels or declines the authorization prompt. It is a deliberate "no", so the flow stops rather than falling back to another channel.

View Source
var ErrPromptUnavailable = errors.New("authorization prompt could not be delivered")

ErrPromptUnavailable is returned by a Prompter when the prompt could not be delivered at all — for example the client advertised an elicitation capability but the request failed at the transport or protocol level. Unlike ErrPromptDeclined it reflects no user decision, so the flow falls back to a channel that needs no client capability instead of giving up.

Functions

func GitHubEndpoint

func GitHubEndpoint(host string) oauth2.Endpoint

GitHubEndpoint returns the OAuth authorization, token, and device endpoints for a GitHub host. An empty host targets github.com.

func NormalizeHost

func NormalizeHost(host string) string

NormalizeHost turns a user-supplied host into a scheme+host base URL with no trailing slash. The API subdomain is stripped because OAuth endpoints live on the web host, not the API host (api.github.com -> github.com). An empty host yields the github.com default, so callers can also use it to recognize the default host (NormalizeHost(host) == "https://github.com").

Types

type Config

type Config struct {
	ClientID     string
	ClientSecret string
	// Scopes requested during authorization. GitHub Apps ignore these (their
	// access is governed by installed permissions); OAuth Apps honor them.
	Scopes []string
	// Endpoint holds the authorization, token, and device endpoints. Build one
	// with [GitHubEndpoint].
	Endpoint oauth2.Endpoint
	// CallbackPort is the fixed local port for the PKCE callback server. Zero
	// requests a random port, which is the secure default for native binaries
	// but cannot be reached through Docker port mapping (see the Manager).
	CallbackPort int
}

Config describes an OAuth client and the GitHub endpoints it talks to.

func NewGitHubConfig

func NewGitHubConfig(clientID, clientSecret string, scopes []string, host string, callbackPort int) Config

NewGitHubConfig builds a Config for the given GitHub host. An empty host targets github.com; otherwise the host may be a GHES or ghe.com hostname, with or without a scheme.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager owns the OAuth login flows and the resulting (refreshing) token for a single stdio session. It is safe for concurrent use; only one authorization flow runs at a time.

func NewManager

func NewManager(cfg Config, logger *slog.Logger) *Manager

NewManager builds a Manager for the given configuration. A nil logger logs to stderr.

func (*Manager) AccessToken

func (m *Manager) AccessToken() string

AccessToken returns a currently valid access token, refreshing it if needed, or "" if the session is not authorized (or a refresh has failed and re-authorization is required). It is cheap to call repeatedly: the underlying token source caches and only refreshes when the token has expired.

func (*Manager) Authenticate

func (m *Manager) Authenticate(ctx context.Context, prompter Prompter) (*Outcome, error)

Authenticate ensures the session is authorized.

It returns (nil, nil) once a token is available, so the caller may proceed. It returns (&Outcome{UserAction}, nil) when the user must complete the flow out of band; the flow continues in the background and the caller should show the action and have the user retry. It returns (nil, err) on failure.

Only one flow runs at a time. Concurrent callers either join a running secure flow, receive the pending user action, or are told to retry shortly.

func (*Manager) HasToken

func (m *Manager) HasToken() bool

HasToken reports whether a valid token is currently available.

type Outcome

type Outcome struct {
	// UserAction, when non-nil, must be surfaced to the user. The authorization
	// flow continues in the background; the user should retry once they have
	// completed it.
	UserAction *UserAction
}

Outcome reports the result of an authorization attempt that did not immediately yield a token.

type Prompt

type Prompt struct {
	// Message is a human-readable instruction.
	Message string
	// URL is the authorization URL (PKCE) or device verification URI.
	URL string
	// UserCode is the device-flow code the user must enter, if any.
	UserCode string
}

Prompt is the content shown to the user when asking them to authorize.

type Prompter

type Prompter interface {
	// CanPromptURL reports whether the client can display a URL securely via
	// URL-mode elicitation.
	CanPromptURL() bool

	// PromptURL securely presents an authorization URL to the user and blocks
	// until the user acknowledges, declines, or ctx is done. Returning nil means
	// the prompt was shown (not that authorization completed); the caller waits
	// for the OAuth flow itself to finish. It returns ErrPromptDeclined if the
	// user declines or cancels, or ErrPromptUnavailable if the prompt could not
	// be delivered.
	PromptURL(ctx context.Context, p Prompt) error

	// CanPromptForm reports whether the client supports form elicitation, used
	// to display a device code when URL elicitation is unavailable.
	CanPromptForm() bool

	// PromptForm presents a textual acknowledgement prompt and blocks until the
	// user responds. It returns ErrPromptDeclined if the user declines, or
	// ErrPromptUnavailable if the prompt could not be delivered.
	PromptForm(ctx context.Context, p Prompt) error
}

Prompter presents authorization prompts to the user out of band from the LLM context — for example via MCP elicitation. Keeping prompts out of the model's context prevents the authorization URL (and any session-bound state) from leaking into tool arguments or transcripts.

A nil Prompter is valid and reports no capabilities, which drives the flow to its last-resort channel. Implementations wrap a transport-specific client (e.g. an MCP session); see the ghmcp adapter.

type UserAction

type UserAction struct {
	// Message is ready to display to the user.
	Message string
	// URL is the authorization URL or device verification URI.
	URL string
	// UserCode is the device-flow code to enter, if any.
	UserCode string
}

UserAction is an instruction for the user to complete authorization out of band (the last-resort channel, used when neither a browser nor URL elicitation is available).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL