mod rest; pub use rest::post_rest_event; use std::sync::Arc; use axum::{ async_trait, extract::FromRequestParts, http::{ header::{CONTENT_LENGTH, DATE, HOST}, request::Parts, }, Extension, }; use chrono::{DateTime, Utc}; use foxchat::{ fed::{SERVER_HEADER, SIGNATURE_HEADER, USER_HEADER}, http::ApiError, signature::{parse_date, verify_signature}, FoxError, }; use tracing::error; use crate::{app_state::AppState, model::identity_instance::IdentityInstance}; /// A parsed and validated federation signature. pub struct FoxRequestData { pub instance: IdentityInstance, pub user_id: Option, } #[async_trait] impl FromRequestParts for FoxRequestData where S: Send + Sync, { type Rejection = ApiError; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { let signature = FoxSignatureData::from_request_parts(parts, state).await?; let state: Extension> = Extension::from_request_parts(parts, state) .await .expect("AppState was not added as an extension"); let instance = IdentityInstance::get(state.0.clone(), &signature.domain).await?; let public_key = instance.parse_public_key()?; if state.config.domain.clone() != signature.host { return Err(FoxError::InvalidHeader.into()); } if let Err(e) = verify_signature( &public_key, signature.signature, signature.date, &signature.host, &signature.request_path, signature.content_length, signature.user_id.clone(), ) { error!( "Verifying signature from request for {} from {}: {}", parts.uri.path(), signature.domain, e ); return Err(FoxError::InvalidSignature.into()); } Ok(FoxRequestData { instance, user_id: signature.user_id, }) } } // An unvalidated federation signature. Use with caution, you should almost always use `FoxRequestData` instead. pub struct FoxSignatureData { pub domain: String, pub signature: String, pub date: DateTime, pub host: String, pub request_path: String, pub content_length: Option, pub user_id: Option, } #[async_trait] impl FromRequestParts for FoxSignatureData where S: Send + Sync, { type Rejection = ApiError; async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { let domain = parts .headers .get(SERVER_HEADER) .ok_or(FoxError::InvalidHeader)? .to_str()? .to_string(); let date = parse_date( parts .headers .get(DATE) .ok_or(FoxError::InvalidHeader)? .to_str()?, )?; let signature = parts .headers .get(SIGNATURE_HEADER) .ok_or(FoxError::MissingSignature)? .to_str()? .to_string(); let host = parts .headers .get(HOST) .ok_or(FoxError::InvalidHeader)? .to_str()? .to_string(); let content_length = if let Some(raw_length) = parts.headers.get(CONTENT_LENGTH) { Some(raw_length.to_str()?.parse::()?) } else { None }; let user_id = if let Some(raw_id) = parts.headers.get(USER_HEADER) { Some(raw_id.to_str()?.to_string()) } else { None }; Ok(FoxSignatureData { domain, date, signature, host, request_path: parts.uri.path().to_string(), content_length, user_id, }) } }