mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix(cli): fix coder login token failing without --url flag (#22742)
Previously `coder login token` didn't load the server URL from config, so it always required --url or CODER_URL when using the keyring to store the session token. This command would only print out the token when already logged in to a deployment and file storage is used to store the session token (keyring is the default on Windows/macOS). It would also print out an incorrect token when --url was specified and the session token stored on disk was for a different deployment that the user logged into. This change fixes all of these issues, and also errors out when using session token file storage with a `--url` argument that doesn't match the stored config URL, since the file only stores one token and would silently return the wrong one. See https://github.com/coder/coder/issues/22733 for a table of the before/after behaviors.
This commit is contained in:
+20
-1
@@ -475,7 +475,26 @@ func (r *RootCmd) loginToken() *serpent.Command {
|
||||
Long: "Print the session token for use in scripts and automation.",
|
||||
Middleware: serpent.RequireNArgs(0),
|
||||
Handler: func(inv *serpent.Invocation) error {
|
||||
tok, err := r.ensureTokenBackend().Read(r.clientURL)
|
||||
if err := r.ensureClientURL(); err != nil {
|
||||
return err
|
||||
}
|
||||
// When using the file storage, a session token is stored for a single
|
||||
// deployment URL that the user is logged in to. They keyring can store
|
||||
// multiple deployment session tokens. Error if the requested URL doesn't
|
||||
// match the stored config URL when using file storage to avoid returning
|
||||
// a token for the wrong deployment.
|
||||
backend := r.ensureTokenBackend()
|
||||
if _, ok := backend.(*sessionstore.File); ok {
|
||||
conf := r.createConfig()
|
||||
storedURL, err := conf.URL().Read()
|
||||
if err == nil {
|
||||
storedURL = strings.TrimSpace(storedURL)
|
||||
if storedURL != r.clientURL.String() {
|
||||
return xerrors.Errorf("file session token storage only supports one server at a time: requested %s but logged into %s", r.clientURL.String(), storedURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
tok, err := backend.Read(r.clientURL)
|
||||
if err != nil {
|
||||
if xerrors.Is(err, os.ErrNotExist) {
|
||||
return xerrors.New("no session token found - run 'coder login' first")
|
||||
|
||||
+24
-1
@@ -558,10 +558,33 @@ func TestLoginToken(t *testing.T) {
|
||||
|
||||
t.Run("NoTokenStored", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
inv, _ := clitest.New(t, "login", "token")
|
||||
client := coderdtest.New(t, nil)
|
||||
inv, _ := clitest.New(t, "login", "token", "--url", client.URL.String())
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "no session token found")
|
||||
})
|
||||
|
||||
t.Run("NoURLProvided", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
inv, _ := clitest.New(t, "login", "token")
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "You are not logged in")
|
||||
})
|
||||
|
||||
t.Run("URLMismatchFileBackend", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
inv, root := clitest.New(t, "login", "token", "--url", "https://other.example.com")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "file session token storage only supports one server")
|
||||
})
|
||||
}
|
||||
|
||||
+24
-21
@@ -550,30 +550,33 @@ type RootCmd struct {
|
||||
useKeyringWithGlobalConfig bool
|
||||
}
|
||||
|
||||
// ensureClientURL loads the client URL from the config file if it
|
||||
// wasn't provided via --url or CODER_URL.
|
||||
func (r *RootCmd) ensureClientURL() error {
|
||||
if r.clientURL != nil && r.clientURL.String() != "" {
|
||||
return nil
|
||||
}
|
||||
rawURL, err := r.createConfig().URL().Read()
|
||||
// If the configuration files are absent, the user is logged out.
|
||||
if os.IsNotExist(err) {
|
||||
binPath, err := os.Executable()
|
||||
if err != nil {
|
||||
binPath = "coder"
|
||||
}
|
||||
return xerrors.Errorf(notLoggedInMessage, binPath)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.clientURL, err = url.Parse(strings.TrimSpace(rawURL))
|
||||
return err
|
||||
}
|
||||
|
||||
// InitClient creates and configures a new client with authentication, telemetry,
|
||||
// and version checks.
|
||||
func (r *RootCmd) InitClient(inv *serpent.Invocation) (*codersdk.Client, error) {
|
||||
conf := r.createConfig()
|
||||
var err error
|
||||
// Read the client URL stored on disk.
|
||||
if r.clientURL == nil || r.clientURL.String() == "" {
|
||||
rawURL, err := conf.URL().Read()
|
||||
// If the configuration files are absent, the user is logged out
|
||||
if os.IsNotExist(err) {
|
||||
binPath, err := os.Executable()
|
||||
if err != nil {
|
||||
binPath = "coder"
|
||||
}
|
||||
return nil, xerrors.Errorf(notLoggedInMessage, binPath)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.clientURL, err = url.Parse(strings.TrimSpace(rawURL))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.ensureClientURL(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.token == "" {
|
||||
tok, err := r.ensureTokenBackend().Read(r.clientURL)
|
||||
|
||||
Reference in New Issue
Block a user