diff --git a/Cargo.lock b/Cargo.lock index b1324ff726..6307366bc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2344,6 +2344,15 @@ dependencies = [ "syn 2.0.68", ] +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + [[package]] name = "security-framework-sys" version = "2.11.0" @@ -2465,6 +2474,7 @@ dependencies = [ "rstest", "runas", "rust-ini", + "secrecy", "semver 1.0.23", "sentry", "serde", @@ -3586,6 +3596,12 @@ dependencies = [ "syn 2.0.68", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zip" version = "0.6.6" diff --git a/Cargo.toml b/Cargo.toml index ec73eb5aaf..758b7f521b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ zip = "0.6.4" data-encoding = "2.3.3" magic_string = "0.3.4" chrono-tz = "0.8.4" +secrecy = "0.8.0" [dev-dependencies] insta = { version = "1.26.0", features = ["redactions", "yaml"] } diff --git a/src/api/mod.rs b/src/api/mod.rs index aae183f9d1..e62975c096 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -37,6 +37,7 @@ use lazy_static::lazy_static; use log::{debug, info, warn}; use parking_lot::Mutex; use regex::{Captures, Regex}; +use secrecy::ExposeSecret; use sentry::protocol::{Exception, Values}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; @@ -1687,7 +1688,10 @@ impl ApiRequest { } Auth::Token(ref token) => { debug!("using token authentication"); - self.with_header("Authorization", &format!("Bearer {token}")) + self.with_header( + "Authorization", + &format!("Bearer {}", token.raw().expose_secret()), + ) } } } diff --git a/src/config.rs b/src/config.rs index da56ae6de3..b128c7a71f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,6 +12,7 @@ use ini::Ini; use lazy_static::lazy_static; use log::{debug, info, set_max_level, warn}; use parking_lot::Mutex; +use secrecy::ExposeSecret; use sentry::types::Dsn; use crate::constants::CONFIG_INI_FILE_PATH; @@ -181,8 +182,11 @@ impl Config { self.cached_base_url = token_url.to_string(); } - self.ini - .set_to(Some("auth"), "token".into(), val.to_string()); + self.ini.set_to( + Some("auth"), + "token".into(), + val.raw().expose_secret().clone(), + ); } Some(Auth::Key(ref val)) => { self.ini diff --git a/src/utils/auth_token/auth_token_impl.rs b/src/utils/auth_token/auth_token_impl.rs index ab44c25363..44dea852f3 100644 --- a/src/utils/auth_token/auth_token_impl.rs +++ b/src/utils/auth_token/auth_token_impl.rs @@ -2,7 +2,7 @@ use super::{AuthTokenPayload, ORG_AUTH_TOKEN_PREFIX, USER_TOKEN_PREFIX}; use super::{OrgAuthToken, UserAuthToken}; -use std::fmt::{Display, Formatter, Result}; +use secrecy::SecretString; /// Represents a (soft) validated Sentry auth token. #[derive(Debug, Clone)] @@ -21,8 +21,8 @@ impl AuthToken { } /// Retrieves a reference to the auth token string. - fn as_str(&self) -> &str { - self.0.as_str() + pub fn raw(&self) -> &SecretString { + self.0.raw() } /// Returns whether the auth token follows a recognized format. If this function returns false, @@ -48,14 +48,6 @@ impl From<&str> for AuthToken { } } -impl Display for AuthToken { - /// Displays the auth token string. - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - write!(f, "{}", self.as_str())?; - Ok(()) - } -} - /// Inner representation of AuthToken type, containing all possible auth token types. #[derive(Debug, Clone)] enum AuthTokenInner { @@ -66,7 +58,7 @@ enum AuthTokenInner { User(UserAuthToken), /// Represents an auth token that has an unrecognized format. - Unknown(String), + Unknown(SecretString), } impl AuthTokenInner { @@ -78,7 +70,7 @@ impl AuthTokenInner { } else if let Ok(user_auth_token) = UserAuthToken::try_from(auth_string.clone()) { AuthTokenInner::User(user_auth_token) } else { - AuthTokenInner::Unknown(auth_string) + AuthTokenInner::Unknown(auth_string.into()) } } @@ -92,10 +84,10 @@ impl AuthTokenInner { } /// Retrieves a reference to the auth token string. - fn as_str(&self) -> &str { + fn raw(&self) -> &SecretString { match self { - AuthTokenInner::Org(ref org_auth_token) => org_auth_token.as_str(), - AuthTokenInner::User(user_auth_token) => user_auth_token.as_str(), + AuthTokenInner::Org(ref org_auth_token) => org_auth_token.raw(), + AuthTokenInner::User(user_auth_token) => user_auth_token.raw(), AuthTokenInner::Unknown(auth_string) => auth_string, } } diff --git a/src/utils/auth_token/org_auth_token.rs b/src/utils/auth_token/org_auth_token.rs index 7fe8306998..e7a325d2e5 100644 --- a/src/utils/auth_token/org_auth_token.rs +++ b/src/utils/auth_token/org_auth_token.rs @@ -1,4 +1,5 @@ use super::{AuthTokenParseError, Result, ORG_AUTH_TOKEN_PREFIX}; +use secrecy::SecretString; use serde::{Deserialize, Deserializer}; const ORG_TOKEN_SECRET_BYTES: usize = 32; @@ -6,7 +7,7 @@ const ORG_TOKEN_SECRET_BYTES: usize = 32; /// Represents a valid org auth token. #[derive(Debug, Clone)] pub struct OrgAuthToken { - auth_string: String, + auth_string: SecretString, pub payload: AuthTokenPayload, } @@ -75,6 +76,8 @@ impl OrgAuthToken { return Err(AuthTokenParseError); } + let auth_string = auth_string.into(); + Ok(OrgAuthToken { auth_string, payload, @@ -82,7 +85,7 @@ impl OrgAuthToken { } /// Retrieves a reference to the auth token string. - pub fn as_str(&self) -> &str { + pub fn raw(&self) -> &SecretString { &self.auth_string } } diff --git a/src/utils/auth_token/test.rs b/src/utils/auth_token/test.rs index 0ffd5c9833..10f87b4ef9 100644 --- a/src/utils/auth_token/test.rs +++ b/src/utils/auth_token/test.rs @@ -2,7 +2,7 @@ use super::AuthToken; use rstest::rstest; - +use secrecy::ExposeSecret; // Org auth token tests ----------------------------------------------------- #[test] @@ -22,7 +22,7 @@ fn test_valid_org_auth_token() { assert_eq!(payload.org, "sentry"); assert_eq!(payload.url, "http://localhost:8000"); - assert_eq!(good_token, token.to_string()); + assert_eq!(good_token, token.raw().expose_secret().clone()); assert!(token.format_recognized()); } @@ -44,7 +44,7 @@ fn test_valid_org_auth_token_missing_url() { assert_eq!(payload.org, "sentry"); assert!(payload.url.is_empty()); - assert_eq!(good_token, token.to_string()); + assert_eq!(good_token, token.raw().expose_secret().clone()); assert!(token.format_recognized()); } @@ -60,7 +60,7 @@ fn test_valid_user_auth_token(#[case] token_str: &'static str) { let token = AuthToken::from(good_token.clone()); assert!(token.payload().is_none()); - assert_eq!(good_token, token.to_string()); + assert_eq!(good_token, token.raw().expose_secret().clone()); assert!(token.format_recognized()); } @@ -130,7 +130,7 @@ fn test_valid_user_auth_token(#[case] token_str: &'static str) { fn test_unknown_auth_token(#[case] token_str: &'static str) { let token = AuthToken::from(token_str.to_owned()); - assert_eq!(token_str, token.to_string()); + assert_eq!(token_str, token.raw().expose_secret().clone()); assert!(token.payload().is_none()); assert!(!token.format_recognized()); diff --git a/src/utils/auth_token/user_auth_token.rs b/src/utils/auth_token/user_auth_token.rs index 6c0aa135d2..99e3c76330 100644 --- a/src/utils/auth_token/user_auth_token.rs +++ b/src/utils/auth_token/user_auth_token.rs @@ -1,10 +1,11 @@ use super::{AuthTokenParseError, Result, USER_TOKEN_PREFIX}; +use secrecy::SecretString; const USER_TOKEN_BYTES: usize = 32; /// Represents a valid User Auth Token. #[derive(Debug, Clone)] -pub struct UserAuthToken(String); +pub struct UserAuthToken(SecretString); impl UserAuthToken { /// Constructs a new UserAuthToken from a string. Returns an error if the string is not a valid user auth token. @@ -16,14 +17,14 @@ impl UserAuthToken { let bytes = data_encoding::HEXLOWER_PERMISSIVE.decode(secret_portion.as_bytes()); if bytes.is_ok() && bytes.unwrap().len() == USER_TOKEN_BYTES { - Ok(UserAuthToken(auth_string)) + Ok(UserAuthToken(auth_string.into())) } else { Err(AuthTokenParseError) } } /// Retrieves a reference to the auth token string. - pub fn as_str(&self) -> &str { + pub fn raw(&self) -> &SecretString { &self.0 } }