github.com/openshift-online/ocm-sdk-go@v0.1.473/authentication/auth.go (about) 1 package authentication 2 3 import ( 4 "context" 5 "crypto/tls" 6 "fmt" 7 "io" 8 "log" 9 "net/http" 10 "net/url" 11 "sync" 12 "time" 13 14 "github.com/skratchdot/open-golang/open" 15 "golang.org/x/oauth2" 16 ) 17 18 var ( 19 conf *oauth2.Config 20 ctx context.Context 21 verifier string 22 authToken string 23 ) 24 25 const ( 26 RedirectURL = "http://127.0.0.1" 27 RedirectPort = "9998" 28 DefaultAuthURL = "https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/auth" 29 CallbackHandler = "/oauth/callback" 30 ) 31 32 func callbackHandler(w http.ResponseWriter, r *http.Request) { 33 queryParts, _ := url.ParseQuery(r.URL.RawQuery) 34 35 // Use the authorization code that is pushed to the redirect URL 36 code := queryParts["code"][0] 37 38 // Exchange will do the handshake to retrieve the initial token. 39 tok, err := conf.Exchange(ctx, code, oauth2.VerifierOption(verifier)) 40 if err != nil { 41 log.Fatal(err) 42 } 43 44 // Get the refresh token and ask user to go back to CLI 45 authToken = tok.RefreshToken 46 _, err = io.WriteString(w, "Login successful! Please close this window and return back to CLI") 47 if err != nil { 48 log.Fatal(err) 49 } 50 } 51 52 func serve(wg *sync.WaitGroup) *http.Server { 53 server := &http.Server{Addr: fmt.Sprintf(":%s", RedirectPort)} 54 http.HandleFunc(CallbackHandler, callbackHandler) 55 go func() { 56 defer wg.Done() // let main know we are done cleaning up 57 58 // always returns error. ErrServerClosed on graceful close 59 if err := server.ListenAndServe(); err != http.ErrServerClosed { 60 // unexpected error. port in use? 61 log.Fatalf("ListenAndServe(): %v", err) 62 } 63 }() 64 65 // returning reference so caller can call Shutdown() 66 return server 67 } 68 69 func shutdown(server *http.Server) { 70 if err := server.Shutdown(context.TODO()); err != nil { 71 log.Fatalf("HTTP shutdown error: %v", err) 72 } 73 } 74 75 func InitiateAuthCode(clientID string) (string, error) { 76 authToken = "" 77 ctx = context.Background() 78 // Create config for OAuth2, redirect to localhost for callback verification and retrieving tokens 79 conf = &oauth2.Config{ 80 ClientID: clientID, 81 ClientSecret: "", 82 Scopes: []string{"openid"}, 83 Endpoint: oauth2.Endpoint{ 84 AuthURL: DefaultAuthURL, 85 TokenURL: DefaultTokenURL, 86 }, 87 RedirectURL: fmt.Sprintf("%s:%s%s", RedirectURL, RedirectPort, CallbackHandler), 88 } 89 verifier = oauth2.GenerateVerifier() 90 91 // add transport for self-signed certificate to context 92 tr := &http.Transport{ 93 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 94 } 95 sslcli := &http.Client{Transport: tr} 96 ctx = context.WithValue(ctx, oauth2.HTTPClient, sslcli) 97 98 // Create URL with PKCE 99 url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(verifier)) 100 101 httpServerExitDone := &sync.WaitGroup{} 102 103 httpServerExitDone.Add(1) 104 server := serve(httpServerExitDone) 105 106 err := open.Run(url) 107 if err != nil { 108 return authToken, err 109 } 110 fiveMinTimer := time.Now().Local().Add(time.Minute * 5) 111 112 // Wait for the user to finish auth process, and return back with authToken. Otherwise, return an error after 5 mins 113 for { 114 if authToken != "" { 115 shutdown(server) 116 return authToken, nil 117 } 118 if time.Now().After(fiveMinTimer) { 119 shutdown(server) 120 return authToken, fmt.Errorf("time expired") 121 } 122 } 123 }