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 }