From f9e98d1fb6bf2b1350f60d5bf401f7038da2d3e8 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 2 Sep 2025 16:59:58 -0500 Subject: [PATCH] Allow providing the `uv auth login` password or token via stdin (#15642) --- crates/uv-cli/src/lib.rs | 4 ++ crates/uv/src/commands/auth/login.rs | 5 ++ crates/uv/tests/it/auth.rs | 84 +++++++++++++++++++++++++++- docs/concepts/authentication/cli.md | 11 ++++ docs/reference/cli.md | 4 +- 5 files changed, 105 insertions(+), 3 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index c272ea60e..5159b611a 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -5567,12 +5567,16 @@ pub struct AuthLoginArgs { pub username: Option, /// The password to use for the service. + /// + /// Use `-` to read the password from stdin. #[arg(long, conflicts_with = "token")] pub password: Option, /// The token to use for the service. /// /// The username will be set to `__token__`. + /// + /// Use `-` to read the token from stdin. #[arg(long, short, conflicts_with = "username", conflicts_with = "password")] pub token: Option, diff --git a/crates/uv/src/commands/auth/login.rs b/crates/uv/src/commands/auth/login.rs index ebe85796b..c2c33c193 100644 --- a/crates/uv/src/commands/auth/login.rs +++ b/crates/uv/src/commands/auth/login.rs @@ -87,6 +87,11 @@ pub(crate) async fn login( (None, Some(_), Some(_)) => { bail!("Cannot include a password in the URL when using `--token`") } + (None, None, Some(value)) | (Some(value), None, None) if value == "-" => { + let mut input = String::new(); + std::io::stdin().read_line(&mut input)?; + input.trim().to_string() + } (Some(cli), None, None) => cli, (None, Some(url), None) => url.to_string(), (None, None, Some(token)) => token, diff --git a/crates/uv/tests/it/auth.rs b/crates/uv/tests/it/auth.rs index 26229398c..be52af15e 100644 --- a/crates/uv/tests/it/auth.rs +++ b/crates/uv/tests/it/auth.rs @@ -1,7 +1,5 @@ -#[cfg(feature = "native-auth")] use anyhow::Result; use assert_cmd::assert::OutputAssertExt; -#[cfg(feature = "native-auth")] use assert_fs::{fixture::PathChild, prelude::FileWriteStr}; #[cfg(feature = "native-auth")] use uv_static::EnvVars; @@ -778,6 +776,88 @@ fn login_text_store() { ); } +#[test] +#[allow(clippy::disallowed_types)] +fn login_password_stdin() -> Result<()> { + let context = TestContext::new_with_versions(&[]); + + // Create a temporary file with the password + let password_file = context.temp_dir.child("password.txt"); + password_file.write_str("secret-password")?; + + // Login with password from stdin + uv_snapshot!(context.auth_login() + .arg("https://example.com/simple") + .arg("--username") + .arg("testuser") + .arg("--password") + .arg("-") + .stdin(std::fs::File::open(password_file)?), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Stored credentials for testuser@https://example.com/ + " + ); + + // Verify the credentials work by retrieving the token + uv_snapshot!(context.auth_token() + .arg("https://example.com/simple") + .arg("--username") + .arg("testuser"), @r" + success: true + exit_code: 0 + ----- stdout ----- + secret-password + + ----- stderr ----- + " + ); + + Ok(()) +} + +#[test] +#[allow(clippy::disallowed_types)] +fn login_token_stdin() -> Result<()> { + let context = TestContext::new_with_versions(&[]); + + // Create a temporary file with the token + let token_file = context.temp_dir.child("token.txt"); + token_file.write_str("secret-token")?; + + // Login with token from stdin + uv_snapshot!(context.auth_login() + .arg("https://example.com/simple") + .arg("--token") + .arg("-") + .stdin(std::fs::File::open(token_file)?), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Stored credentials for https://example.com/ + " + ); + + // Verify the credentials work by retrieving the token + uv_snapshot!(context.auth_token() + .arg("https://example.com/simple"), @r" + success: true + exit_code: 0 + ----- stdout ----- + secret-token + + ----- stderr ----- + " + ); + + Ok(()) +} + #[test] fn token_text_store() { let context = TestContext::new_with_versions(&[]); diff --git a/docs/concepts/authentication/cli.md b/docs/concepts/authentication/cli.md index 5c8890f64..fd35b2e7b 100644 --- a/docs/concepts/authentication/cli.md +++ b/docs/concepts/authentication/cli.md @@ -15,6 +15,17 @@ This will prompt for the credentials. The credentials can also be provided using the `--username` and `--password` options, or the `--token` option for services which use a `__token__` or arbitrary username. +!!! note + + We recommend providing the secret via stdin. Use `-` to indicate the value should be read from + stdin, e.g., for `--password`: + + ```console + $ echo 'my-password' | uv auth login example.com --password - + ``` + + The same pattern can be used with `--token`. + Once credentials are added, uv will use them for packaging operations that require fetching content from the given service. At this time, only HTTPS Basic authentication is supported. The credentials will not yet be used for Git requests. diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 3f945e73c..8fbbbeb7c 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -110,7 +110,8 @@ uv auth login [OPTIONS]

May also be set with the UV_NO_PROGRESS environment variable.

--no-python-downloads

Disable automatic downloads of Python.

--offline

Disable network access.

When disabled, uv will only use locally cached data and locally available files.

-

May also be set with the UV_OFFLINE environment variable.

--password password

The password to use for the service

+

May also be set with the UV_OFFLINE environment variable.

--password password

The password to use for the service.

+

Use - to read the password from stdin.

--project project

Run the command within the given project directory.

All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (.venv).

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

@@ -120,6 +121,7 @@ uv auth login [OPTIONS]

Repeating this option, e.g., -qq, will enable a silent mode in which uv will write no output to stdout.

--token, -t token

The token to use for the service.

The username will be set to __token__.

+

Use - to read the token from stdin.

--username, -u username

The username to use for the service

--verbose, -v

Use verbose output.

You can configure fine-grained logging using the RUST_LOG environment variable. (https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives)