code-intelligence.com/cifuzz@v0.40.0/internal/cmdutils/auth/auth.go (about)

     1  package auth
     2  
     3  import (
     4  	"net/url"
     5  	"os"
     6  
     7  	"github.com/pkg/browser"
     8  	"github.com/pkg/errors"
     9  	"github.com/spf13/viper"
    10  	"golang.org/x/term"
    11  
    12  	"code-intelligence.com/cifuzz/internal/api"
    13  	"code-intelligence.com/cifuzz/internal/tokenstorage"
    14  	"code-intelligence.com/cifuzz/pkg/dialog"
    15  	"code-intelligence.com/cifuzz/pkg/log"
    16  	"code-intelligence.com/cifuzz/pkg/messaging"
    17  )
    18  
    19  // GetToken returns the API access token for the given server.
    20  func GetToken(server string) string {
    21  	// Try the environment variable
    22  	token := os.Getenv("CIFUZZ_API_TOKEN")
    23  	if token != "" {
    24  		log.Print("Using token from $CIFUZZ_API_TOKEN")
    25  		return token
    26  	}
    27  
    28  	// Try the access tokens config file
    29  	return tokenstorage.Get(server)
    30  }
    31  
    32  // GetAuthStatus returns the authentication status of the user
    33  // for the given server.
    34  func GetAuthStatus(server string) (bool, error) {
    35  	// Obtain the API access token
    36  	token := GetToken(server)
    37  
    38  	if token == "" {
    39  		return false, nil
    40  	}
    41  
    42  	// Token might be invalid, so try to authenticate with it
    43  	apiClient := api.APIClient{Server: server}
    44  	err := EnsureValidToken(apiClient, token)
    45  	if err != nil {
    46  		return false, err
    47  	}
    48  
    49  	return true, nil
    50  }
    51  
    52  // readTokenInteractively reads the API access token from the user with an
    53  // interactive dialog prompt.
    54  func readTokenInteractively(server string, additionalParams *url.Values) (string, error) {
    55  	path, err := url.JoinPath(server, "dashboard", "settings", "account", "tokens")
    56  	if err != nil {
    57  		return "", errors.WithStack(err)
    58  	}
    59  
    60  	values := url.Values{}
    61  	values.Set("create", "")
    62  	values.Add("origin", "cli")
    63  
    64  	// Add additional params to existing values
    65  	if additionalParams != nil {
    66  		for key, params := range *additionalParams {
    67  			for _, param := range params {
    68  				values.Add(key, param)
    69  			}
    70  		}
    71  	}
    72  
    73  	url, err := url.Parse(path)
    74  	if err != nil {
    75  		return "", errors.WithStack(err)
    76  	}
    77  	url.RawQuery = values.Encode()
    78  
    79  	log.Printf("You need an API access token which can be generated here:\n%s", url.String())
    80  
    81  	openBrowser, err := dialog.Confirm("Open browser to generate a new token?", true)
    82  	if err != nil {
    83  		return "", err
    84  	}
    85  
    86  	if openBrowser {
    87  		err = browser.OpenURL(url.String())
    88  		if err != nil {
    89  			err = errors.WithStack(err)
    90  			log.Errorf(err, "Failed to open browser: %v", err)
    91  		}
    92  	}
    93  
    94  	token, err := dialog.ReadSecret("Paste your access token")
    95  	if err != nil {
    96  		return "", err
    97  	}
    98  
    99  	return token, nil
   100  }
   101  
   102  // ReadCheckAndStoreTokenInteractively reads the API access token from the
   103  // user, checks if it is valid and stores it.
   104  func ReadCheckAndStoreTokenInteractively(apiClient *api.APIClient, additionalParams *url.Values) (string, error) {
   105  	token, err := readTokenInteractively(apiClient.Server, additionalParams)
   106  	if err != nil {
   107  		return "", err
   108  	}
   109  
   110  	err = CheckAndStoreToken(apiClient, token)
   111  	if err != nil {
   112  		return "", err
   113  	}
   114  
   115  	return token, nil
   116  }
   117  
   118  // EnsureValidToken checks if the token is valid and asks the user to log in
   119  // again if it is not.
   120  func EnsureValidToken(apiClient api.APIClient, token string) error {
   121  	isValid, err := apiClient.IsTokenValid(token)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	if !isValid {
   126  		log.Warn(`Failed to authenticate with the configured API access token.
   127  It's possible that the token has been revoked.`)
   128  
   129  		if viper.GetBool("interactive") && term.IsTerminal(int(os.Stdin.Fd())) {
   130  			tryAgain, err := dialog.Confirm("Do you want to log in again?", true)
   131  			if err != nil {
   132  				return err
   133  			}
   134  			if tryAgain {
   135  				_, err = ReadCheckAndStoreTokenInteractively(&apiClient, nil)
   136  				if err != nil {
   137  					return err
   138  				}
   139  			}
   140  		}
   141  	} else {
   142  		log.Success("You are logged in.")
   143  	}
   144  	return nil
   145  }
   146  
   147  // ShowServerConnectionDialog ask users if they want to use a SaaS backend
   148  // if they are not authenticated and returns their wish to authenticate.
   149  func ShowServerConnectionDialog(server string, context messaging.MessagingContext) (bool, error) {
   150  	additionalParams := messaging.ShowServerConnectionMessage(server, context)
   151  
   152  	wishToAuthenticate, err := dialog.Confirm("Do you want to authenticate?", true)
   153  	if err != nil {
   154  		return false, err
   155  	}
   156  
   157  	if wishToAuthenticate {
   158  		apiClient := api.APIClient{Server: server}
   159  		_, err := ReadCheckAndStoreTokenInteractively(&apiClient, additionalParams)
   160  		if err != nil {
   161  			return false, err
   162  		}
   163  	}
   164  
   165  	return wishToAuthenticate, nil
   166  }
   167  
   168  // CheckAndStoreToken checks if the token is valid and stores it if it is.
   169  func CheckAndStoreToken(apiClient *api.APIClient, token string) error {
   170  	err := EnsureValidToken(*apiClient, token)
   171  	if err != nil {
   172  		return err
   173  	}
   174  	err = tokenstorage.Set(apiClient.Server, token)
   175  	if err != nil {
   176  		return err
   177  	}
   178  	log.Successf("Successfully authenticated with %s", apiClient.Server)
   179  	log.Infof("Your API access token is stored in %s", tokenstorage.GetTokenFilePath())
   180  	return nil
   181  }