use crate::app_config::AppConfig; use crate::broadcast_messages::{BroadcastMessage, BroadcastSender}; use crate::constants; use crate::controllers::server_controller::ServerConstraints; use crate::matrix_connection::matrix_client::EncryptionRecoveryState; use crate::utils::rand_utils::rand_string; use crate::utils::time_utils::time_secs; use anyhow::Context; 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 create API token directory: {0}")] CreateApiTokensDirectory(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) } } /// Base API token information #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct BaseAPIToken { /// Token name pub name: String, /// Restricted API network for token pub networks: Option>, /// Token max inactivity pub max_inactivity: u32, /// Token expiration pub expiration: Option, /// Read only access pub read_only: bool, } impl BaseAPIToken { /// Check API token information validity pub fn check(&self) -> Option<&'static str> { let constraints = ServerConstraints::default(); if !lazy_regex::regex!("^[a-zA-Z0-9 :-]+$").is_match(&self.name) { return Some("Token name contains invalid characters!"); } if !constraints.token_name.check_str(&self.name) { return Some("Invalid token name length!"); } if !constraints .token_max_inactivity .check_u32(self.max_inactivity) { return Some("Invalid token max inactivity!"); } if let Some(expiration) = self.expiration && expiration <= time_secs() { return Some("Given expiration time is in the past!"); } None } } /// Single API token information #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct APIToken { #[serde(flatten)] pub base: BaseAPIToken, /// Token unique ID pub id: APITokenID, /// Client secret pub secret: String, /// Client creation time pub created: u64, /// Client last usage time pub last_used: u64, } impl APIToken { /// Get the list of tokens of a user pub async fn list_user(email: &UserEmail) -> anyhow::Result> { let tokens_dir = AppConfig::get().user_api_token_directory(email); if !tokens_dir.exists() { return Ok(vec![]); } let mut list = vec![]; for u in std::fs::read_dir(&tokens_dir)? { let entry = u?; list.push( Self::load( email, &APITokenID::from_str( entry .file_name() .to_str() .context("Cannot decode API Token ID as string!")?, )?, ) .await?, ); } Ok(list) } /// Create a new token pub async fn create(email: &UserEmail, base: BaseAPIToken) -> anyhow::Result { let tokens_dir = AppConfig::get().user_api_token_directory(email); if !tokens_dir.exists() { std::fs::create_dir_all(tokens_dir) .map_err(MatrixGWUserError::CreateApiTokensDirectory)?; } let token = APIToken { base, id: Default::default(), secret: rand_string(constants::TOKENS_LEN), created: time_secs(), last_used: time_secs(), }; token.write(email).await?; Ok(token) } /// 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, tx: &BroadcastSender) -> 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)?; if let Err(e) = tx.send(BroadcastMessage::APITokenDeleted(self)) { log::error!("Failed to notify API token deletion! {e}"); } Ok(()) } pub fn shall_update_time_used(&self) -> bool { let refresh_interval = min(600, self.base.max_inactivity / 10); (self.last_used) < time_secs() - refresh_interval as u64 } pub fn is_expired(&self) -> bool { // Check for hard coded expiration if let Some(exp_time) = self.base.expiration && exp_time < time_secs() { return true; } // Control max token inactivity (self.last_used + self.base.max_inactivity as u64) < time_secs() } } #[derive(serde::Serialize, Debug, Clone)] pub struct ExtendedUserInfo { #[serde(flatten)] pub user: User, pub matrix_account_connected: bool, pub matrix_user_id: Option, pub matrix_device_id: Option, pub matrix_recovery_state: EncryptionRecoveryState, }