Skip to content
This repository has been archived by the owner on Mar 16, 2024. It is now read-only.

Introduce a login retry when a user's token is expired #1955

Merged
merged 1 commit into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 20 additions & 33 deletions pkg/cli/credential_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
34 changes: 26 additions & 8 deletions pkg/manager/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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)
}
Expand All @@ -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
}
32 changes: 29 additions & 3 deletions pkg/manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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")
}
Expand All @@ -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
Expand Down
18 changes: 15 additions & 3 deletions pkg/project/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package project

import (
"context"
"errors"
"fmt"
"regexp"
"strings"
Expand Down Expand Up @@ -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{
Expand Down