github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/internal/authflow/flow.go (about) 1 package authflow 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "net/http" 8 "os" 9 "strings" 10 11 "github.com/cli/cli/api" 12 "github.com/cli/cli/internal/ghinstance" 13 "github.com/cli/cli/pkg/cmdutil" 14 "github.com/cli/cli/pkg/iostreams" 15 "github.com/cli/oauth" 16 ) 17 18 var ( 19 // The "GitHub CLI" OAuth app 20 oauthClientID = "178c6fc778ccc68e1d6a" 21 // This value is safe to be embedded in version control 22 oauthClientSecret = "34ddeff2b558a23d38fba8a6de74f086ede1cc0b" 23 ) 24 25 type iconfig interface { 26 Set(string, string, string) error 27 Write() error 28 } 29 30 func AuthFlowWithConfig(cfg iconfig, IO *iostreams.IOStreams, hostname, notice string, additionalScopes []string) (string, error) { 31 // TODO this probably shouldn't live in this package. It should probably be in a new package that 32 // depends on both iostreams and config. 33 stderr := IO.ErrOut 34 cs := IO.ColorScheme() 35 36 token, userLogin, err := authFlow(hostname, IO, notice, additionalScopes) 37 if err != nil { 38 return "", err 39 } 40 41 err = cfg.Set(hostname, "user", userLogin) 42 if err != nil { 43 return "", err 44 } 45 err = cfg.Set(hostname, "oauth_token", token) 46 if err != nil { 47 return "", err 48 } 49 50 err = cfg.Write() 51 if err != nil { 52 return "", err 53 } 54 55 fmt.Fprintf(stderr, "%s Authentication complete. %s to continue...\n", 56 cs.SuccessIcon(), cs.Bold("Press Enter")) 57 _ = waitForEnter(IO.In) 58 59 return token, nil 60 } 61 62 func authFlow(oauthHost string, IO *iostreams.IOStreams, notice string, additionalScopes []string) (string, string, error) { 63 w := IO.ErrOut 64 cs := IO.ColorScheme() 65 66 httpClient := http.DefaultClient 67 if envDebug := os.Getenv("DEBUG"); envDebug != "" { 68 logTraffic := strings.Contains(envDebug, "api") || strings.Contains(envDebug, "oauth") 69 httpClient.Transport = api.VerboseLog(IO.ErrOut, logTraffic, IO.ColorEnabled())(httpClient.Transport) 70 } 71 72 minimumScopes := []string{"repo", "read:org", "gist"} 73 scopes := append(minimumScopes, additionalScopes...) 74 75 callbackURI := "http://127.0.0.1/callback" 76 if ghinstance.IsEnterprise(oauthHost) { 77 // the OAuth app on Enterprise hosts is still registered with a legacy callback URL 78 // see https://github.com/cli/cli/pull/222, https://github.com/cli/cli/pull/650 79 callbackURI = "http://localhost/" 80 } 81 82 flow := &oauth.Flow{ 83 Hostname: oauthHost, 84 ClientID: oauthClientID, 85 ClientSecret: oauthClientSecret, 86 CallbackURI: callbackURI, 87 Scopes: scopes, 88 DisplayCode: func(code, verificationURL string) error { 89 fmt.Fprintf(w, "%s First copy your one-time code: %s\n", cs.Yellow("!"), cs.Bold(code)) 90 return nil 91 }, 92 BrowseURL: func(url string) error { 93 fmt.Fprintf(w, "- %s to open %s in your browser... ", cs.Bold("Press Enter"), oauthHost) 94 _ = waitForEnter(IO.In) 95 96 // FIXME: read the browser from cmd Factory rather than recreating it 97 browser := cmdutil.NewBrowser(os.Getenv("BROWSER"), IO.Out, IO.ErrOut) 98 if err := browser.Browse(url); err != nil { 99 fmt.Fprintf(w, "%s Failed opening a web browser at %s\n", cs.Red("!"), url) 100 fmt.Fprintf(w, " %s\n", err) 101 fmt.Fprint(w, " Please try entering the URL in your browser manually\n") 102 } 103 return nil 104 }, 105 WriteSuccessHTML: func(w io.Writer) { 106 fmt.Fprintln(w, oauthSuccessPage) 107 }, 108 HTTPClient: httpClient, 109 Stdin: IO.In, 110 Stdout: w, 111 } 112 113 fmt.Fprintln(w, notice) 114 115 token, err := flow.DetectFlow() 116 if err != nil { 117 return "", "", err 118 } 119 120 userLogin, err := getViewer(oauthHost, token.Token) 121 if err != nil { 122 return "", "", err 123 } 124 125 return token.Token, userLogin, nil 126 } 127 128 func getViewer(hostname, token string) (string, error) { 129 http := api.NewClient(api.AddHeader("Authorization", fmt.Sprintf("token %s", token))) 130 return api.CurrentLoginName(http, hostname) 131 } 132 133 func waitForEnter(r io.Reader) error { 134 scanner := bufio.NewScanner(r) 135 scanner.Scan() 136 return scanner.Err() 137 }