Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions crates/uv-auth/src/keyring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,26 @@ impl KeyringProvider {
// Ensure we strip credentials from the URL before storing
let url = url.without_credentials();

// If there's no path, we'll perform a host-level login
let target = if let Some(host) = url.host_str().filter(|_| !url.path().is_empty()) {
let mut target = String::new();
if url.scheme() != "https" {
target.push_str(url.scheme());
target.push_str("://");
}
target.push_str(host);
if let Some(port) = url.port() {
target.push(':');
target.push_str(&port.to_string());
}
target
} else {
url.to_string()
};

match &self.backend {
KeyringProviderBackend::Native => {
self.store_native(url.as_str(), username, password).await?;
self.store_native(&target, username, password).await?;
Ok(true)
}
KeyringProviderBackend::Subprocess => {
Expand Down Expand Up @@ -122,9 +139,26 @@ impl KeyringProvider {
// Ensure we strip credentials from the URL before storing
let url = url.without_credentials();

// If there's no path, we'll perform a host-level login
let target = if let Some(host) = url.host_str().filter(|_| !url.path().is_empty()) {
let mut target = String::new();
if url.scheme() != "https" {
target.push_str(url.scheme());
target.push_str("://");
}
target.push_str(host);
if let Some(port) = url.port() {
target.push(':');
target.push_str(&port.to_string());
}
target
} else {
url.to_string()
};
Comment on lines +142 to +157
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This repetition is questionable, but I'll refactor this in a subsequent change this week.


match &self.backend {
KeyringProviderBackend::Native => {
self.remove_native(url.as_str(), username).await?;
self.remove_native(&target, username).await?;
Ok(())
}
KeyringProviderBackend::Subprocess => {
Expand Down
91 changes: 46 additions & 45 deletions crates/uv/tests/it/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,17 @@ fn add_package_native_auth_realm() -> Result<()> {
// storied in the system keyring.
uv_snapshot!(context.add().arg("anyio").arg("--default-index").arg("https://public@pypi-proxy.fly.dev/basic-auth/simple")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 1
success: true
exit_code: 0
----- stdout -----

----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because anyio was not found in the package registry and your project depends on anyio, we can conclude that your project's requirements are unsatisfiable.

hint: An index URL (https://pypi-proxy.fly.dev/basic-auth/simple) could not be queried due to a lack of valid authentication credentials (401 Unauthorized).
help: If you want to add the package regardless of the failed resolution, provide the `--frozen` flag to skip locking and syncing.
Resolved 4 packages in [TIME]
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ anyio==4.3.0
+ idna==3.6
+ sniffio==1.3.1
"
);

Expand Down Expand Up @@ -176,16 +177,17 @@ fn add_package_native_auth() -> Result<()> {
// credentials storied in the system keyring.
uv_snapshot!(context.add().arg("anyio").arg("--default-index").arg("https://public@pypi-proxy.fly.dev/basic-auth/simple")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 1
success: true
exit_code: 0
----- stdout -----

----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because anyio was not found in the package registry and your project depends on anyio, we can conclude that your project's requirements are unsatisfiable.

hint: An index URL (https://pypi-proxy.fly.dev/basic-auth/simple) could not be queried due to a lack of valid authentication credentials (401 Unauthorized).
help: If you want to add the package regardless of the failed resolution, provide the `--frozen` flag to skip locking and syncing.
Resolved 4 packages in [TIME]
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ anyio==4.3.0
+ idna==3.6
+ sniffio==1.3.1
"
);

Expand Down Expand Up @@ -286,12 +288,12 @@ fn token_native_auth() -> Result<()> {
.arg("--username")
.arg("public")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron

----- stderr -----
error: Failed to fetch credentials for public@https://pypi-proxy.fly.dev/basic-auth/simple
");

// Without the username
Expand Down Expand Up @@ -341,12 +343,12 @@ fn token_native_auth() -> Result<()> {
uv_snapshot!(context.auth_token()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron

----- stderr -----
error: Failed to fetch credentials for https://pypi-proxy.fly.dev/basic-auth/simple
");

context
Expand All @@ -360,12 +362,12 @@ fn token_native_auth() -> Result<()> {
uv_snapshot!(context.auth_token()
.arg("https://public@pypi-proxy.fly.dev/basic-auth/simple")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron

----- stderr -----
error: Failed to fetch credentials for public@https://pypi-proxy.fly.dev/basic-auth/simple
");

// Conflict between --username and URL username is rejected
Expand Down Expand Up @@ -410,12 +412,12 @@ fn token_native_auth_realm() -> Result<()> {
uv_snapshot!(context.auth_token()
.arg("pypi-proxy.fly.dev")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron

----- stderr -----
error: Failed to fetch credentials for https://pypi-proxy.fly.dev/
");

// Without persisted credentials (with a username in the request)
Expand Down Expand Up @@ -469,36 +471,36 @@ fn token_native_auth_realm() -> Result<()> {
.arg("--username")
.arg("public")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron

----- stderr -----
error: Failed to fetch credentials for public@https://pypi-proxy.fly.dev/basic-auth/simple
");

// Without the username
uv_snapshot!(context.auth_token()
.arg("pypi-proxy.fly.dev")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron

----- stderr -----
error: Failed to fetch credentials for https://pypi-proxy.fly.dev/
");

// Without the username
uv_snapshot!(context.auth_token()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron

----- stderr -----
error: Failed to fetch credentials for https://pypi-proxy.fly.dev/basic-auth/simple
");

// With a mismatched username
Expand Down Expand Up @@ -568,12 +570,12 @@ fn token_native_auth_realm() -> Result<()> {
uv_snapshot!(context.auth_token()
.arg("https://public@pypi-proxy.fly.dev/basic-auth/simple")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron

----- stderr -----
error: Failed to fetch credentials for public@https://pypi-proxy.fly.dev/basic-auth/simple
");

Ok(())
Expand Down Expand Up @@ -1848,12 +1850,12 @@ fn native_auth_prefix_match() -> Result<()> {
.arg("--username")
.arg("testuser")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
testpass

----- stderr -----
error: Failed to fetch credentials for testuser@https://example.com/api/v1
"
);

Expand Down Expand Up @@ -1892,18 +1894,17 @@ fn native_auth_host_fallback() -> Result<()> {
);

// Should fallback to host-level matching
// TODO(zanieb): This is not working as intended
uv_snapshot!(context.auth_token()
.arg("https://example.com/any/path")
.arg("--username")
.arg("testuser")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
hostpass

----- stderr -----
error: Failed to fetch credentials for testuser@https://example.com/any/path
"
);

Expand Down
Loading