diff --git a/cli/login.go b/cli/login.go index ded67a016c..2ae79df8d0 100644 --- a/cli/login.go +++ b/cli/login.go @@ -356,12 +356,19 @@ func (r *RootCmd) login() *serpent.Command { return nil } - // If CODER_SESSION_TOKEN is set in the environment, abort login. - // The env var takes precedence over a token stored on disk, so - // even if we complete login and write a new token to the session - // file, subsequent CLI commands would still use the environment - // variable value. - if inv.Environ.Get(envSessionToken) != "" { + sessionToken, _ := inv.ParsedFlags().GetString(varToken) + tokenFlagProvided := inv.ParsedFlags().Changed(varToken) + + // If CODER_SESSION_TOKEN is set in the environment, abort + // interactive login unless --use-token-as-session or --token + // is specified. The env var takes precedence over a token + // stored on disk, so even if we complete login and write a + // new token to the session file, subsequent CLI commands + // would still use the environment variable value. When + // --token is provided on the command line, the user + // explicitly wants to authenticate with that token (common + // in CI), so we skip this check. + if !tokenFlagProvided && inv.Environ.Get(envSessionToken) != "" && !useTokenForSession { return xerrors.Errorf( "%s is set. This environment variable takes precedence over any session token stored on disk.\n\n"+ "To log in, unset the environment variable and re-run this command:\n\n"+ @@ -369,8 +376,6 @@ func (r *RootCmd) login() *serpent.Command { envSessionToken, envSessionToken, ) } - - sessionToken, _ := inv.ParsedFlags().GetString(varToken) if sessionToken == "" { authURL := *serverURL // Don't use filepath.Join, we don't want to use the os separator diff --git a/cli/login_test.go b/cli/login_test.go index b03354ac5a..6d6e54eb6e 100644 --- a/cli/login_test.go +++ b/cli/login_test.go @@ -528,6 +528,28 @@ func TestLogin(t *testing.T) { require.Contains(t, err.Error(), "unset CODER_SESSION_TOKEN") }) + t.Run("SessionTokenEnvVarWithUseTokenAsSession", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, nil) + coderdtest.CreateFirstUser(t, client) + root, _ := clitest.New(t, "login", client.URL.String(), "--use-token-as-session") + root.Environ.Set("CODER_SESSION_TOKEN", client.SessionToken()) + err := root.Run() + require.NoError(t, err) + }) + + t.Run("SessionTokenEnvVarWithTokenFlag", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, nil) + coderdtest.CreateFirstUser(t, client) + // Using --token with CODER_SESSION_TOKEN set should succeed. + // This is the standard pattern used by coder/setup-action. + root, _ := clitest.New(t, "login", client.URL.String(), "--token", client.SessionToken()) + root.Environ.Set("CODER_SESSION_TOKEN", client.SessionToken()) + err := root.Run() + require.NoError(t, err) + }) + t.Run("KeepOrganizationContext", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil)