Skip to content

Commit

Permalink
ref(token): Use secrecy crate to store auth token (#2116)
Browse files Browse the repository at this point in the history
Using the secrecy crate prevents the auth token from accidentally being leaked if we log the AuthToken struct.
  • Loading branch information
szokeasaurusrex authored Aug 1, 2024
1 parent 3d05326 commit 8b89630
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 29 deletions.
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
6 changes: 5 additions & 1 deletion src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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()),
)
}
}
}
Expand Down
8 changes: 6 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
24 changes: 8 additions & 16 deletions src/utils/auth_token/auth_token_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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,
Expand All @@ -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 {
Expand All @@ -66,7 +58,7 @@ enum AuthTokenInner {
User(UserAuthToken),

/// Represents an auth token that has an unrecognized format.
Unknown(String),
Unknown(SecretString),
}

impl AuthTokenInner {
Expand All @@ -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())
}
}

Expand All @@ -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,
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/utils/auth_token/org_auth_token.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use super::{AuthTokenParseError, Result, ORG_AUTH_TOKEN_PREFIX};
use secrecy::SecretString;
use serde::{Deserialize, Deserializer};

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,
}

Expand Down Expand Up @@ -75,14 +76,16 @@ impl OrgAuthToken {
return Err(AuthTokenParseError);
}

let auth_string = auth_string.into();

Ok(OrgAuthToken {
auth_string,
payload,
})
}

/// Retrieves a reference to the auth token string.
pub fn as_str(&self) -> &str {
pub fn raw(&self) -> &SecretString {
&self.auth_string
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/utils/auth_token/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use super::AuthToken;
use rstest::rstest;

use secrecy::ExposeSecret;
// Org auth token tests -----------------------------------------------------

#[test]
Expand All @@ -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());
}
Expand All @@ -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());
}
Expand All @@ -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());
}
Expand Down Expand Up @@ -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());
Expand Down
7 changes: 4 additions & 3 deletions src/utils/auth_token/user_auth_token.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
}
}
Expand Down

0 comments on commit 8b89630

Please sign in to comment.