Create & list tokens
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
use crate::app_config::AppConfig;
|
||||
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;
|
||||
@@ -14,6 +18,8 @@ enum MatrixGWUserError {
|
||||
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}")]
|
||||
@@ -101,17 +107,63 @@ impl User {
|
||||
}
|
||||
}
|
||||
|
||||
/// Single API client information
|
||||
/// Base API token information
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||
pub struct APIToken {
|
||||
/// Token unique ID
|
||||
pub id: APITokenID,
|
||||
|
||||
/// Client description
|
||||
pub description: String,
|
||||
pub struct BaseAPIToken {
|
||||
/// Token name
|
||||
pub name: String,
|
||||
|
||||
/// Restricted API network for token
|
||||
pub network: Option<ipnet::IpNet>,
|
||||
pub networks: Option<Vec<ipnet::IpNet>>,
|
||||
|
||||
/// Read only access
|
||||
pub read_only: bool,
|
||||
|
||||
/// Token max inactivity
|
||||
pub max_inactivity: u32,
|
||||
|
||||
/// Token expiration
|
||||
pub expiration: Option<u64>,
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -121,15 +173,58 @@ pub struct APIToken {
|
||||
|
||||
/// 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 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);
|
||||
@@ -158,13 +253,21 @@ impl APIToken {
|
||||
}
|
||||
|
||||
pub fn shall_update_time_used(&self) -> bool {
|
||||
let refresh_interval = min(600, self.max_inactivity / 10);
|
||||
let refresh_interval = min(600, self.base.max_inactivity / 10);
|
||||
|
||||
(self.last_used) < time_secs() - refresh_interval
|
||||
(self.last_used) < time_secs() - refresh_interval as u64
|
||||
}
|
||||
|
||||
pub fn is_expired(&self) -> bool {
|
||||
(self.last_used + self.max_inactivity) < time_secs()
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user