Skip to main content

rama_http/layer/csrf/
layer.rs

1use std::fmt::{self, Debug, Formatter};
2use std::sync::Arc;
3
4use rama_core::Layer;
5
6use super::origin::{Origins, parse_trusted_origin};
7use super::service::Csrf;
8use super::{BypassFn, ConfigError, DebugFn, DefaultResponseForProtectionError};
9use crate::Method;
10use rama_net::uri::Uri;
11
12/// Layer that applies the [`Csrf`] middleware.
13///
14/// See the [module docs](crate::layer::csrf) for an example.
15#[derive(Clone)]
16#[must_use]
17pub struct CsrfLayer<T = DefaultResponseForProtectionError> {
18    insecure_bypass: Option<Arc<BypassFn>>,
19    rejection_response: T,
20    trusted_origins: Origins,
21}
22
23impl Default for CsrfLayer {
24    fn default() -> Self {
25        Self {
26            insecure_bypass: None,
27            rejection_response: DefaultResponseForProtectionError,
28            trusted_origins: Origins::default(),
29        }
30    }
31}
32
33impl<T> Debug for CsrfLayer<T> {
34    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
35        f.debug_struct("CsrfLayer")
36            .field(
37                "insecure_bypass",
38                &self.insecure_bypass.as_ref().map(|_| DebugFn),
39            )
40            .field("trusted_origins", &self.trusted_origins)
41            .field("rejection_response", &DebugFn)
42            .finish()
43    }
44}
45
46impl CsrfLayer {
47    /// Creates a new `CsrfLayer` with no trusted origins, no bypass, and the default rejection
48    /// response.
49    pub fn new() -> Self {
50        Self::default()
51    }
52}
53
54impl<T> CsrfLayer<T> {
55    /// Adds a trusted origin that allows all requests whose `Origin` matches the given value.
56    ///
57    /// The value is compared **structurally** (via [`rama_net::uri::Uri`]) against the request's
58    /// `Origin`: the host is matched case-insensitively and a default port compares equal whether
59    /// written explicitly or omitted. The input must be a bare origin of the form
60    /// `scheme://host[:port]` with an `http`/`https` scheme and no userinfo, path, query, or
61    /// fragment; anything else is rejected with a [`ConfigError`].
62    pub fn add_trusted_origin<S: AsRef<str>>(mut self, origin: S) -> Result<Self, ConfigError> {
63        let origin = parse_trusted_origin(origin.as_ref())?;
64        self.trusted_origins.insert(origin);
65        Ok(self)
66    }
67
68    /// Adds a bypass predicate that returns `true` for requests which should skip CSRF protection.
69    ///
70    /// This is an escape hatch for endpoints that legitimately need to accept cross-origin POSTs
71    /// (e.g. webhook receivers). Bypassed endpoints must have their own protection (signed
72    /// payloads, authentication tokens, etc.) — otherwise they are CSRF-vulnerable.
73    pub fn with_insecure_bypass<F>(mut self, predicate: F) -> Self
74    where
75        F: Fn(&Method, &Uri) -> bool + Send + Sync + 'static,
76    {
77        self.insecure_bypass = Some(Arc::new(predicate));
78        self
79    }
80
81    /// Replaces the response builder used when a request is rejected.
82    ///
83    /// Accepts any type that implements
84    /// [`ResponseForProtectionError`](super::ResponseForProtectionError), including a
85    /// `Fn(ProtectionError) -> Response<B> + Clone + Send + Sync + 'static` closure. The default
86    /// builder returns a `403 Forbidden` with an empty body. Regardless of the builder,
87    /// [`Csrf`](super::Csrf) attaches the [`ProtectionError`](super::ProtectionError) to the
88    /// response's extensions, so a custom builder need not re-attach it.
89    pub fn with_rejection_response<R>(self, rejection_response: R) -> CsrfLayer<R>
90    where
91        R: Clone,
92    {
93        CsrfLayer {
94            insecure_bypass: self.insecure_bypass,
95            trusted_origins: self.trusted_origins,
96            rejection_response,
97        }
98    }
99}
100
101impl<S, T> Layer<S> for CsrfLayer<T>
102where
103    T: Clone,
104{
105    type Service = Csrf<S, T>;
106
107    fn layer(&self, inner: S) -> Self::Service {
108        Csrf::new(
109            inner,
110            self.insecure_bypass.clone(),
111            self.rejection_response.clone(),
112            self.trusted_origins.clone(),
113        )
114    }
115
116    fn into_layer(self, inner: S) -> Self::Service {
117        Csrf::new(
118            inner,
119            self.insecure_bypass,
120            self.rejection_response,
121            self.trusted_origins,
122        )
123    }
124}