1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use std::fmt::{self, Debug, Formatter};
use std::sync::Arc;
use http::{Method, Uri};
use tower_layer::Layer;
use super::service::Csrf;
use super::url::UriExt;
use super::{BypassFn, ConfigError, DebugFn, DefaultResponseForProtectionError, Origins};
/// Layer that applies the [`Csrf`] middleware.
///
/// See the [module docs](crate::csrf) for an example.
#[derive(Clone)]
#[must_use]
pub struct CsrfLayer<T = DefaultResponseForProtectionError> {
insecure_bypass: Option<Arc<BypassFn>>,
rejection_response: T,
trusted_origins: Origins,
}
impl Default for CsrfLayer {
fn default() -> Self {
Self {
insecure_bypass: None,
rejection_response: DefaultResponseForProtectionError,
trusted_origins: Origins::default(),
}
}
}
impl<T> Debug for CsrfLayer<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("CsrfLayer")
.field(
"insecure_bypass",
&self.insecure_bypass.as_ref().map(|_| DebugFn),
)
.field("trusted_origins", &self.trusted_origins)
.field("rejection_response", &DebugFn)
.finish()
}
}
impl CsrfLayer {
/// Creates a new `CsrfLayer` with no trusted origins, no bypass, and the
/// default rejection response.
pub fn new() -> Self {
Self::default()
}
}
impl<T> CsrfLayer<T> {
/// Adds a trusted origin that allows all requests whose `Origin` header
/// matches the given value.
///
/// The value is matched **byte-for-byte** against the request's `Origin`
/// header — there is no normalization (this mirrors the Go reference). It
/// must therefore be written exactly as a browser sends it:
///
/// - form `scheme://host[:port]`, where `scheme` is `http` or `https`;
/// - the host lowercased (browsers lowercase it; IDN hosts must be given in
/// punycode, e.g. `xn--exmple-cua.com`);
/// - **default ports omitted** — browsers drop `:80`/`:443`, so an explicit
/// default port (e.g. `https://example.com:443`) will never match;
/// - **no trailing slash**, path, query, or fragment.
///
/// Inputs that can't represent a browser `Origin` are rejected with a
/// [`ConfigError`]; inputs that parse but aren't in the canonical browser
/// form above are accepted but will silently never match.
///
/// ```
/// # use tower_http::csrf::CsrfLayer;
/// // Matches `Origin: https://example.com`:
/// let layer = CsrfLayer::new().add_trusted_origin("https://example.com")?;
///
/// // Accepted, but never matches a browser Origin (explicit default port):
/// let layer = CsrfLayer::new().add_trusted_origin("https://example.com:443")?;
/// # Ok::<_, tower_http::csrf::ConfigError>(())
/// ```
pub fn add_trusted_origin<S: AsRef<str>>(mut self, origin: S) -> Result<Self, ConfigError> {
let origin = origin.as_ref();
// validate the form; the origin is stored and matched verbatim.
Uri::parse_origin(origin)?;
#[cfg(feature = "tracing")]
tracing::debug!(origin = %origin, "added trusted origin");
self.trusted_origins.insert(origin.to_owned());
Ok(self)
}
/// Adds a bypass predicate that returns `true` for requests which should
/// skip CSRF protection.
///
/// This is an escape hatch for endpoints that legitimately need to accept
/// cross-origin POSTs (e.g. webhook receivers). Bypassed endpoints must
/// have their own protection (signed payloads, authentication tokens,
/// etc.) — otherwise they are CSRF-vulnerable.
pub fn with_insecure_bypass<F>(mut self, predicate: F) -> Self
where
F: Fn(&Method, &Uri) -> bool + Send + Sync + 'static,
{
#[cfg(feature = "tracing")]
tracing::debug!("added insecure bypass");
self.insecure_bypass = Some(Arc::new(predicate));
self
}
/// Replaces the response builder used when a request is rejected.
///
/// Accepts any type that implements [`ResponseForProtectionError`](super::ResponseForProtectionError),
/// including a `FnMut(ProtectionError) -> Response<B> + Clone` closure.
/// The default builder returns a `403 Forbidden` with an empty body.
/// Regardless of the builder, [`Csrf`](super::Csrf) attaches the
/// [`ProtectionError`](super::ProtectionError) to the response's extensions,
/// so a custom builder need not re-attach it.
pub fn with_rejection_response<R>(self, rejection_response: R) -> CsrfLayer<R>
where
R: Clone,
{
CsrfLayer {
insecure_bypass: self.insecure_bypass,
trusted_origins: self.trusted_origins,
rejection_response,
}
}
}
impl<S, T> Layer<S> for CsrfLayer<T>
where
T: Clone,
{
type Service = Csrf<S, T>;
fn layer(&self, inner: S) -> Self::Service {
Csrf::new(
inner,
self.insecure_bypass.clone(),
self.rejection_response.clone(),
self.trusted_origins.clone(),
)
}
}