205 lines
5.4 KiB
Rust
205 lines
5.4 KiB
Rust
//! # API tokens management
|
|
|
|
use crate::app_config::AppConfig;
|
|
use crate::constants;
|
|
use crate::utils::jwt_utils;
|
|
use crate::utils::jwt_utils::{TokenPrivKey, TokenPubKey};
|
|
use crate::utils::time_utils::time;
|
|
use actix_http::Method;
|
|
use std::path::Path;
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
|
pub struct TokenID(pub uuid::Uuid);
|
|
|
|
impl TokenID {
|
|
/// Parse a string as a token id
|
|
pub fn parse(t: &str) -> anyhow::Result<Self> {
|
|
Ok(Self(uuid::Uuid::parse_str(t)?))
|
|
}
|
|
}
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
|
pub struct TokenRight {
|
|
verb: TokenVerb,
|
|
uri: String,
|
|
}
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
|
pub struct TokenRights(Vec<TokenRight>);
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
|
pub struct Token {
|
|
pub id: TokenID,
|
|
pub name: String,
|
|
pub description: String,
|
|
created: u64,
|
|
updated: u64,
|
|
#[serde(skip_serializing_if = "TokenPubKey::is_invalid")]
|
|
pub pub_key: TokenPubKey,
|
|
pub rights: TokenRights,
|
|
pub last_used: u64,
|
|
pub ip_restriction: Option<ipnetwork::IpNetwork>,
|
|
pub max_inactivity: Option<u64>,
|
|
}
|
|
|
|
impl Token {
|
|
/// Turn the token into a JSON string
|
|
fn save(&self) -> anyhow::Result<()> {
|
|
let json = serde_json::to_string(self)?;
|
|
|
|
std::fs::write(AppConfig::get().api_token_definition_path(self.id), json)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Load token information from a file
|
|
fn load_from_file(path: &Path) -> anyhow::Result<Self> {
|
|
Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?)
|
|
}
|
|
|
|
/// Check whether a token is expired or not
|
|
pub fn is_expired(&self) -> bool {
|
|
if let Some(max_inactivity) = self.max_inactivity {
|
|
if max_inactivity + self.last_used < time() {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
/// Check whether last_used shall be updated or not
|
|
pub fn should_update_last_activity(&self) -> bool {
|
|
self.last_used + 3600 < time()
|
|
}
|
|
}
|
|
|
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
|
|
pub enum TokenVerb {
|
|
GET,
|
|
POST,
|
|
PUT,
|
|
PATCH,
|
|
DELETE,
|
|
}
|
|
|
|
impl TokenVerb {
|
|
pub fn as_method(&self) -> Method {
|
|
match self {
|
|
TokenVerb::GET => Method::GET,
|
|
TokenVerb::POST => Method::POST,
|
|
TokenVerb::PUT => Method::PUT,
|
|
TokenVerb::PATCH => Method::PATCH,
|
|
TokenVerb::DELETE => Method::DELETE,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Structure used to create a token
|
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
|
pub struct NewToken {
|
|
pub name: String,
|
|
pub description: String,
|
|
pub rights: TokenRights,
|
|
pub ip_restriction: Option<ipnetwork::IpNetwork>,
|
|
pub delete_after_inactivity: Option<u64>,
|
|
}
|
|
|
|
impl TokenRights {
|
|
pub fn check_error(&self) -> Option<&'static str> {
|
|
for r in &self.0 {
|
|
if !r.uri.starts_with("/api/") {
|
|
return Some("All API rights shall start with /api/");
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
impl NewToken {
|
|
/// Check for error in token
|
|
pub fn check_error(&self) -> Option<&'static str> {
|
|
if self.name.len() < constants::API_TOKEN_NAME_MIN_LENGTH {
|
|
return Some("Name is too short!");
|
|
}
|
|
|
|
if self.name.len() > constants::API_TOKEN_NAME_MAX_LENGTH {
|
|
return Some("Name is too long!");
|
|
}
|
|
|
|
if self.description.len() < constants::API_TOKEN_DESCRIPTION_MIN_LENGTH {
|
|
return Some("Description is too short!");
|
|
}
|
|
|
|
if self.description.len() > constants::API_TOKEN_DESCRIPTION_MAX_LENGTH {
|
|
return Some("Description is too long!");
|
|
}
|
|
|
|
if let Some(err) = self.rights.check_error() {
|
|
return Some(err);
|
|
}
|
|
|
|
if let Some(t) = self.delete_after_inactivity {
|
|
if t < 3600 {
|
|
return Some("API tokens shall be valid for at least 1 hour!");
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Create a new Token
|
|
pub async fn create(t: &NewToken) -> anyhow::Result<(Token, TokenPrivKey)> {
|
|
let (pub_key, priv_key) = jwt_utils::generate_key_pair()?;
|
|
|
|
let token = Token {
|
|
name: t.name.to_string(),
|
|
description: t.description.to_string(),
|
|
id: TokenID(uuid::Uuid::new_v4()),
|
|
created: time(),
|
|
updated: time(),
|
|
pub_key,
|
|
rights: t.rights.clone(),
|
|
last_used: time(),
|
|
ip_restriction: t.ip_restriction,
|
|
max_inactivity: t.delete_after_inactivity,
|
|
};
|
|
|
|
token.save()?;
|
|
|
|
Ok((token, priv_key))
|
|
}
|
|
|
|
/// Get the entire list of api tokens
|
|
pub async fn full_list() -> anyhow::Result<Vec<Token>> {
|
|
let mut list = Vec::new();
|
|
for f in std::fs::read_dir(AppConfig::get().api_tokens_path())? {
|
|
list.push(Token::load_from_file(&f?.path())?);
|
|
}
|
|
Ok(list)
|
|
}
|
|
|
|
/// Get the information about a single token
|
|
pub async fn get_single(id: TokenID) -> anyhow::Result<Token> {
|
|
Token::load_from_file(&AppConfig::get().api_token_definition_path(id))
|
|
}
|
|
|
|
/// Update API tokens rights
|
|
pub async fn update_rights(id: TokenID, rights: TokenRights) -> anyhow::Result<()> {
|
|
let mut token = get_single(id).await?;
|
|
token.rights = rights;
|
|
token.updated = time();
|
|
token.save()?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Delete an API token
|
|
pub async fn delete(id: TokenID) -> anyhow::Result<()> {
|
|
let path = AppConfig::get().api_token_definition_path(id);
|
|
if path.exists() {
|
|
std::fs::remove_file(path)?;
|
|
}
|
|
Ok(())
|
|
}
|