diff --git a/cmd/login.go b/cmd/login.go index 582cc404..b77beda7 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -7,6 +7,7 @@ import ( "github.com/localstack/lstk/internal/auth" "github.com/localstack/lstk/internal/env" "github.com/localstack/lstk/internal/log" + "github.com/localstack/lstk/internal/output" "github.com/localstack/lstk/internal/telemetry" "github.com/localstack/lstk/internal/ui" "github.com/localstack/lstk/internal/version" @@ -23,14 +24,20 @@ func newLoginCmd(cfg *env.Env, tel *telemetry.Client, logger log.Logger) *cobra. if !isInteractiveMode(cfg) { return fmt.Errorf("login requires an interactive terminal") } + tokenStorage, err := auth.NewTokenStorage(cfg.ForceFileKeyring, logger) + if err != nil { + return fmt.Errorf("failed to initialize token storage: %w", err) + } + storedToken, _ := tokenStorage.GetAuthToken() + if cfg.AuthToken != "" || storedToken != "" { + return ui.RunMessage(cmd.Context(), output.MessageEvent{Severity: output.SeverityNote, Text: "You're already logged in"}) + } platformClient := api.NewPlatformClient(cfg.APIEndpoint, logger) if err := ui.RunLogin(cmd.Context(), version.Version(), platformClient, cfg.AuthToken, cfg.ForceFileKeyring, cfg.WebAppURL, logger); err != nil { return err } - if tokenStorage, err := auth.NewTokenStorage(cfg.ForceFileKeyring, logger); err == nil { - if token, err := tokenStorage.GetAuthToken(); err == nil && token != "" { - tel.SetAuthToken(token) - } + if token, err := tokenStorage.GetAuthToken(); err == nil && token != "" { + tel.SetAuthToken(token) } return nil }, diff --git a/internal/ui/run.go b/internal/ui/run.go index f176b7cb..969817a1 100644 --- a/internal/ui/run.go +++ b/internal/ui/run.go @@ -98,6 +98,13 @@ func Run(parentCtx context.Context, runOpts RunOptions) error { return nil } +func RunMessage(parentCtx context.Context, event output.MessageEvent) error { + return runWithTUI(parentCtx, withoutHeader(), func(ctx context.Context, sink output.Sink) error { + sink.Emit(event) + return nil + }) +} + func IsInteractive() bool { return term.IsTerminal(int(os.Stdout.Fd())) && term.IsTerminal(int(os.Stdin.Fd())) } diff --git a/internal/ui/styles/styles.go b/internal/ui/styles/styles.go index b71191f6..9dcbbc2c 100644 --- a/internal/ui/styles/styles.go +++ b/internal/ui/styles/styles.go @@ -45,7 +45,7 @@ var ( Foreground(lipgloss.Color(SuccessColor)) Note = lipgloss.NewStyle(). - Foreground(lipgloss.Color("33")) + Foreground(lipgloss.Color("69")) Warning = lipgloss.NewStyle(). Foreground(lipgloss.Color("214")) diff --git a/test/integration/login_test.go b/test/integration/login_test.go index c912526d..d4be99ad 100644 --- a/test/integration/login_test.go +++ b/test/integration/login_test.go @@ -8,7 +8,9 @@ import ( "io" "net/http" "net/http/httptest" + "os" "os/exec" + "path/filepath" "runtime" "testing" "time" @@ -194,3 +196,40 @@ func TestDeviceFlowFailure_RequestNotConfirmed(t *testing.T) { assert.Error(t, err, "no token should be stored when login fails") assertCommandTelemetry(t, events, "login", 1) } + +func TestLoginShortCircuitsWhenEnvTokenSet(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("PTY not supported on Windows") + } + t.Parallel() + + environ := append(testEnvWithHome(t.TempDir(), ""), string(env.AuthToken)+"=fake-env-token") + + out, err := runLstkInPTY(t, testContext(t), environ, "login") + require.NoError(t, err, "login should succeed when env token is set: %s", out) + requireExitCode(t, 0, err) + assert.Contains(t, out, "You're already logged in") + assert.NotContains(t, out, "Opening browser") + assert.NotContains(t, out, "Waiting for authorization") +} + +func TestLoginShortCircuitsWhenStoredTokenExists(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("PTY not supported on Windows") + } + t.Parallel() + + tmpHome := t.TempDir() + tokenDir := filepath.Join(tmpHome, ".config", "lstk") + require.NoError(t, os.MkdirAll(tokenDir, 0700)) + require.NoError(t, os.WriteFile(filepath.Join(tokenDir, "auth-token"), []byte("stored-token"), 0600)) + + environ := env.Environ(testEnvWithHome(tmpHome, "")).Without(env.AuthToken) + + out, err := runLstkInPTY(t, testContext(t), environ, "login") + require.NoError(t, err, "login should succeed when stored token exists: %s", out) + requireExitCode(t, 0, err) + assert.Contains(t, out, "You're already logged in") + assert.NotContains(t, out, "Opening browser") + assert.NotContains(t, out, "Waiting for authorization") +}