Add API tokens support #9
@ -10,6 +10,15 @@ use std::path::Path;
|
|||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||||
pub struct TokenID(pub uuid::Uuid);
|
pub struct TokenID(pub uuid::Uuid);
|
||||||
|
|
||||||
|
#[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)]
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||||
pub struct Token {
|
pub struct Token {
|
||||||
pub id: TokenID,
|
pub id: TokenID,
|
||||||
@ -19,7 +28,7 @@ pub struct Token {
|
|||||||
updated: u64,
|
updated: u64,
|
||||||
#[serde(skip_serializing_if = "TokenPubKey::is_invalid")]
|
#[serde(skip_serializing_if = "TokenPubKey::is_invalid")]
|
||||||
pub pub_key: TokenPubKey,
|
pub pub_key: TokenPubKey,
|
||||||
pub rights: Vec<TokenRights>,
|
pub rights: TokenRights,
|
||||||
pub last_used: Option<u64>,
|
pub last_used: Option<u64>,
|
||||||
pub ip_restriction: Option<ipnetwork::IpNetwork>,
|
pub ip_restriction: Option<ipnetwork::IpNetwork>,
|
||||||
pub delete_after_inactivity: Option<u64>,
|
pub delete_after_inactivity: Option<u64>,
|
||||||
@ -27,8 +36,12 @@ pub struct Token {
|
|||||||
|
|
||||||
impl Token {
|
impl Token {
|
||||||
/// Turn the token into a JSON string
|
/// Turn the token into a JSON string
|
||||||
pub fn to_json(&self) -> String {
|
fn save(&self) -> anyhow::Result<()> {
|
||||||
serde_json::to_string(self).expect("Failed to serialize an API token!")
|
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
|
/// Load token information from a file
|
||||||
@ -46,22 +59,27 @@ pub enum TokenVerb {
|
|||||||
DELETE,
|
DELETE,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
|
||||||
pub struct TokenRights {
|
|
||||||
verb: TokenVerb,
|
|
||||||
uri: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Structure used to create a token
|
/// Structure used to create a token
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||||
pub struct NewToken {
|
pub struct NewToken {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub rights: Vec<TokenRights>,
|
pub rights: TokenRights,
|
||||||
pub ip_restriction: Option<ipnetwork::IpNetwork>,
|
pub ip_restriction: Option<ipnetwork::IpNetwork>,
|
||||||
pub delete_after_inactivity: Option<u64>,
|
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 {
|
impl NewToken {
|
||||||
/// Check for error in token
|
/// Check for error in token
|
||||||
pub fn check_error(&self) -> Option<&'static str> {
|
pub fn check_error(&self) -> Option<&'static str> {
|
||||||
@ -81,10 +99,8 @@ impl NewToken {
|
|||||||
return Some("Description is too long!");
|
return Some("Description is too long!");
|
||||||
}
|
}
|
||||||
|
|
||||||
for r in &self.rights {
|
if let Some(err) = self.rights.check_error() {
|
||||||
if !r.uri.starts_with("/api/") {
|
return Some(err);
|
||||||
return Some("All API rights shall start with /api/");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(t) = self.delete_after_inactivity {
|
if let Some(t) = self.delete_after_inactivity {
|
||||||
@ -114,11 +130,6 @@ pub async fn create(t: &NewToken) -> anyhow::Result<(Token, TokenPrivKey)> {
|
|||||||
delete_after_inactivity: t.delete_after_inactivity,
|
delete_after_inactivity: t.delete_after_inactivity,
|
||||||
};
|
};
|
||||||
|
|
||||||
std::fs::write(
|
|
||||||
AppConfig::get().api_token_definition_path(token.id),
|
|
||||||
token.to_json(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok((token, priv_key))
|
Ok((token, priv_key))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,3 +146,21 @@ pub async fn full_list() -> anyhow::Result<Vec<Token>> {
|
|||||||
pub async fn get_single(id: TokenID) -> anyhow::Result<Token> {
|
pub async fn get_single(id: TokenID) -> anyhow::Result<Token> {
|
||||||
Token::load_from_file(&AppConfig::get().api_token_definition_path(id))
|
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(())
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! # API tokens management
|
//! # API tokens management
|
||||||
|
|
||||||
use crate::api_tokens;
|
use crate::api_tokens;
|
||||||
use crate::api_tokens::{NewToken, TokenID};
|
use crate::api_tokens::{NewToken, TokenID, TokenRights};
|
||||||
use crate::controllers::api_tokens_controller::rest_token::RestToken;
|
use crate::controllers::api_tokens_controller::rest_token::RestToken;
|
||||||
use crate::controllers::HttpResult;
|
use crate::controllers::HttpResult;
|
||||||
use crate::utils::jwt_utils::TokenPrivKey;
|
use crate::utils::jwt_utils::TokenPrivKey;
|
||||||
@ -70,3 +70,31 @@ pub async fn get_single(path: web::Path<TokenIDInPath>) -> HttpResult {
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok().json(RestToken::new(token)))
|
Ok(HttpResponse::Ok().json(RestToken::new(token)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct UpdateTokenBody {
|
||||||
|
rights: TokenRights,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update a token
|
||||||
|
pub async fn update(
|
||||||
|
path: web::Path<TokenIDInPath>,
|
||||||
|
body: web::Json<UpdateTokenBody>,
|
||||||
|
) -> HttpResult {
|
||||||
|
if let Some(err) = body.rights.check_error() {
|
||||||
|
log::error!("Failed to validate updated API token information! {err}");
|
||||||
|
return Ok(HttpResponse::BadRequest()
|
||||||
|
.json(format!("Failed to validate API token information! {err}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
api_tokens::update_rights(path.uid, body.0.rights).await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Accepted().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a token
|
||||||
|
pub async fn delete(path: web::Path<TokenIDInPath>) -> HttpResult {
|
||||||
|
api_tokens::delete(path.uid).await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Accepted().finish())
|
||||||
|
}
|
||||||
|
@ -290,14 +290,14 @@ async fn main() -> std::io::Result<()> {
|
|||||||
"/api/tokens/{uid}",
|
"/api/tokens/{uid}",
|
||||||
web::get().to(api_tokens_controller::get_single),
|
web::get().to(api_tokens_controller::get_single),
|
||||||
)
|
)
|
||||||
/* TODO .route(
|
.route(
|
||||||
"/api/tokens/{uid}",
|
"/api/tokens/{uid}",
|
||||||
web::put().to(api_tokens_controller::update),
|
web::patch().to(api_tokens_controller::update),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/tokens/{uid}",
|
"/api/tokens/{uid}",
|
||||||
web::delete().to(api_tokens_controller::delete),
|
web::delete().to(api_tokens_controller::delete),
|
||||||
)*/
|
)
|
||||||
// Static assets
|
// Static assets
|
||||||
.route("/", web::get().to(static_controller::root_index))
|
.route("/", web::get().to(static_controller::root_index))
|
||||||
.route(
|
.route(
|
||||||
|
Loading…
Reference in New Issue
Block a user