use async_trait::async_trait;
use axum_login::{AuthnBackend, UserId};
use password_auth::verify_password;
use serde::Deserialize;
use sqlx::SqlitePool;
use tokio::task;
use crate::db::{Model, User};
#[derive(Debug, Clone, Deserialize)]
pub struct Credentials {
pub username: String,
pub password: String,
}
#[derive(Debug, Clone)]
pub struct Backend {
db: SqlitePool,
}
impl Backend {
pub const fn new(db: SqlitePool) -> Self {
Self { db }
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Sqlx(#[from] sqlx::Error),
#[error(transparent)]
TaskJoin(#[from] task::JoinError),
}
#[async_trait]
impl AuthnBackend for Backend {
type User = User;
type Credentials = Credentials;
type Error = Error;
async fn authenticate(
&self,
creds: Self::Credentials,
) -> Result<Option<Self::User>, Self::Error> {
let user: Self::User = User::get_using_username(&self.db, &creds.username).await?;
task::spawn_blocking(|| {
Ok(Some(user)
.filter(|user| verify_password(creds.password, &user.password_hash).is_ok()))
})
.await?
}
async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> {
let user = User::get_using_id(&self.db, *user_id).await?;
Ok(Some(user))
}
}
pub type AuthSession = axum_login::AuthSession<Backend>;