diff --git a/crates/uv-auth/src/access_token.rs b/crates/uv-auth/src/access_token.rs index 76f7d3ef0..73bb13222 100644 --- a/crates/uv-auth/src/access_token.rs +++ b/crates/uv-auth/src/access_token.rs @@ -29,6 +29,6 @@ impl AsRef<[u8]> for AccessToken { impl std::fmt::Display for AccessToken { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) + write!(f, "****") } } diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index 002565852..2b670c660 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -30,7 +30,7 @@ pub enum Credentials { /// RFC 6750 Bearer Token Authentication Bearer { /// The token to use for authentication. - token: Vec, + token: Token, }, } @@ -102,6 +102,36 @@ impl fmt::Debug for Password { } } +#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Default, Deserialize)] +#[serde(transparent)] +pub struct Token(Vec); + +impl Token { + pub fn new(token: Vec) -> Self { + Self(token) + } + + /// Return the [`Token`] as a byte slice. + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } + + /// Convert the [`Token`] into its underlying [`Vec`]. + pub fn into_bytes(self) -> Vec { + self.0 + } + + /// Return whether the [`Token`] is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl fmt::Debug for Token { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "****") + } +} impl Credentials { /// Create a set of HTTP Basic Authentication credentials. #[allow(dead_code)] @@ -115,7 +145,9 @@ impl Credentials { /// Create a set of Bearer Authentication credentials. #[allow(dead_code)] pub fn bearer(token: Vec) -> Self { - Self::Bearer { token } + Self::Bearer { + token: Token::new(token), + } } pub fn username(&self) -> Option<&str> { @@ -286,7 +318,7 @@ impl Credentials { // Parse a `Bearer` authentication header. if let Some(token) = header.as_bytes().strip_prefix(b"Bearer ") { return Some(Self::Bearer { - token: token.to_vec(), + token: Token::new(token.to_vec()), }); } @@ -591,4 +623,15 @@ mod tests { "Basic { username: Username(Some(\"user\")), password: Some(****) }" ); } + + #[test] + fn test_bearer_token_obfuscation() { + let token = "super_secret_token"; + let credentials = Credentials::bearer(token.into()); + let debugged = format!("{credentials:?}"); + assert!( + !debugged.contains(token), + "Token should be obfuscated in Debug impl: {debugged}" + ); + } } diff --git a/crates/uv-auth/src/providers.rs b/crates/uv-auth/src/providers.rs index 268e0c17d..aef80dea7 100644 --- a/crates/uv-auth/src/providers.rs +++ b/crates/uv-auth/src/providers.rs @@ -10,6 +10,7 @@ use uv_static::EnvVars; use uv_warnings::warn_user_once; use crate::Credentials; +use crate::credentials::Token; use crate::realm::{Realm, RealmRef}; /// The [`Realm`] for the Hugging Face platform. @@ -45,7 +46,7 @@ impl HuggingFaceProvider { if RealmRef::from(url) == *HUGGING_FACE_REALM { if let Some(token) = HUGGING_FACE_TOKEN.as_ref() { return Some(Credentials::Bearer { - token: token.clone(), + token: Token::new(token.clone()), }); } } diff --git a/crates/uv-auth/src/pyx.rs b/crates/uv-auth/src/pyx.rs index ba4889f2d..d651e6c01 100644 --- a/crates/uv-auth/src/pyx.rs +++ b/crates/uv-auth/src/pyx.rs @@ -15,6 +15,7 @@ use uv_small_str::SmallString; use uv_state::{StateBucket, StateStore}; use uv_static::EnvVars; +use crate::credentials::Token; use crate::{AccessToken, Credentials, Realm}; /// Retrieve the pyx API key from the environment variable, or return `None`. @@ -84,7 +85,7 @@ impl From for Credentials { impl From for Credentials { fn from(access_token: AccessToken) -> Self { Self::Bearer { - token: access_token.into_bytes(), + token: Token::new(access_token.into_bytes()), } } } diff --git a/crates/uv-auth/src/store.rs b/crates/uv-auth/src/store.rs index 5964b3cc8..d51cc0e26 100644 --- a/crates/uv-auth/src/store.rs +++ b/crates/uv-auth/src/store.rs @@ -13,7 +13,7 @@ use uv_redacted::DisplaySafeUrl; use uv_state::{StateBucket, StateStore}; use uv_static::EnvVars; -use crate::credentials::{Password, Username}; +use crate::credentials::{Password, Token, Username}; use crate::realm::Realm; use crate::service::Service; use crate::{Credentials, KeyringProvider}; @@ -142,7 +142,7 @@ impl From for TomlCredentialWire { username: Username::new(None), scheme: AuthScheme::Bearer, password: None, - token: Some(String::from_utf8(token).expect("Token is valid UTF-8")), + token: Some(String::from_utf8(token.into_bytes()).expect("Token is valid UTF-8")), }, } } @@ -190,7 +190,7 @@ impl TryFrom for TomlCredential { )); } let credentials = Credentials::Bearer { - token: value.token.unwrap().into_bytes(), + token: Token::new(value.token.unwrap().into_bytes()), }; Ok(Self { service: value.service,