use axum::extract::{FromRequest, Multipart, Request};
use axum::response::{IntoResponse, Response};
use crate::error::csrf_rejected;
use crate::session::{self, SessionHandle};
pub struct VerifiedUpload {
inner: Multipart,
}
impl VerifiedUpload {
pub async fn next_field(
&mut self,
) -> Result<Option<axum::extract::multipart::Field<'_>>, axum::extract::multipart::MultipartError>
{
self.inner.next_field().await
}
}
impl<S> FromRequest<S> for VerifiedUpload
where
S: Send + Sync,
{
type Rejection = Response;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let expected = req
.extensions()
.get::<SessionHandle>()
.map(|h| h.csrf_token())
.unwrap_or_default();
let pending = req.extensions().get::<session::CsrfPending>().cloned();
let mut inner = Multipart::from_request(req, state)
.await
.map_err(|e| e.into_response())?;
let Some(pending) = pending else {
return Ok(Self { inner });
};
let submitted = match inner.next_field().await {
Ok(Some(field)) if field.name() == Some(session::CSRF_FIELD) => field.text().await.ok(),
_ => None,
};
if !session::token_matches(&expected, submitted.as_deref()) {
tracing::warn!("upload rejected: request token missing or wrong");
return Err(csrf_rejected());
}
pending.satisfy();
Ok(Self { inner })
}
}