use data_encoding::HEXUPPER; use mongod::{ assert_reference_of, derive::{Model, Referencable}, Model, Referencable, Reference, Validate, }; use mongodb::bson::doc; use rand::RngCore; use serde::{Deserialize, Serialize}; use serde_json::json; fn gen_token(token_length: usize) -> String { let mut token_bytes = vec![0u8; token_length]; rand::thread_rng().fill_bytes(&mut token_bytes); HEXUPPER.encode(&token_bytes) } #[derive(Debug, Clone, Serialize, Deserialize, Model, Referencable)] pub struct User { pub _id: String, pub username: String, pub password: String, pub role: UserRole, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum UserRole { Regular, Admin, } impl Validate for User { async fn validate(&self) -> Result<(), String> { Ok(()) } } impl User { pub async fn create(username: &str, password: &str, role: UserRole) -> Option { if User::find_one_partial(doc! { "username": username }, json!({})) .await .is_some() { return None; } let u = User { _id: uuid::Uuid::new_v4().to_string(), username: username.to_string(), password: bcrypt::hash(password, bcrypt::DEFAULT_COST).unwrap(), role, }; u.insert().await.ok()?; Some(u) } pub async fn login(username: &str, password: &str) -> Option { let u = User::find_one(doc! { "username": username }).await?; if !u.verify_pw(password) { return None; } Some(u.session().await) } pub async fn session(&self) -> Session { let s = Session { _id: uuid::Uuid::new_v4().to_string(), token: gen_token(60), user: self.reference(), }; s.insert().await.unwrap(); s } pub fn is_admin(&self) -> bool { matches!(self.role, UserRole::Admin) } pub fn verify_pw(&self, password: &str) -> bool { bcrypt::verify(password, &self.password).unwrap() } } #[derive(Debug, Clone, Serialize, Deserialize, Model, Referencable)] pub struct Session { pub _id: String, pub token: String, pub user: Reference, } impl Validate for Session { async fn validate(&self) -> Result<(), String> { assert_reference_of!(self.user, User); Ok(()) } }