#[cfg(feature = "server")]
use async_trait::async_trait;
#[cfg(feature = "server")]
use chrono::{DateTime, Utc};
#[cfg(feature = "server")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "server")]
use uuid::Uuid;
#[cfg(feature = "server")]
use super::traits::StorageError;
#[cfg(feature = "server")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub id: Uuid,
pub email: String,
pub name: String,
pub password_hash: String,
pub roles: Vec<String>,
pub is_active: bool,
pub email_verified: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub last_login_at: Option<DateTime<Utc>>,
}
#[cfg(feature = "server")]
#[derive(Debug, Clone, Default)]
pub struct UserFilter {
pub email: Option<String>,
pub is_active: Option<bool>,
pub role: Option<String>,
pub limit: Option<usize>,
pub offset: Option<usize>,
}
#[cfg(feature = "server")]
pub type Result<T> = std::result::Result<T, StorageError>;
#[cfg(feature = "server")]
#[async_trait]
pub trait UserStorage: Send + Sync {
async fn create_user(&self, user: &User) -> Result<Uuid>;
async fn get_user(&self, id: Uuid) -> Result<Option<User>>;
async fn get_user_by_email(&self, email: &str) -> Result<Option<User>>;
async fn update_user(&self, id: Uuid, user: &User) -> Result<()>;
async fn delete_user(&self, id: Uuid) -> Result<()>;
async fn list_users(&self, filter: &UserFilter) -> Result<Vec<User>>;
async fn update_password(&self, id: Uuid, password_hash: &str) -> Result<()>;
async fn update_last_login(&self, id: Uuid) -> Result<()>;
async fn email_exists(&self, email: &str) -> Result<bool> {
Ok(self.get_user_by_email(email).await?.is_some())
}
async fn verify_email(&self, id: Uuid) -> Result<()>;
async fn add_role(&self, id: Uuid, role: &str) -> Result<()>;
async fn remove_role(&self, id: Uuid, role: &str) -> Result<()>;
async fn has_role(&self, id: Uuid, role: &str) -> Result<bool>;
}
#[cfg(feature = "server")]
pub mod password {
use argon2::{
password_hash::{
rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString,
},
Argon2,
};
pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let password_hash = argon2.hash_password(password.as_bytes(), &salt)?;
Ok(password_hash.to_string())
}
pub fn verify_password(
password: &str,
hash: &str,
) -> Result<bool, argon2::password_hash::Error> {
let parsed_hash = PasswordHash::new(hash)?;
let argon2 = Argon2::default();
Ok(argon2
.verify_password(password.as_bytes(), &parsed_hash)
.is_ok())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_password_hashing() {
let password = "my_secure_password_123";
let hash = hash_password(password).unwrap();
assert!(verify_password(password, &hash).unwrap());
assert!(!verify_password("wrong_password", &hash).unwrap());
}
}
}