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  }