diff --git a/Cargo.lock b/Cargo.lock index 815aaa052..c6d076d25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4424,6 +4424,7 @@ dependencies = [ "tokio", "tracing", "url", + "urlencoding", "wiremock", ] diff --git a/crates/uv-auth/Cargo.toml b/crates/uv-auth/Cargo.toml index 8ed8b1522..0e4851fe1 100644 --- a/crates/uv-auth/Cargo.toml +++ b/crates/uv-auth/Cargo.toml @@ -14,6 +14,7 @@ task-local-extensions = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } url = { workspace = true } +urlencoding = { workspace = true } once_cell = { workspace = true } [dev-dependencies] diff --git a/crates/uv-auth/src/store.rs b/crates/uv-auth/src/store.rs index d43824f84..9ac85234e 100644 --- a/crates/uv-auth/src/store.rs +++ b/crates/uv-auth/src/store.rs @@ -100,7 +100,13 @@ impl AuthenticationStore { return; } let auth = UrlAuthData { - username: url.username().to_string(), + // Using the encoded username can break authentication when `@` is converted to `%40` + // so we decode it for storage; RFC7617 does not explicitly say that authentication should + // not be percent-encoded, but the omission of percent-encoding from all encoding discussion + // indicates that it probably should not be done. + username: urlencoding::decode(url.username()) + .expect("An encoded username should always decode") + .into_owned(), password: url.password().map(str::to_string), }; credentials.insert(netloc, Some(Credential::UrlEncoded(auth)));