use axum_login::{AuthUser, AuthnBackend, UserId}; use sea_orm::{DatabaseConnection, EntityTrait, ColumnTrait, QueryFilter}; use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; use crate::models::{user, User}; #[derive(Debug, Clone)] pub struct AuthBackend { pub db: DatabaseConnection, } impl AuthUser for user::Model { type Id = i32; fn id(&self) -> Self::Id { self.id } fn session_auth_hash(&self) -> &[u8] { self.password_hash.as_deref().unwrap_or("oauth").as_bytes() } } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Database error: {0}")] Database(#[from] sea_orm::DbErr), #[error("Password hashing error")] PasswordHash, #[error("Invalid credentials")] InvalidCredentials, } #[derive(Debug, Clone)] pub struct Credentials { pub username: String, pub password: String, } #[async_trait::async_trait] impl AuthnBackend for AuthBackend { type User = user::Model; type Credentials = Credentials; type Error = Error; async fn authenticate( &self, creds: Self::Credentials, ) -> Result, Self::Error> { let user = User::find() .filter(user::Column::Username.eq(&creds.username)) .one(&self.db) .await?; if let Some(user) = user { let password_hash = user.password_hash.as_ref().ok_or(Error::InvalidCredentials)?; let parsed_hash = PasswordHash::new(password_hash) .map_err(|_| Error::PasswordHash)?; let is_valid = Argon2::default() .verify_password(creds.password.as_bytes(), &parsed_hash) .is_ok(); if is_valid { Ok(Some(user)) } else { Err(Error::InvalidCredentials) } } else { Err(Error::InvalidCredentials) } } async fn get_user(&self, user_id: &UserId) -> Result, Self::Error> { let user = User::find_by_id(*user_id).one(&self.db).await?; Ok(user) } } pub fn hash_password(password: &str) -> Result { let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); argon2 .hash_password(password.as_bytes(), &salt) .map(|hash| hash.to_string()) .map_err(|_| Error::PasswordHash) }