Include payload in integrity controls
This commit is contained in:
		@@ -3,25 +3,34 @@ use crate::utils::curr_time;
 | 
			
		||||
use actix_remote_ip::RemoteIP;
 | 
			
		||||
use actix_web::dev::Payload;
 | 
			
		||||
use actix_web::{FromRequest, HttpRequest};
 | 
			
		||||
use bytes::Bytes;
 | 
			
		||||
use jwt_simple::common::VerificationOptions;
 | 
			
		||||
use jwt_simple::prelude::{Duration, HS256Key, MACLike};
 | 
			
		||||
use sha2::{Digest, Sha256};
 | 
			
		||||
use std::net::IpAddr;
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
pub struct APIClientAuth {
 | 
			
		||||
    pub user: UserConfig,
 | 
			
		||||
    client: APIClient,
 | 
			
		||||
    payload: Option<Vec<u8>>,
 | 
			
		||||
    pub payload: Option<Vec<u8>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub struct TokenClaims {
 | 
			
		||||
    #[serde(rename = "met")]
 | 
			
		||||
    pub method: String,
 | 
			
		||||
    pub uri: String,
 | 
			
		||||
    #[serde(rename = "pay", skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub payload_sha256: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl APIClientAuth {
 | 
			
		||||
    async fn extract_auth(req: &HttpRequest, remote_ip: IpAddr) -> Result<Self, actix_web::Error> {
 | 
			
		||||
    async fn extract_auth(
 | 
			
		||||
        req: &HttpRequest,
 | 
			
		||||
        remote_ip: IpAddr,
 | 
			
		||||
        payload_bytes: Option<Bytes>,
 | 
			
		||||
    ) -> Result<Self, actix_web::Error> {
 | 
			
		||||
        let Some(token) = req.headers().get("x-client-auth") else {
 | 
			
		||||
            return Err(actix_web::error::ErrorBadRequest(
 | 
			
		||||
                "Missing authentication header!",
 | 
			
		||||
@@ -129,7 +138,27 @@ impl APIClientAuth {
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO : handle payload
 | 
			
		||||
        let payload = match (payload_bytes, claims.custom.payload_sha256) {
 | 
			
		||||
            (None, _) => None,
 | 
			
		||||
            (Some(_), None) => {
 | 
			
		||||
                return Err(actix_web::error::ErrorBadRequest(
 | 
			
		||||
                    "A payload digest must be included in the JWT when the request has a payload!",
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
            (Some(payload), Some(provided_digest)) => {
 | 
			
		||||
                let computed_digest = base16ct::lower::encode_string(&Sha256::digest(&payload));
 | 
			
		||||
                if computed_digest != provided_digest {
 | 
			
		||||
                    log::error!(
 | 
			
		||||
                        "Expected digest {provided_digest} but computed {computed_digest}!"
 | 
			
		||||
                    );
 | 
			
		||||
                    return Err(actix_web::error::ErrorBadRequest(
 | 
			
		||||
                        "Computed digest is different from the one provided in the JWT!",
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Some(payload.to_vec())
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Update last use (if needed)
 | 
			
		||||
        if client.need_update_last_used() {
 | 
			
		||||
@@ -145,7 +174,7 @@ impl APIClientAuth {
 | 
			
		||||
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            client: client.clone(),
 | 
			
		||||
            payload: None,
 | 
			
		||||
            payload,
 | 
			
		||||
            user,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
@@ -163,6 +192,24 @@ impl FromRequest for APIClientAuth {
 | 
			
		||||
            Err(e) => return Box::pin(async { Err(e) }),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Box::pin(async move { Self::extract_auth(&req, remote_ip.0).await })
 | 
			
		||||
        let mut payload = payload.take();
 | 
			
		||||
 | 
			
		||||
        Box::pin(async move {
 | 
			
		||||
            let payload_bytes = match Bytes::from_request(&req, &mut payload).await {
 | 
			
		||||
                Ok(b) => {
 | 
			
		||||
                    if b.is_empty() {
 | 
			
		||||
                        None
 | 
			
		||||
                    } else {
 | 
			
		||||
                        Some(b)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Err(e) => {
 | 
			
		||||
                    log::error!("Failed to extract request payload! {e}");
 | 
			
		||||
                    None
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            Self::extract_auth(&req, remote_ip.0, payload_bytes).await
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user