Skip to content

Commit

Permalink
ref(token): Use secrecy crate to store auth token
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 committed Aug 1, 2024
1 parent 35547f7 commit 28026ee
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 28026ee

Please sign in to comment.