146 lines
3.9 KiB
Rust
146 lines
3.9 KiB
Rust
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<String>,
|
|
}
|
|
|
|
#[async_trait]
|
|
impl<S> FromRequestParts<S> for FoxRequestData
|
|
where
|
|
S: Send + Sync,
|
|
{
|
|
type Rejection = ApiError;
|
|
|
|
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
|
let signature = FoxSignatureData::from_request_parts(parts, state).await?;
|
|
let state: Extension<Arc<AppState>> = 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<Utc>,
|
|
pub host: String,
|
|
pub request_path: String,
|
|
pub content_length: Option<usize>,
|
|
pub user_id: Option<String>,
|
|
}
|
|
|
|
#[async_trait]
|
|
impl<S> FromRequestParts<S> for FoxSignatureData
|
|
where
|
|
S: Send + Sync,
|
|
{
|
|
type Rejection = ApiError;
|
|
|
|
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
|
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::<usize>()?)
|
|
} 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,
|
|
})
|
|
}
|
|
}
|