2 Commits

Author SHA1 Message Date
d5eee04d7a Can block token unauthorized to access a specific route
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is passing
2024-04-10 21:22:15 +02:00
579e54f7d3 Update last activity of token 2024-04-10 21:10:00 +02:00
2 changed files with 42 additions and 15 deletions

View File

@@ -19,7 +19,7 @@ impl TokenID {
} }
} }
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct TokenRight { pub struct TokenRight {
verb: TokenVerb, verb: TokenVerb,
path: String, path: String,
@@ -28,6 +28,24 @@ pub struct TokenRight {
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct TokenRights(Vec<TokenRight>); pub struct TokenRights(Vec<TokenRight>);
impl TokenRights {
pub fn check_error(&self) -> Option<&'static str> {
for r in &self.0 {
if !r.path.starts_with("/api/") {
return Some("All API rights shall start with /api/");
}
}
None
}
pub fn contains(&self, verb: TokenVerb, path: &str) -> bool {
self.0.contains(&TokenRight {
verb,
path: path.to_string(),
})
}
}
#[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,
@@ -121,17 +139,6 @@ pub struct NewToken {
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.path.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> {
@@ -210,6 +217,14 @@ pub async fn update_rights(id: TokenID, rights: TokenRights) -> anyhow::Result<(
Ok(()) Ok(())
} }
/// Set last_used value of token
pub async fn refresh_last_used(id: TokenID) -> anyhow::Result<()> {
let mut token = get_single(id).await?;
token.last_used = time();
token.save()?;
Ok(())
}
/// Delete an API token /// Delete an API token
pub async fn delete(id: TokenID) -> anyhow::Result<()> { pub async fn delete(id: TokenID) -> anyhow::Result<()> {
let path = AppConfig::get().api_token_definition_path(id); let path = AppConfig::get().api_token_definition_path(id);

View File

@@ -4,7 +4,7 @@ use crate::api_tokens;
use crate::utils::jwt_utils; use crate::utils::jwt_utils;
use crate::utils::time_utils::time; use crate::utils::time_utils::time;
use actix_web::dev::Payload; use actix_web::dev::Payload;
use actix_web::error::ErrorBadRequest; use actix_web::error::{ErrorBadRequest, ErrorUnauthorized};
use actix_web::{Error, FromRequest, HttpRequest}; use actix_web::{Error, FromRequest, HttpRequest};
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
@@ -108,13 +108,25 @@ impl FromRequest for ApiAuthExtractor {
return Err(ErrorBadRequest("JWT method mismatch!")); return Err(ErrorBadRequest("JWT method mismatch!"));
} }
// TODO : check if route is authorized with token if !token.rights.contains(claims.verb, req.path()) {
log::error!(
"Attempt to use a token for an unauthorized route! (token_id={})",
token.id.0
);
return Err(ErrorUnauthorized(
"Token cannot be used to query this route!",
));
}
// TODO : check for ip restriction // TODO : check for ip restriction
// TODO : manually validate all checks // TODO : manually validate all checks
if token.should_update_last_activity() { if token.should_update_last_activity() {
// TODO : update last activity if let Err(e) = api_tokens::refresh_last_used(token.id).await {
log::error!("Could not update token last activity! {e}");
return Err(ErrorBadRequest("Couldn't refresh token last activity!"));
}
} }
Ok(ApiAuthExtractor { token, claims }) Ok(ApiAuthExtractor { token, claims })