From 872ba31607ebb5a43fe127f7f4b82429a5fd89af Mon Sep 17 00:00:00 2001 From: bircni Date: Sun, 11 Oct 2026 21:53:35 +0200 Subject: [PATCH] fix(auth): prompt for --with-token without echo on a terminal With stdin on a terminal, auth login --with-token read the pasted token from the raw terminal, which echoed it in clear text without a prompt. It now asks with a hidden "Paste an access token" prompt, and refuses to read the terminal when prompting is disabled. Piped stdin is read as before. Signed-off-by: bircni --- internal/app/auth.go | 14 +++++++++++++- internal/app/auth_test.go | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/internal/app/auth.go b/internal/app/auth.go index a75e7969..c734b0d7 100644 --- a/internal/app/auth.go +++ b/internal/app/auth.go @@ -185,6 +185,18 @@ func (a *application) authCommand() *cobra.Command { return err } method = settings.AuthOAuth + } else if a.deps.Streams.InTTY { + if !a.policy(snapshot).CanPrompt(a.deps.Streams) { + return clierr.Wrap(clierr.Usage, errors.New("--with-token reads the token from stdin; pipe it in, for example: tea auth login --with-token < token.txt")) + } + token, err := a.deps.Prompter.SecretInput(cmd.Context(), "Paste an access token") + if err != nil { + return err + } + credential.AccessToken = strings.TrimSpace(token) + if credential.AccessToken == "" { + return clierr.Wrap(clierr.Usage, errors.New("access token cannot be empty")) + } } else { data, err := io.ReadAll(io.LimitReader(a.deps.Streams.In, 65537)) if err != nil { @@ -214,7 +226,7 @@ func (a *application) authCommand() *cobra.Command { login.Flags().BoolVar(&oauth, "oauth", false, "Authorize through a browser using OAuth PKCE") login.Flags().StringVar(&clientID, "client-id", "", "OAuth client ID registered on the instance (default: the instance's stored ID, then tea's)") login.Flags().BoolVar(&insecure, "insecure", false, "Skip TLS certificate verification for this instance") - login.Flags().BoolVar(&withToken, "with-token", false, "Read a personal access token from stdin") + login.Flags().BoolVar(&withToken, "with-token", false, "Read a personal access token from stdin, or prompt for it on a terminal") login.Flags().StringVar(&sshKey, "ssh-key", "", "Authenticate with an SSH private key via HTTP signatures") login.Flags().StringVar(&sshAgentPrincipal, "ssh-agent-principal", "", "Authenticate with an ssh-agent certificate principal via HTTP signatures") login.Flags().StringVar(&sshAgentKey, "ssh-agent-key", "", "Authenticate with an ssh-agent public key fingerprint via HTTP signatures") diff --git a/internal/app/auth_test.go b/internal/app/auth_test.go index cda348aa..9716cca6 100644 --- a/internal/app/auth_test.go +++ b/internal/app/auth_test.go @@ -87,6 +87,26 @@ func TestLoginValidatesUserEnvironmentWithFlagPrecedence(t *testing.T) { } } +func TestLoginWithTokenPromptsWithoutEchoOnATerminal(t *testing.T) { + server := newAuthenticatedServer(http.NotFoundHandler()) + defer server.Close() + args := []string{"auth", "login", "--host", server.URL, "--with-token", "--credential-store", "file"} + deps, _, _ := testDeps(t) + deps.Streams.In = strings.NewReader("echoed-token\n") + deps.Streams.InTTY, deps.Streams.ErrTTY = true, true + prompt := &scriptedPrompter{secret: " private-login-token "} + deps.Prompter = prompt + err := Execute(context.Background(), deps, append(args, "--no-input")) + require.Equal(t, clierr.Usage, clierr.ExitCode(err), "%v", err) + require.ErrorContains(t, err, "tea auth login --with-token < token.txt") + require.Empty(t, prompt.secrets) + require.NoError(t, Execute(context.Background(), deps, args)) + require.Equal(t, []string{"Paste an access token"}, prompt.secrets) + credential, err := deps.Credentials.Load(server.URL, "alice", settings.StoreFile) + require.NoError(t, err) + require.Equal(t, "private-login-token", credential.AccessToken) +} + func TestLoginInsecureInstance(t *testing.T) { server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte(`{"login":"alice"}`))