use crate::app_config::AppConfig; use crate::utils::time_utils::time_secs; use jwt_simple::reexports::serde_json; use std::cmp::min; use std::str::FromStr; /// Matrix Gateway user errors #[derive(thiserror::Error, Debug)] enum MatrixGWUserError { #[error("Failed to load user metadata: {0}")] LoadUserMetadata(std::io::Error), #[error("Failed to decode user metadata: {0}")] DecodeUserMetadata(serde_json::Error), #[error("Failed to save user metadata: {0}")] SaveUserMetadata(std::io::Error), #[error("Failed to delete API token: {0}")] DeleteToken(std::io::Error), #[error("Failed to load API token: {0}")] LoadApiToken(std::io::Error), #[error("Failed to decode API token: {0}")] DecodeApiToken(serde_json::Error), #[error("API Token does not exists!")] ApiTokenDoesNotExists, #[error("Failed to save API token: {0}")] SaveAPIToken(std::io::Error), } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, Hash)] pub struct UserEmail(pub String); impl UserEmail { pub fn is_valid(&self) -> bool { mailchecker::is_valid(&self.0) } } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq)] pub struct APITokenID(pub uuid::Uuid); impl Default for APITokenID { fn default() -> Self { Self(uuid::Uuid::new_v4()) } } impl FromStr for APITokenID { type Err = uuid::Error; fn from_str(s: &str) -> Result { Ok(Self(uuid::Uuid::from_str(s)?)) } } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct User { pub email: UserEmail, pub name: String, pub time_create: u64, pub last_login: u64, } impl User { /// Get a user by its mail pub async fn get_by_mail(mail: &UserEmail) -> anyhow::Result { let path = AppConfig::get().user_metadata_file_path(mail); let data = std::fs::read_to_string(path).map_err(MatrixGWUserError::LoadUserMetadata)?; Ok(serde_json::from_str(&data).map_err(MatrixGWUserError::DecodeUserMetadata)?) } /// Update user metadata on disk pub async fn write(&self) -> anyhow::Result<()> { let path = AppConfig::get().user_metadata_file_path(&self.email); std::fs::write(&path, serde_json::to_string(&self)?) .map_err(MatrixGWUserError::SaveUserMetadata)?; Ok(()) } /// Create or update user information pub async fn create_or_update_user(mail: &UserEmail, name: &str) -> anyhow::Result { let storage_dir = AppConfig::get().user_directory(mail); let mut user = if !storage_dir.exists() { std::fs::create_dir_all(storage_dir)?; User { email: mail.clone(), name: name.to_string(), time_create: time_secs(), last_login: time_secs(), } } else { Self::get_by_mail(mail).await? }; // Update some user information user.name = name.to_string(); user.last_login = time_secs(); user.write().await?; Ok(user) } } /// Single API client information #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct APIToken { /// Token unique ID pub id: APITokenID, /// Client description pub description: String, /// Restricted API network for token pub network: Option, /// Client secret pub secret: String, /// Client creation time pub created: u64, /// Client last usage time pub last_used: u64, /// Read only access pub read_only: bool, /// Token max inactivity pub max_inactivity: u64, } impl APIToken { /// Get a token information pub async fn load(email: &UserEmail, id: &APITokenID) -> anyhow::Result { let token_file = AppConfig::get().user_api_token_metadata_file(email, id); match token_file.exists() { true => Ok(serde_json::from_str::( &std::fs::read_to_string(&token_file).map_err(MatrixGWUserError::LoadApiToken)?, ) .map_err(MatrixGWUserError::DecodeApiToken)?), false => Err(MatrixGWUserError::ApiTokenDoesNotExists.into()), } } /// Write this token information pub async fn write(&self, mail: &UserEmail) -> anyhow::Result<()> { let path = AppConfig::get().user_api_token_metadata_file(mail, &self.id); std::fs::write(&path, serde_json::to_string(&self)?) .map_err(MatrixGWUserError::SaveAPIToken)?; Ok(()) } /// Delete this token pub async fn delete(self, email: &UserEmail) -> anyhow::Result<()> { let token_file = AppConfig::get().user_api_token_metadata_file(email, &self.id); std::fs::remove_file(&token_file).map_err(MatrixGWUserError::DeleteToken)?; Ok(()) } pub fn shall_update_time_used(&self) -> bool { let refresh_interval = min(600, self.max_inactivity / 10); (self.last_used) < time_secs() - refresh_interval } pub fn is_expired(&self) -> bool { (self.last_used + self.max_inactivity) < time_secs() } } #[derive(serde::Serialize, Debug, Clone)] pub struct ExtendedUserInfo { #[serde(flatten)] pub user: User, pub matrix_user_id: Option, } impl ExtendedUserInfo { pub async fn from_user(user: User) -> anyhow::Result { Ok(Self { user, matrix_user_id: None, // TODO }) } }