288 lines
8.5 KiB
Rust
288 lines
8.5 KiB
Rust
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<Self, Self::Err> {
|
|
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<Self> {
|
|
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<User> {
|
|
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<Vec<ipnet::IpNet>>,
|
|
|
|
/// Token max inactivity
|
|
pub max_inactivity: u32,
|
|
|
|
/// Token expiration
|
|
pub expiration: Option<u64>,
|
|
|
|
/// 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<Vec<Self>> {
|
|
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<Self> {
|
|
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<Self> {
|
|
let token_file = AppConfig::get().user_api_token_metadata_file(email, id);
|
|
match token_file.exists() {
|
|
true => Ok(serde_json::from_str::<Self>(
|
|
&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_user_id: Option<String>,
|
|
pub matrix_device_id: Option<String>,
|
|
pub matrix_recovery_state: EncryptionRecoveryState,
|
|
}
|