use { crate::{db, db::Database}, argon2::{ password_hash::{ rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString, }, Argon2, }, std::ops::Deref, tracing::{error, warn}, }; mod jwt; pub use jwt::{authenticate_user_with_jwt, create_jwt_for_user, Error as JwtError, ParsedJwt}; #[derive(Debug)] pub enum AuthenticationError { DatabaseError(db::Error), HashError(argon2::password_hash::Error), } impl std::fmt::Display for AuthenticationError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { AuthenticationError::HashError(e) => write!(f, "{}", e), AuthenticationError::DatabaseError(e) => { write!(f, "Could not get password hash from database: {}", e) } } } } impl std::error::Error for AuthenticationError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { AuthenticationError::HashError(_) => None, AuthenticationError::DatabaseError(e) => Some(e), } } } impl From for AuthenticationError { fn from(value: db::Error) -> Self { warn!(details = value.to_string(), "Database error"); AuthenticationError::DatabaseError(value) } } impl From for AuthenticationError { fn from(value: argon2::password_hash::Error) -> Self { error!(details = value.to_string(), "Error hashing password."); AuthenticationError::HashError(value) } } #[derive(Debug, Clone)] pub struct AuthenticatedUser(db::User); impl Deref for AuthenticatedUser { type Target = db::User; fn deref(&self) -> &db::User { &self.0 } } #[derive(Debug)] pub struct AuthenticatedAdminUser(db::User); impl Deref for AuthenticatedAdminUser { type Target = db::User; fn deref(&self) -> &db::User { &self.0 } } pub struct Password { hash: String, } impl Password { #[tracing::instrument] pub fn new(password: &str) -> Result { let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); Ok(Password { hash: argon2 .hash_password(password.as_bytes(), &salt)? .to_string(), }) } pub fn check(&self, password: &str) -> Result { let hash = PasswordHash::new(&self.hash)?; Ok(Argon2::default() .verify_password(password.as_bytes(), &hash) .is_ok()) } } impl From for Password { fn from(password: db::PasswordHash) -> Self { Password { hash: password.to_string(), } } } impl From for db::PasswordHash { fn from(password: Password) -> Self { db::PasswordHash(password.hash) } } #[tracing::instrument] pub async fn authenticate_user_with_password( db: &D, user: db::User, supplied_password: &str, ) -> Result, AuthenticationError> { let password: Password = db.get_password_for_user(&user).await?.into(); Ok(if password.check(supplied_password)? { Some(AuthenticatedUser(user)) } else { None }) } #[tracing::instrument] pub async fn check_if_user_is_admin( db: &D, user: &AuthenticatedUser, ) -> Result, db::Error> { if db.is_user_admin(user).await? { Ok(Some(AuthenticatedAdminUser(user.0.clone()))) } else { Ok(None) } }