use crate::user::{APIClient, APIClientID, UserConfig, UserID}; use actix_web::dev::Payload; use actix_web::{FromRequest, HttpRequest}; use jwt_simple::common::VerificationOptions; use jwt_simple::prelude::{Duration, HS256Key, MACLike}; use std::str::FromStr; pub struct APIClientAuth { pub user: UserConfig, client: APIClient, payload: Option>, } #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct TokenClaims { pub method: String, pub uri: String, } impl APIClientAuth { async fn extract_auth(req: &HttpRequest) -> Result { let Some(token) = req.headers().get("x-client-auth") else { return Err(actix_web::error::ErrorBadRequest( "Missing authentication header!", )); }; let Ok(jwt_token) = token.to_str() else { return Err(actix_web::error::ErrorBadRequest( "Failed to decode token as string!", )); }; let metadata = match jwt_simple::token::Token::decode_metadata(jwt_token) { Ok(m) => m, Err(e) => { log::error!("Failed to decode JWT header metadata! {e}"); return Err(actix_web::error::ErrorBadRequest( "Failed to decode JWT header metadata!", )); } }; let Some(kid) = metadata.key_id() else { return Err(actix_web::error::ErrorBadRequest( "Missing key id in request!", )); }; let Some((user_id, client_id)) = kid.split_once("#") else { return Err(actix_web::error::ErrorBadRequest( "Invalid key format (missing part)!", )); }; let (Ok(user_id), Ok(client_id)) = (urlencoding::decode(user_id), urlencoding::decode(client_id)) else { return Err(actix_web::error::ErrorBadRequest( "Invalid key format (decoding failed)!", )); }; // Fetch user const USER_NOT_FOUND_ERROR: &str = "User not found!"; let user = match UserConfig::load(&UserID(user_id.to_string()), false).await { Ok(u) => u, Err(e) => { log::error!("Failed to get user information! {e}"); return Err(actix_web::error::ErrorForbidden(USER_NOT_FOUND_ERROR)); } }; // Find client let Ok(client_id) = APIClientID::from_str(&client_id) else { return Err(actix_web::error::ErrorBadRequest("Invalid token format!")); }; let Some(client) = user.find_client_by_id(&client_id) else { log::error!("Client not found for user!"); return Err(actix_web::error::ErrorForbidden(USER_NOT_FOUND_ERROR)); }; // Decode JWT let key = HS256Key::from_bytes(client.secret.as_bytes()); let mut verif = VerificationOptions::default(); verif.max_validity = Some(Duration::from_mins(15)); let claims = match key.verify_token::(jwt_token, Some(verif)) { Ok(t) => t, Err(e) => { log::error!("JWT validation failed! {e}"); return Err(actix_web::error::ErrorForbidden("JWT validation failed!")); } }; // Check for nonce if claims.nonce.is_none() { return Err(actix_web::error::ErrorBadRequest( "A nonce is required in JWT!", )); } // Check URI & verb if claims.custom.uri != req.uri().to_string() { return Err(actix_web::error::ErrorBadRequest("URI mismatch!")); } if claims.custom.method != req.method().to_string() { return Err(actix_web::error::ErrorBadRequest("Method mismatch!")); } // TODO : handle payload // TODO : check read only access // TODO : update last use (if required) // TODO : check for IP restriction Ok(Self { client: client.clone(), payload: None, user, }) } } impl FromRequest for APIClientAuth { type Error = actix_web::Error; type Future = futures_util::future::LocalBoxFuture<'static, Result>; fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { let req = req.clone(); Box::pin(async move { Self::extract_auth(&req).await }) } }