use crate::api_tokens::{Token, TokenID, TokenVerb}; use crate::api_tokens; use crate::utils::time_utils::time; use actix_remote_ip::RemoteIP; use actix_web::dev::Payload; use actix_web::error::{ErrorBadRequest, ErrorUnauthorized}; 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(); let remote_ip = match RemoteIP::from_request(&req, payload).into_inner() { Ok(ip) => ip, Err(e) => return Box::pin(async { Err(e) }), }; 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 token .pub_key .as_ref() .expect("All tokens shall have public key!") .validate_jwt::(&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!")); } 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!", )); } if let Some(ip) = token.ip_restriction { if !ip.contains(remote_ip.0) { log::error!( "Attempt to use a token for an unauthorized IP! {remote_ip:?} token_id={}", token.id.0 ); return Err(ErrorUnauthorized("Token cannot be used from this IP!")); } } if token.should_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 }) }) } }