diff --git a/pkg/cli/credential_login.go b/pkg/cli/credential_login.go index 0da6d3f15..51d7edbbc 100644 --- a/pkg/cli/credential_login.go +++ b/pkg/cli/credential_login.go @@ -96,52 +96,39 @@ func (a *CredentialLogin) Run(cmd *cobra.Command, args []string) error { return err } - if isManager { - user, pass, err := manager.Login(cmd.Context(), a.Password, serverAddress) - if err != nil { + if !isManager { + if err = survey.Ask(q, a); err != nil { return err } - a.Username = user - a.Password = pass - a.LocalStorage = true - a.SkipChecks = true - } else { - if err := survey.Ask(q, a); err != nil { - return err + + if !a.LocalStorage { + client, err = a.client.CreateDefault() + if err != nil { + return err + } } - } - if !a.LocalStorage { - client, err = a.client.CreateDefault() + store, err := credentials.NewStore(cfg, client) if err != nil { return err } - } - store, err := credentials.NewStore(cfg, client) - if err != nil { - return err - } - - err = store.Add(cmd.Context(), apiv1.Credential{ - ServerAddress: serverAddress, - Username: a.Username, - Password: &a.Password, - LocalStorage: a.LocalStorage, - }, a.SkipChecks) - if err != nil { - return err - } - - if isManager { - // reload config, could have changed - cfg, err = a.client.Options().CLIConfig() + if err = store.Add(cmd.Context(), apiv1.Credential{ + ServerAddress: serverAddress, + Username: a.Username, + Password: &a.Password, + LocalStorage: a.LocalStorage, + }, a.SkipChecks); err != nil { + return err + } + } else { + a.Username, a.Password, err = manager.Login(cmd.Context(), cfg, a.Password, serverAddress) if err != nil { return err } var projectSet bool - def, err := manager.DefaultProject(cmd.Context(), serverAddress, a.Username, a.Password) + def, err := manager.DefaultProject(cmd.Context(), serverAddress, a.Password) if err != nil { return err } diff --git a/pkg/manager/client.go b/pkg/manager/client.go index 5a9f75a99..c295f8e85 100644 --- a/pkg/manager/client.go +++ b/pkg/manager/client.go @@ -6,11 +6,18 @@ import ( "fmt" "io" "net/http" + "runtime" + "github.com/acorn-io/runtime/pkg/version" "github.com/sirupsen/logrus" ) -var ErrTokenNotFound = fmt.Errorf("token not found") +var ( + ErrTokenNotFound = fmt.Errorf("token not found") + ErrForbidden = fmt.Errorf("forbidden") + + userAgent = fmt.Sprintf("acorn/%s (%s; %s)", version.Get().String(), runtime.GOOS, runtime.GOARCH) +) type tokenRequest struct { Spec tokenRequestSpec `json:"spec,omitempty"` @@ -46,11 +53,10 @@ type accountStatus struct { func httpDelete(ctx context.Context, url, token string) { logrus.Debugf("Delete %s", url) - req, err := http.NewRequest(http.MethodDelete, url, nil) + req, err := newRequest(url, http.MethodDelete, token) if err != nil { return } - req.Header.Set("Authorization", "Bearer "+token) resp, err := http.DefaultClient.Do(req.WithContext(ctx)) if err != nil { @@ -61,15 +67,11 @@ func httpDelete(ctx context.Context, url, token string) { func httpGet(ctx context.Context, url, token string, into interface{}) error { logrus.Debugf("Looking up %s", url) - req, err := http.NewRequest(http.MethodGet, url, nil) + req, err := newRequest(url, http.MethodGet, token) if err != nil { return err } - if token != "" { - req.Header.Add("Authorization", "Bearer "+token) - } - resp, err := http.DefaultClient.Do(req.WithContext(ctx)) if err != nil { return err @@ -81,6 +83,8 @@ func httpGet(ctx context.Context, url, token string, into interface{}) error { break case http.StatusNotFound: return fmt.Errorf("%w: %v", ErrTokenNotFound, resp.StatusCode) + case http.StatusForbidden: + return ErrForbidden default: return fmt.Errorf("invalid status code: %v", resp.StatusCode) } @@ -94,3 +98,17 @@ func httpGet(ctx context.Context, url, token string, into interface{}) error { return json.Unmarshal(body, into) } + +func newRequest(url, method, token string) (*http.Request, error) { + req, err := http.NewRequest(method, url, nil) + if err != nil { + return nil, err + } + + if token != "" { + req.Header.Add("Authorization", "Bearer "+token) + } + req.Header.Add("User-Agent", userAgent) + + return req, nil +} diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 9a4fc7871..03db564e3 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -10,7 +10,9 @@ import ( "time" "github.com/acorn-io/baaah/pkg/randomtoken" + apiv1 "github.com/acorn-io/runtime/pkg/apis/api.acorn.io/v1" "github.com/acorn-io/runtime/pkg/config" + "github.com/acorn-io/runtime/pkg/credentials" "github.com/acorn-io/runtime/pkg/system" "github.com/pkg/browser" "github.com/sirupsen/logrus" @@ -77,7 +79,7 @@ func ProjectURL(ctx context.Context, serverAddress, accountName, token string) ( return obj.Status.EndpointURL, nil } -func Login(ctx context.Context, password, address string) (user string, pass string, err error) { +func Login(ctx context.Context, cfg *config.CLIConfig, password, address string) (user string, pass string, err error) { passwordIsSpecified := password != "" if !passwordIsSpecified { password, err = randomtoken.Generate() @@ -106,7 +108,9 @@ func Login(ctx context.Context, password, address string) (user string, pass str } if tokenRequest.Status.Token != "" { httpDelete(ctx, tokenRequestURL, tokenRequest.Status.Token) - return tokenRequest.Spec.AccountName, tokenRequest.Status.Token, nil + user = tokenRequest.Spec.AccountName + pass = tokenRequest.Status.Token + break } else { logrus.Debugf("tokenRequest.Status.Token is empty") } @@ -122,9 +126,31 @@ func Login(ctx context.Context, password, address string) (user string, pass str return "", "", ctx.Err() } } + + store, err := credentials.NewStore(cfg, nil) + if err != nil { + return user, pass, err + } + + if err = store.Add(ctx, apiv1.Credential{ + ServerAddress: address, + Username: user, + Password: &pass, + LocalStorage: true, + }, true); err != nil { + return user, pass, err + } + + // reload config, could have changed + if newCfg, err := config.ReadCLIConfig(false); err != nil { + return user, pass, err + } else { + *cfg = *newCfg + } + return user, pass, nil } -func DefaultProject(ctx context.Context, address, user, token string) (string, error) { +func DefaultProject(ctx context.Context, address, token string) (string, error) { projects, err := Projects(ctx, address, token) if err != nil { return "", err diff --git a/pkg/project/client.go b/pkg/project/client.go index 0ac20737a..dc56ebf57 100644 --- a/pkg/project/client.go +++ b/pkg/project/client.go @@ -2,6 +2,7 @@ package project import ( "context" + "errors" "fmt" "regexp" "strings" @@ -213,9 +214,20 @@ func getClient(ctx context.Context, cfg *config.CLIConfig, opts Options, project New: func() (client.Client, error) { url := cfg.ProjectURLs[server+"/"+account] if url == "" { - url, err = manager.ProjectURL(ctx, server, account, cred.Password) - if err != nil { - return nil, err + loginRetry := true + for { + url, err = manager.ProjectURL(ctx, server, account, cred.Password) + if loginRetry && errors.Is(err, manager.ErrForbidden) { + cred.Username, cred.Password, err = manager.Login(ctx, cfg, "", server) + if err != nil { + return nil, err + } + loginRetry = false + continue + } else if err != nil { + return nil, err + } + break } } return client.New(&rest.Config{