use crate::api_tokens::{Token, TokenID, TokenVerb}; use crate::api_tokens; use crate::utils::jwt_utils; use crate::utils::time_utils::time; use actix_web::dev::Payload; use actix_web::error::ErrorBadRequest; use actix_web::{Error, FromRequest, HttpRequest}; use std::future::Future; use std::pin::Pin; #[derive(serde::Serialize, serde::Deserialize, Debug)] pub struct TokenClaims { pub sub: String, pub iat: usize, pub exp: usize, pub verb: TokenVerb, pub path: String, pub nonce: String, } pub struct ApiAuthExtractor { pub token: Token, pub claims: TokenClaims, } impl FromRequest for ApiAuthExtractor { type Error = Error; type Future = Pin>>>; fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future { let req = req.clone(); Box::pin(async move { let (token_id, token_jwt) = match ( req.headers().get("x-token-id"), req.headers().get("x-token-content"), ) { (Some(id), Some(jwt)) => ( id.to_str().unwrap_or("").to_string(), jwt.to_str().unwrap_or("").to_string(), ), (_, _) => { return Err(ErrorBadRequest("API auth headers were not all specified!")); } }; let token_id = match TokenID::parse(&token_id) { Ok(t) => t, Err(e) => { log::error!("Failed to parse token id! {e}"); return Err(ErrorBadRequest("Unable to validate token ID!")); } }; let token = match api_tokens::get_single(token_id).await { Ok(t) => t, Err(e) => { log::error!("Failed to retrieve token: {e}"); return Err(ErrorBadRequest("Unable to validate token!")); } }; if token.is_expired() { log::error!("Token has expired (not been used for too long)!"); return Err(ErrorBadRequest("Unable to validate token!")); } let claims = match jwt_utils::validate_jwt::(&token.pub_key, &token_jwt) { Ok(c) => c, Err(e) => { log::error!("Failed to validate JWT: {e}"); return Err(ErrorBadRequest("Unable to validate token!")); } }; if claims.sub != token.id.0.to_string() { log::error!("JWT sub mismatch (should equal to token id)!"); return Err(ErrorBadRequest( "JWT sub mismatch (should equal to token id)!", )); } if time() + 60 * 15 < claims.iat as u64 { log::error!("iat is in the future!"); return Err(ErrorBadRequest("iat is in the future!")); } if claims.exp < claims.iat { log::error!("exp shall not be smaller than iat!"); return Err(ErrorBadRequest("exp shall not be smaller than iat!")); } if claims.exp - claims.iat > 1800 { log::error!("JWT shall not be valid more than 30 minutes!"); return Err(ErrorBadRequest( "JWT shall not be valid more than 30 minutes!", )); } if claims.path != req.path() { log::error!("JWT path mismatch!"); return Err(ErrorBadRequest("JWT path mismatch!")); } if claims.verb.as_method() != req.method() { log::error!("JWT method mismatch!"); return Err(ErrorBadRequest("JWT method mismatch!")); } // TODO : check if route is authorized with token // TODO : check for ip restriction // TODO : manually validate all checks if token.should_update_last_activity() { // TODO : update last activity } Ok(ApiAuthExtractor { token, claims }) }) } }