use axum_login::{AuthManagerLayer, AuthUser, AuthnBackend, UserId}; use diesel::prelude::*; use diesel_async::RunQueryDsl; use tower_sessions_redis_store::RedisStore; use crate::models::user::{DbUser, UserCredentials}; use crate::server::{ database::{DbConn, DbPool}, key_val_store::KeyValPool, }; use crate::util::error::{Contextualize, Error, Result}; pub type AuthLayer = AuthManagerLayer>; pub type AuthSession = axum_login::AuthSession; impl AuthUser for DbUser { type Id = i32; fn id(&self) -> Self::Id { self.id } fn session_auth_hash(&self) -> &[u8] { self.hashed_password.auth_hash() } } #[derive(Clone)] pub struct AuthBackend { pub db_pool: DbPool, } impl AuthnBackend for AuthBackend { type User = DbUser; type Credentials = UserCredentials; type Error = Error; async fn authenticate( &self, attempt_creds: Self::Credentials, ) -> Result, Self::Error> { let mut db_conn = self .db_pool .get() .await .err_context("Failed to get database pool connection")?; let user = get_user_by_username(&mut db_conn, attempt_creds.username) .await .err_context("Error fetching user for authentication check")?; let Some(user) = user else { return Ok(None) }; let password_result = user .hashed_password .check(attempt_creds.password) .err_context("Error checking user password attempt")?; if password_result { Ok(Some(user)) } else { Ok(None) } } async fn get_user(&self, user_id: &UserId) -> Result, Self::Error> { let mut db_conn = self .db_pool .get() .await .err_context("Failed to get database pool connection")?; get_user_by_id(&mut db_conn, *user_id) .await .err_context("Failed fetching user for session") } } pub async fn get_user_by_id(db_conn: &mut DbConn, id: i32) -> Result> { crate::schema::users::table .find(id) .first(db_conn) .await .optional() .err_context("Error fetching user from database by id") } pub async fn get_user_by_username( db_conn: &mut DbConn, username: String, ) -> Result> { crate::schema::users::table .filter(crate::schema::users::username.eq(username)) .first(db_conn) .await .optional() .err_context("Error fetching user from database by username") } /// Create the authentication middleware layer pub fn build_auth_layer(db_pool: DbPool, key_val_pool: KeyValPool) -> AuthLayer { use axum_login::{AuthManagerLayerBuilder, tower_sessions::SessionManagerLayer}; use tower_sessions_redis_store::RedisStore; let auth_session_store = RedisStore::new(key_val_pool); let session_layer = SessionManagerLayer::new(auth_session_store); let auth_backend = AuthBackend { db_pool }; AuthManagerLayerBuilder::new(auth_backend, session_layer).build() }