// Package oauth is a library for Go client applications that need to perform OAuth authorization // against a server, typically GitHub.com. package oauth import ( "errors" "fmt" "io" "net/http" "net/url" "strings" "github.com/cli/oauth/api" "github.com/cli/oauth/device" ) type httpClient interface { PostForm(string, url.Values) (*http.Response, error) } // Host defines the endpoints used to authorize against an OAuth server. type Host struct { DeviceCodeURL string AuthorizeURL string TokenURL string } // NewGitHubHost constructs a Host from the given URL to a GitHub instance. func NewGitHubHost(hostURL string) (*Host, error) { base, err := url.Parse(strings.TrimSpace(hostURL)) if err != nil { return nil, err } createURL := func(path string) string { u := *base // Copy base URL u.Path = path return u.String() } return &Host{ DeviceCodeURL: createURL("/login/device/code"), AuthorizeURL: createURL("/login/oauth/authorize"), TokenURL: createURL("/login/oauth/access_token"), }, nil } // GitHubHost constructs a Host from the given URL to a GitHub instance. // // Deprecated: `GitHubHost` can panic with a malformed `hostURL`. Use `NewGitHubHost` instead for graceful error handling. func GitHubHost(hostURL string) *Host { u, _ := url.Parse(hostURL) return &Host{ DeviceCodeURL: fmt.Sprintf("%s://%s/login/device/code", u.Scheme, u.Host), AuthorizeURL: fmt.Sprintf("%s://%s/login/oauth/authorize", u.Scheme, u.Host), TokenURL: fmt.Sprintf("%s://%s/login/oauth/access_token", u.Scheme, u.Host), } } // Flow facilitates a single OAuth authorization flow. type Flow struct { // The hostname to authorize the app with. // // Deprecated: Use Host instead. Hostname string // Host configuration to authorize the app with. Host *Host // OAuth scopes to request from the user. Scopes []string // OAuth audience to request from the user. Audience string // OAuth application ID. ClientID string // OAuth application secret. Only applicable in web application flow. ClientSecret string // The localhost URI for web application flow callback, e.g. "http://127.0.0.1/callback". CallbackURI string // Display a one-time code to the user. Receives the code and the browser URL as arguments. Defaults to printing the // code to the user on Stdout with instructions to copy the code and to press Enter to continue in their browser. DisplayCode func(string, string) error // Open a web browser at a URL. Defaults to opening the default system browser. BrowseURL func(string) error // Render an HTML page to the user upon completion of web application flow. The default is to // render a simple message that informs the user they can close the browser tab and return to the app. WriteSuccessHTML func(io.Writer) // The HTTP client to use for API POST requests. Defaults to http.DefaultClient. HTTPClient httpClient // The stream to listen to keyboard input on. Defaults to os.Stdin. Stdin io.Reader // The stream to print UI messages to. Defaults to os.Stdout. Stdout io.Writer } // DetectFlow tries to perform Device flow first and falls back to Web application flow. func (oa *Flow) DetectFlow() (*api.AccessToken, error) { accessToken, err := oa.DeviceFlow() if errors.Is(err, device.ErrUnsupported) { return oa.WebAppFlow() } return accessToken, err }