pub mod jwt; pub mod password; use axum::{async_trait, extract::FromRequestParts, http::request::Parts}; use axum_extra::headers::{authorization::Bearer, Authorization}; use axum_extra::TypedHeader; use serde::{Deserialize, Serialize}; use crate::{ error::AppError, state::{AppState, PgPooledConnection}, }; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuthenticatedUser { pub user_id: uuid::Uuid, pub username: String, pub tenant_id: uuid::Uuid, } #[async_trait] impl FromRequestParts for AuthenticatedUser { type Rejection = AppError; async fn from_request_parts( parts: &mut Parts, state: &AppState, ) -> Result { if let Some(user) = parts.extensions.get::() { return Ok(user.clone()); } let TypedHeader(Authorization(bearer)) = TypedHeader::>::from_request_parts(parts, state) .await .map_err(|_| AppError::unauthorized())?; let claims = state .jwt .verify_token(bearer.token()) .map_err(|_| AppError::unauthorized())?; let user = AuthenticatedUser { user_id: claims.sub, username: claims.username, tenant_id: claims.tenant_id, }; parts.extensions.insert(user.clone()); Ok(user) } } pub struct TenantScopedConn { pub conn: PgPooledConnection, pub tenant_id: Uuid, pub user_id: Uuid, pub user: AuthenticatedUser, } impl TenantScopedConn { pub fn conn(&mut self) -> &mut PgPooledConnection { &mut self.conn } } #[async_trait] impl FromRequestParts for TenantScopedConn { type Rejection = AppError; async fn from_request_parts( parts: &mut Parts, state: &AppState, ) -> Result { let user = AuthenticatedUser::from_request_parts(parts, state).await?; let tenant_id = user.tenant_id; let conn = state.db_for_tenant(tenant_id)?; Ok(Self { conn, tenant_id, user_id: user.user_id, user, }) } }