use std::collections::HashSet; use argon2::{ password_hash::{rand_core::OsRng, PasswordHasher, SaltString}, Argon2, PasswordHash, PasswordVerifier, }; use axum::{ extract::FromRequestParts, http::{request::Parts, StatusCode}, RequestPartsExt, }; use axum_extra::{ extract::cookie::{Cookie, CookieJar}, headers::{authorization::Bearer, Authorization}, typed_header::TypedHeaderRejectionReason, TypedHeader, }; use bearer::verify_bearer; pub use error::AuthError; use rand::distr::Alphanumeric; use rand::prelude::*; use rand_chacha::ChaCha20Rng; use tokio::task; use crate::database::model::User; mod bearer; mod error; mod scopes; #[derive(Debug)] pub struct Permissions<'a>(pub HashSet<&'a str>); // Middleware for getting permissions impl FromRequestParts for Permissions<'_> where S: Send + Sync, { type Rejection = crate::Error; async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { // First check if the request has a beaerer token to authenticate match parts.extract::>>().await { Ok(bearer) => { verify_bearer(bearer.token().to_string()).map_err(|_| AuthError::InvalidToken)?; let permissions = Permissions { 0: HashSet::from(["root"]), }; return Ok(permissions); } Err(err) => match err.reason() { TypedHeaderRejectionReason::Missing => (), TypedHeaderRejectionReason::Error(_err) => { return Err(AuthError::InvalidToken.into()) } _ => return Err(AuthError::Unexpected.into()), }, }; match parts.extract::().await { Ok(jar) => { if let Some(session_token) = jar.get("session_token") { // TODO: Implement function to retrieve user permissions tracing::info!("{session_token:?}") } } Err(_) => (), } Err(AuthError::Unauthorized.into()) } } pub async fn generate_password_hash( password: String, ) -> Result { let password_hash: Result = task::spawn_blocking(move || { let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); let password_hash = argon2 .hash_password(password.as_bytes(), &salt)? .to_string(); Ok(password_hash) }) .await .unwrap(); Ok(password_hash?) } pub async fn verify_password_hash( password: &str, hash: &str, ) -> Result<(), argon2::password_hash::Error> { let parsed_hash = PasswordHash::new(hash)?; Argon2::default().verify_password(password.as_bytes(), &parsed_hash)?; Ok(()) } pub fn generate_session_token() -> String { let session = ChaCha20Rng::from_os_rng() .sample_iter(&Alphanumeric) .take(60) .map(char::from) .collect::(); session }