diff --git a/crates/uv-auth/src/index.rs b/crates/uv-auth/src/index.rs index 35a7f1f19..aa01add65 100644 --- a/crates/uv-auth/src/index.rs +++ b/crates/uv-auth/src/index.rs @@ -95,9 +95,9 @@ impl Indexes { index_urls } - /// Get the index URL prefix for a URL if one exists. - pub fn index_url_for(&self, url: &Url) -> Option<&DisplaySafeUrl> { - self.find_prefix_index(url).map(|index| &index.url) + /// Get the index for a URL if one exists. + pub fn index_for(&self, url: &Url) -> Option<&Index> { + self.find_prefix_index(url) } /// Get the [`AuthPolicy`] for a URL. diff --git a/crates/uv-auth/src/middleware.rs b/crates/uv-auth/src/middleware.rs index 8663153dd..90a902af9 100644 --- a/crates/uv-auth/src/middleware.rs +++ b/crates/uv-auth/src/middleware.rs @@ -22,7 +22,7 @@ use crate::{ realm::Realm, }; -use crate::{TextCredentialStore, TomlCredentialError}; +use crate::{Index, TextCredentialStore, TomlCredentialError}; /// Strategy for loading netrc files. enum NetrcMode { @@ -297,7 +297,7 @@ impl Middleware for AuthMiddleware { // In the middleware, existing credentials are already moved from the URL // to the headers so for display purposes we restore some information let url = tracing_url(&request, request_credentials.as_ref()); - let maybe_index_url = self.indexes.index_url_for(request.url()); + let index = self.indexes.index_for(request.url()); let auth_policy = self.indexes.auth_policy_for(request.url()); trace!("Handling request for {url} with authentication policy {auth_policy}"); @@ -312,7 +312,7 @@ impl Middleware for AuthMiddleware { extensions, next, &url, - maybe_index_url, + index, auth_policy, ) .await; @@ -406,8 +406,8 @@ impl Middleware for AuthMiddleware { .as_ref() .map(|credentials| credentials.to_username()) .unwrap_or(Username::none()); - let credentials = if let Some(index_url) = maybe_index_url { - self.cache().get_url(index_url, &username).or_else(|| { + let credentials = if let Some(index) = index { + self.cache().get_url(&index.url, &username).or_else(|| { self.cache() .get_realm(Realm::from(&**retry_request_url), username) }) @@ -435,7 +435,7 @@ impl Middleware for AuthMiddleware { .fetch_credentials( credentials.as_deref(), retry_request_url, - maybe_index_url, + index, auth_policy, ) .await @@ -529,7 +529,7 @@ impl AuthMiddleware { extensions: &mut Extensions, next: Next<'_>, url: &DisplaySafeUrl, - index_url: Option<&DisplaySafeUrl>, + index: Option<&Index>, auth_policy: AuthPolicy, ) -> reqwest_middleware::Result { let credentials = Arc::new(credentials); @@ -545,11 +545,15 @@ impl AuthMiddleware { trace!("Request for {url} is missing a password, looking for credentials"); // There's just a username, try to find a password. - // If we have an index URL, check the cache for that URL. Otherwise, + // If we have an index, check the cache for that URL. Otherwise, // check for the realm. - let maybe_cached_credentials = if let Some(index_url) = index_url { + let maybe_cached_credentials = if let Some(index) = index { self.cache() - .get_url(index_url, credentials.as_username().as_ref()) + .get_url(&index.url, credentials.as_username().as_ref()) + .or_else(|| { + self.cache() + .get_url(&index.root_url, credentials.as_username().as_ref()) + }) } else { self.cache() .get_realm(Realm::from(request.url()), credentials.to_username()) @@ -574,14 +578,14 @@ impl AuthMiddleware { .fetch_credentials( Some(&credentials), DisplaySafeUrl::ref_cast(request.url()), - index_url, + index, auth_policy, ) .await { request = credentials.authenticate(request); Some(credentials) - } else if index_url.is_some() { + } else if index.is_some() { // If this is a known index, we fall back to checking for the realm. if let Some(credentials) = self .cache() @@ -608,7 +612,7 @@ impl AuthMiddleware { &self, credentials: Option<&Credentials>, url: &DisplaySafeUrl, - maybe_index_url: Option<&DisplaySafeUrl>, + index: Option<&Index>, auth_policy: AuthPolicy, ) -> Option> { let username = Username::from( @@ -617,8 +621,8 @@ impl AuthMiddleware { // Fetches can be expensive, so we will only run them _once_ per realm or index URL and username combination // All other requests for the same realm or index URL will wait until the first one completes - let key = if let Some(index_url) = maybe_index_url { - (FetchUrl::Index(index_url.clone()), username) + let key = if let Some(index) = index { + (FetchUrl::Index(index.url.clone()), username) } else { (FetchUrl::Realm(Realm::from(&**url)), username) }; @@ -719,13 +723,16 @@ impl AuthMiddleware { } else { String::new() }; - if let Some(index_url) = maybe_index_url { - debug!("Checking native store for credentials for index URL {}{}", display_username, index_url); - native_store.fetch(DisplaySafeUrl::ref_cast(index_url), username).await + if let Some(index) = index { + // N.B. The native store performs an exact look up right now, so we use the root + // URL of the index instead of relying on prefix-matching. + debug!("Checking native store for credentials for index URL {}{}", display_username, index.root_url); + native_store.fetch(&index.root_url, username).await } else { debug!("Checking native store for credentials for URL {}{}", display_username, url); native_store.fetch(url, username).await } + // TODO(zanieb): We should have a realm fallback here too } else { None } @@ -742,19 +749,20 @@ impl AuthMiddleware { // URLs; instead, we fetch if there's a username or if the user has requested to // always authenticate. if let Some(username) = credentials.and_then(|credentials| credentials.username()) { - if let Some(index_url) = maybe_index_url { - debug!("Checking keyring for credentials for index URL {}@{}", username, index_url); - keyring.fetch(DisplaySafeUrl::ref_cast(index_url), Some(username)).await + if let Some(index) = index { + debug!("Checking keyring for credentials for index URL {}@{}", username, index.url); + keyring.fetch(DisplaySafeUrl::ref_cast(&index.url), Some(username)).await } else { debug!("Checking keyring for credentials for full URL {}@{}", username, url); keyring.fetch(url, Some(username)).await } } else if matches!(auth_policy, AuthPolicy::Always) { - if let Some(index_url) = maybe_index_url { + if let Some(index) = index { debug!( - "Checking keyring for credentials for index URL {index_url} without username due to `authenticate = always`" + "Checking keyring for credentials for index URL {} without username due to `authenticate = always`", + index.url ); - keyring.fetch(DisplaySafeUrl::ref_cast(index_url), None).await + keyring.fetch(DisplaySafeUrl::ref_cast(&index.url), None).await } else { None } diff --git a/crates/uv/tests/it/auth.rs b/crates/uv/tests/it/auth.rs index 77b1cd721..21c4e9ec9 100644 --- a/crates/uv/tests/it/auth.rs +++ b/crates/uv/tests/it/auth.rs @@ -176,16 +176,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 " );