Add users authentication routes
This commit is contained in:
168
matrixgw_backend/src/users.rs
Normal file
168
matrixgw_backend/src/users.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
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<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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<ipnet::IpNet>,
|
||||
|
||||
/// 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<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) -> 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user