Merge pull request #13068 from 333fred/print-refresh-for-401s
Print `gh auth refresh` for 401 returns
This commit is contained in:
commit
7c439196c1
5 changed files with 108 additions and 5 deletions
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/cli/cli/v2/internal/gh/ghtelemetry"
|
||||
"github.com/cli/cli/v2/internal/telemetry"
|
||||
"github.com/cli/cli/v2/internal/update"
|
||||
"github.com/cli/cli/v2/pkg/cmd/auth/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmd/factory"
|
||||
"github.com/cli/cli/v2/pkg/cmd/root"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -227,7 +228,11 @@ func Main() exitCode {
|
|||
|
||||
var httpErr api.HTTPError
|
||||
if errors.As(err, &httpErr) && httpErr.StatusCode == 401 {
|
||||
fmt.Fprintln(stderr, "Try authenticating with: gh auth login")
|
||||
authCommand := "gh auth login"
|
||||
if cfg, cfgErr := cmdFactory.Config(); cfgErr == nil {
|
||||
authCommand = authRecoveryCommand(cfg, httpErr)
|
||||
}
|
||||
fmt.Fprintf(stderr, "Try authenticating with: %s\n", authCommand)
|
||||
} else if u := factory.SSOURL(); u != "" {
|
||||
// handles organization SAML enforcement error
|
||||
fmt.Fprintf(stderr, "Authorize in your web browser: %s\n", u)
|
||||
|
|
@ -291,6 +296,20 @@ func printError(out io.Writer, err error, cmd *cobra.Command, debug bool) {
|
|||
}
|
||||
}
|
||||
|
||||
func authRecoveryCommand(cfg gh.Config, httpErr api.HTTPError) string {
|
||||
if httpErr.RequestURL == nil {
|
||||
return "gh auth login"
|
||||
}
|
||||
|
||||
hostname := ghauth.NormalizeHostname(httpErr.RequestURL.Hostname())
|
||||
token, source := cfg.Authentication().ActiveToken(hostname)
|
||||
if shared.AuthTokenRefreshable(token, source) {
|
||||
return fmt.Sprintf("gh auth refresh -h %s", hostname)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("gh auth login -h %s", hostname)
|
||||
}
|
||||
|
||||
func checkForUpdate(ctx context.Context, f *cmdutil.Factory, currentVersion string) (*update.ReleaseInfo, error) {
|
||||
if updaterEnabled == "" || !update.ShouldCheckForUpdate() {
|
||||
return nil, nil
|
||||
|
|
|
|||
|
|
@ -5,11 +5,15 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/gh"
|
||||
ghmock "github.com/cli/cli/v2/internal/gh/mock"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
ghAPI "github.com/cli/go-gh/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
|
@ -511,3 +515,74 @@ func disableColorLabelsConfig() gh.Config {
|
|||
func enableColorLabelsConfig() gh.Config {
|
||||
return config.NewFromString("color_labels: enabled")
|
||||
}
|
||||
|
||||
func Test_authRecoveryCommand(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
source string
|
||||
requestURL string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "stored oauth token",
|
||||
token: "gho_abc123",
|
||||
source: "oauth_token",
|
||||
requestURL: "https://api.github.com/graphql",
|
||||
want: "gh auth refresh -h github.com",
|
||||
},
|
||||
{
|
||||
name: "stored pat",
|
||||
token: "github_pat_abc123",
|
||||
source: "oauth_token",
|
||||
requestURL: "https://api.github.com/graphql",
|
||||
want: "gh auth login -h github.com",
|
||||
},
|
||||
{
|
||||
name: "env token",
|
||||
token: "gho_abc123",
|
||||
source: "GH_TOKEN",
|
||||
requestURL: "https://api.github.com/graphql",
|
||||
want: "gh auth login -h github.com",
|
||||
},
|
||||
{
|
||||
name: "missing request url",
|
||||
token: "gho_abc123",
|
||||
source: "oauth_token",
|
||||
want: "gh auth login",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
authCfg := config.NewBlankConfig().Authentication()
|
||||
authCfg.SetActiveToken(tt.token, tt.source)
|
||||
cfg := &ghmock.ConfigMock{
|
||||
AuthenticationFunc: func() gh.AuthConfig {
|
||||
return authCfg
|
||||
},
|
||||
}
|
||||
|
||||
var requestURL *url.URL
|
||||
if tt.requestURL != "" {
|
||||
var err error
|
||||
requestURL, err = url.Parse(tt.requestURL)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse request URL: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
httpErr := api.HTTPError{
|
||||
HTTPError: &ghAPI.HTTPError{
|
||||
RequestURL: requestURL,
|
||||
StatusCode: 401,
|
||||
},
|
||||
}
|
||||
|
||||
got := authRecoveryCommand(cfg, httpErr)
|
||||
if got != tt.want {
|
||||
t.Errorf("authRecoveryCommand() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@ import (
|
|||
"github.com/cli/cli/v2/internal/gh"
|
||||
)
|
||||
|
||||
// AuthTokenRefreshable reports whether the token is stored by gh and can be
|
||||
// renewed with `gh auth refresh`.
|
||||
func AuthTokenRefreshable(token, src string) bool {
|
||||
return token != "" && !strings.HasSuffix(src, "_TOKEN") && strings.HasPrefix(token, "gho_")
|
||||
}
|
||||
|
||||
func AuthTokenWriteable(authCfg gh.AuthConfig, hostname string) (string, bool) {
|
||||
token, src := authCfg.ActiveToken(hostname)
|
||||
return src, (token == "" || !strings.HasSuffix(src, "_TOKEN"))
|
||||
|
|
|
|||
|
|
@ -96,6 +96,9 @@ func (e authEntry) String(cs *iostreams.ColorScheme) string {
|
|||
sb.WriteString(fmt.Sprintf(" - The token in %s is invalid.\n", e.TokenSource))
|
||||
if authTokenWriteable(e.TokenSource) {
|
||||
loginInstructions := fmt.Sprintf("gh auth login -h %s", e.Host)
|
||||
if shared.AuthTokenRefreshable(e.Token, e.TokenSource) {
|
||||
loginInstructions = fmt.Sprintf("gh auth refresh -h %s", e.Host)
|
||||
}
|
||||
logoutInstructions := fmt.Sprintf("gh auth logout -h %s -u %s", e.Host, e.Login)
|
||||
sb.WriteString(fmt.Sprintf(" - To re-authenticate, run: %s\n", cs.Bold(loginInstructions)))
|
||||
sb.WriteString(fmt.Sprintf(" - To forget about this account, run: %s\n", cs.Bold(logoutInstructions)))
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ func Test_statusRun(t *testing.T) {
|
|||
X Failed to log in to ghe.io account monalisa-ghe (GH_CONFIG_DIR/hosts.yml)
|
||||
- Active account: true
|
||||
- The token in GH_CONFIG_DIR/hosts.yml is invalid.
|
||||
- To re-authenticate, run: gh auth login -h ghe.io
|
||||
- To re-authenticate, run: gh auth refresh -h ghe.io
|
||||
- To forget about this account, run: gh auth logout -h ghe.io -u monalisa-ghe
|
||||
`),
|
||||
},
|
||||
|
|
@ -229,7 +229,7 @@ func Test_statusRun(t *testing.T) {
|
|||
X Failed to log in to ghe.io account monalisa-ghe (GH_CONFIG_DIR/hosts.yml)
|
||||
- Active account: true
|
||||
- The token in GH_CONFIG_DIR/hosts.yml is invalid.
|
||||
- To re-authenticate, run: gh auth login -h ghe.io
|
||||
- To re-authenticate, run: gh auth refresh -h ghe.io
|
||||
- To forget about this account, run: gh auth logout -h ghe.io -u monalisa-ghe
|
||||
`),
|
||||
},
|
||||
|
|
@ -447,7 +447,7 @@ func Test_statusRun(t *testing.T) {
|
|||
X Failed to log in to ghe.io account monalisa-ghe (GH_CONFIG_DIR/hosts.yml)
|
||||
- Active account: false
|
||||
- The token in GH_CONFIG_DIR/hosts.yml is invalid.
|
||||
- To re-authenticate, run: gh auth login -h ghe.io
|
||||
- To re-authenticate, run: gh auth refresh -h ghe.io
|
||||
- To forget about this account, run: gh auth logout -h ghe.io -u monalisa-ghe
|
||||
`),
|
||||
},
|
||||
|
|
@ -535,7 +535,7 @@ func Test_statusRun(t *testing.T) {
|
|||
X Failed to log in to ghe.io account monalisa-ghe-2 (GH_CONFIG_DIR/hosts.yml)
|
||||
- Active account: true
|
||||
- The token in GH_CONFIG_DIR/hosts.yml is invalid.
|
||||
- To re-authenticate, run: gh auth login -h ghe.io
|
||||
- To re-authenticate, run: gh auth refresh -h ghe.io
|
||||
- To forget about this account, run: gh auth logout -h ghe.io -u monalisa-ghe-2
|
||||
`),
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue