use argon2::{
Argon2,
password_hash::{
PasswordHash, PasswordHasher, PasswordVerifier as ArgonVerifier, SaltString,
rand_core::OsRng,
},
};
use crate::error::{SaTokenError, SaTokenResult};
pub(super) const ARGON2_PREFIX: &str = "$argon2";
pub struct ClientSecretHasher;
impl ClientSecretHasher {
pub fn hash_plain_secret(plain: &str) -> SaTokenResult<String> {
if plain.is_empty() {
return Err(SaTokenError::OAuth2InvalidCredentials);
}
let salt = SaltString::generate(&mut OsRng);
let hash = Argon2::default()
.hash_password(plain.as_bytes(), &salt)
.map_err(|e| SaTokenError::InternalError(format!("hash client secret failed: {e}")))?;
Ok(hash.to_string())
}
pub fn is_hashed(stored: &str) -> bool {
stored.starts_with(ARGON2_PREFIX)
}
pub fn verify_plain_secret(plain: &str, stored_hash: &str) -> SaTokenResult<bool> {
let parsed =
PasswordHash::new(stored_hash).map_err(|_| SaTokenError::OAuth2InvalidCredentials)?;
match Argon2::default().verify_password(plain.as_bytes(), &parsed) {
Ok(()) => Ok(true),
Err(argon2::password_hash::Error::Password) => Ok(false),
Err(e) => Err(SaTokenError::InternalError(format!(
"verify client secret failed: {e}"
))),
}
}
}
impl std::fmt::Debug for ClientSecretHasher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("ClientSecretHasher { .. }")
}
}