Skip to content
Clone
fad06fd
91 lines 2.8 KB Rust
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<i64>,
}
#[async_trait]
pub trait SessionGuard: Send + Sync {
async fn lookup_session_with_user(
&self,
token_hash: &str,
) -> Result<Option<SessionWithUser>, 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<Option<SessionWithUser>, 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<S>(
State(service): State<S>,
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::<String>(&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
}