use axum::body::Body;
use axum::extract::Request;
use axum::http::header::SET_COOKIE;
use axum::http::HeaderValue;
use axum::middleware::Next;
use axum::response::Response;
use super::session::CURRENT_CSRF_TOKEN;
pub(crate) async fn csrf_context(request: Request<Body>, next: Next) -> Response {
if super::session::current_csrf_token().is_some() {
return next.run(request).await;
}
let (token, set_cookie) =
crate::forms::csrf::ensure_token_under_layer(request.headers(), request.extensions());
let mut response = CURRENT_CSRF_TOKEN.scope(token, next.run(request)).await;
let cacheable = crate::forms::csrf::is_publicly_cacheable(response.headers());
if let Some(cookie) = set_cookie.filter(|_| !cacheable) {
if let Ok(v) = HeaderValue::from_str(&cookie) {
response.headers_mut().append(SET_COOKIE, v);
}
}
response
}
#[cfg(test)]
mod tests {
use axum::routing::get;
use axum::Router;
use tower::ServiceExt as _;
use crate::forms::csrf::{with_config, CsrfConfig};
#[tokio::test]
async fn the_token_cookie_follows_the_outer_layer() {
let app = Router::new()
.route(
"/",
get(|| async { super::super::session::current_csrf_token().unwrap_or_default() }),
)
.layer(axum::middleware::from_fn(super::csrf_context))
.layer(with_config(CsrfConfig {
cookie_name: "outer_csrf".into(),
..CsrfConfig::default()
}));
let resp = app
.oneshot(
axum::http::Request::builder()
.uri("/")
.body(super::Body::empty())
.unwrap(),
)
.await
.unwrap();
let cookies: Vec<String> = resp
.headers()
.get_all(super::SET_COOKIE)
.iter()
.map(|c| c.to_str().unwrap().to_owned())
.collect();
let token = axum::body::to_bytes(resp.into_body(), 1 << 20)
.await
.unwrap();
let token = String::from_utf8_lossy(&token);
assert_eq!(cookies.len(), 1, "{cookies:?}");
assert!(
cookies[0].starts_with(&format!("outer_csrf={token};")),
"{cookies:?}"
);
assert!(cookies[0].contains("; Secure"), "{cookies:?}");
}
}