use crate::context::{AuthContext, SecretMaterial};
use crate::crypto::{parse_envelope, symmetric_decrypt, symmetric_encrypt};
use crate::error::RustAuthError;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct StoredOAuthTokens {
pub access_token: Option<String>,
pub refresh_token: Option<String>,
pub id_token: Option<String>,
}
pub fn set_token_util(
token: Option<&str>,
context: &AuthContext,
) -> Result<Option<String>, RustAuthError> {
let Some(token) = token else {
return Ok(None);
};
if context.options.account.encrypt_oauth_tokens {
encrypt_with_context(token, context).map(Some)
} else {
Ok(Some(token.to_owned()))
}
}
pub fn encrypt_oauth_tokens_for_storage(
access_token: Option<&str>,
refresh_token: Option<&str>,
id_token: Option<&str>,
context: &AuthContext,
) -> Result<StoredOAuthTokens, RustAuthError> {
Ok(StoredOAuthTokens {
access_token: set_token_util(access_token, context)?,
refresh_token: set_token_util(refresh_token, context)?,
id_token: set_token_util(id_token, context)?,
})
}
pub fn decrypt_oauth_token(token: &str, context: &AuthContext) -> Result<String, RustAuthError> {
if token.is_empty() || !context.options.account.encrypt_oauth_tokens {
return Ok(token.to_owned());
}
if !is_likely_encrypted(token) {
return Ok(token.to_owned());
}
decrypt_with_context(token, context)
}
pub fn decrypt_optional_oauth_token(
token: Option<&str>,
context: &AuthContext,
) -> Result<Option<String>, RustAuthError> {
token
.map(|token| decrypt_oauth_token(token, context))
.transpose()
}
pub(crate) fn encrypt_with_context(
data: &str,
context: &AuthContext,
) -> Result<String, RustAuthError> {
match &context.secret_config {
SecretMaterial::Single(secret) => symmetric_encrypt(secret.as_str(), data),
SecretMaterial::Rotating(config) => symmetric_encrypt(config, data),
}
}
pub(crate) fn decrypt_with_context(
data: &str,
context: &AuthContext,
) -> Result<String, RustAuthError> {
match &context.secret_config {
SecretMaterial::Single(secret) => symmetric_decrypt(secret.as_str(), data),
SecretMaterial::Rotating(config) => symmetric_decrypt(config, data),
}
}
fn is_likely_encrypted(token: &str) -> bool {
parse_envelope(token).is_some()
|| (!token.is_empty()
&& token.len() % 2 == 0
&& token.chars().all(|character| character.is_ascii_hexdigit()))
}