diff --git a/crates/uv-configuration/src/authentication.rs b/crates/uv-configuration/src/authentication.rs index 459c393d5..ca3317696 100644 --- a/crates/uv-configuration/src/authentication.rs +++ b/crates/uv-configuration/src/authentication.rs @@ -52,6 +52,14 @@ impl std::fmt::Display for KeyringProviderType { } } +#[derive(thiserror::Error, Debug)] +pub enum ServiceParseError { + #[error("failed to parse URL: {0}")] + InvalidUrl(#[from] url::ParseError), + #[error("only HTTPS is supported")] + UnsupportedScheme, +} + /// A service URL that wraps [`DisplaySafeUrl`] for CLI usage. /// /// This type provides automatic URL parsing and validation when used as a CLI argument, @@ -72,19 +80,26 @@ impl Service { } impl FromStr for Service { - type Err = url::ParseError; + type Err = ServiceParseError; fn from_str(s: &str) -> Result { // First try parsing as-is - match DisplaySafeUrl::parse(s) { - Ok(url) => Ok(Self(url)), + let url = match DisplaySafeUrl::parse(s) { + Ok(url) => url, Err(url::ParseError::RelativeUrlWithoutBase) => { // If it's a relative URL, try prepending https:// let with_https = format!("https://{s}"); - DisplaySafeUrl::parse(&with_https).map(Service) + DisplaySafeUrl::parse(&with_https)? } - Err(e) => Err(e), + Err(err) => return Err(err.into()), + }; + + // Only allow HTTPS URLs + if url.scheme() != "https" { + return Err(ServiceParseError::UnsupportedScheme); } + + Ok(Self(url)) } } diff --git a/crates/uv/tests/it/auth.rs b/crates/uv/tests/it/auth.rs index 9a87a1f8e..df40d7c70 100644 --- a/crates/uv/tests/it/auth.rs +++ b/crates/uv/tests/it/auth.rs @@ -704,7 +704,13 @@ fn logout_native_keyring() -> Result<()> { ----- stdout ----- ----- stderr ----- - error: When using `--token`, a username cannot not be provided; found: public + error: unexpected argument '--token' found + + tip: to pass '--token' as a value, use '-- --token' + + Usage: uv auth logout --cache-dir [CACHE_DIR] + + For more information, try '--help'. "); Ok(()) @@ -776,7 +782,7 @@ fn login_native_keyring_url() { Logged in to test@https://example.com/ "); - // When including a protocol explicitly, it is retained + // HTTP URLs are not allowed - only HTTPS uv_snapshot!(context.auth_login() .arg("http://example.com") .arg("--username") @@ -785,13 +791,14 @@ fn login_native_keyring_url() { .arg("test") .arg("--keyring-provider") .arg("native"), @r" - success: true - exit_code: 0 + success: false + exit_code: 2 ----- stdout ----- ----- stderr ----- - warning: The native keyring provider is experimental and may change without warning. Pass `--preview-features native-keyring` to disable this warning. - Logged in to test@http://example.com/ + error: invalid value 'http://example.com' for '': only HTTPS is supported + + For more information, try '--help'. "); uv_snapshot!(context.auth_login() @@ -843,7 +850,7 @@ fn login_native_keyring_url() { ----- stdout ----- ----- stderr ----- - error: invalid value 'not a valid url' for '': invalid international domain name + error: invalid value 'not a valid url' for '': failed to parse URL: invalid international domain name For more information, try '--help'. ");