use async_trait::async_trait; use axum::extract::{Request, State}; use axum::middleware::Next; use axum::response::{IntoResponse, Response}; use time::OffsetDateTime; use uuid::Uuid; use crate::error::SessionError; use crate::share::session::{pwd_version_matches, session_token_key}; use crate::{Session, SessionUser, SessionUserMut}; pub struct SessionWithUser { pub user_id: Uuid, pub is_revoked: bool, pub expires_at: OffsetDateTime, pub is_active: bool, pub prohibit_login: bool, pub is_restricted: bool, pub passwd_version: Option, } #[async_trait] pub trait SessionGuard: Send + Sync { async fn lookup_session_with_user( &self, token_hash: &str, ) -> Result, SessionError>; } /// Default `SessionGuard::lookup_session_with_user` body shared by all services. pub async fn lookup_session_with_user( db: &sea_orm::DatabaseConnection, token_hash: &str, ) -> Result, SessionError> { use models::users::{user, user_sessions}; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; let outcome = user_sessions::Entity::find() .filter(user_sessions::Column::TokenHash.eq(token_hash)) .find_also_related(user::Entity) .one(db) .await?; Ok(outcome.and_then(|(row, current)| { current.map(|c| SessionWithUser { user_id: row.user_id, is_revoked: row.is_revoked, expires_at: row.expires_at, is_active: c.is_active, prohibit_login: c.prohibit_login, is_restricted: c.is_restricted, passwd_version: c.passwd_version, }) })) } pub async fn session_guard( State(service): State, session: Session, request: Request, next: Next, ) -> Response where S: SessionGuard, { let Some(uid) = session.uid() else { return SessionError::Unauthorized.into_response(); }; let Some(token_hash) = session.get::(&session_token_key(uid)) else { return SessionError::Unauthorized.into_response(); }; let lookup = match service.lookup_session_with_user(&token_hash).await { Ok(value) => value, Err(error) => return error.into_response(), }; let Some(row) = lookup else { session.logout_user(uid); return SessionError::Unauthorized.into_response(); }; let authorized = !row.is_revoked && row.user_id == uid && row.expires_at > OffsetDateTime::now_utc() && row.is_active && !row.prohibit_login && !row.is_restricted && pwd_version_matches(&session, row.passwd_version); if !authorized { session.logout_user(uid); return SessionError::Unauthorized.into_response(); } next.run(request).await }