pub struct CsrfPlugin { /* private fields */ }Expand description
CSRF protection plugin.
Validates the Origin or Referer header on state-changing requests
(POST, PATCH, DELETE, PUT) against a list of allowed origins. This is
complementary to CORS: CORS controls which origins can read responses,
while CSRF protection ensures that state-changing requests originate from
trusted sources.
Implementations§
Source§impl CsrfPlugin
impl CsrfPlugin
Sourcepub fn new(allowed_origins: Vec<String>) -> Self
pub fn new(allowed_origins: Vec<String>) -> Self
Create a CSRF plugin with explicit allowed origins.
Sourcepub fn with_localhost(port: u16) -> Self
pub fn with_localhost(port: u16) -> Self
Convenience constructor for local development. Allows both localhost
and 127.0.0.1 on the given port.
Sourcepub fn check(
&self,
method: &str,
origin: Option<&str>,
referer: Option<&str>,
) -> Result<(), PluginError>
pub fn check( &self, method: &str, origin: Option<&str>, referer: Option<&str>, ) -> Result<(), PluginError>
Validate an incoming request.
For safe methods this always succeeds. For state-changing
methods, the Origin header is checked first; if absent the
origin is derived from the Referer header.
CSRF defense model. Modern browsers always send Origin
on cross-origin state-changing requests — a malicious page
can’t suppress it. Browsers also send Origin on same-site
POSTs in current spec. So a request with NEITHER Origin nor
Referer is by definition not a browser request — it’s a
server-to-server caller (Next.js SSR forwarding a session
cookie, a curl script with --cookie, an internal admin
tool, etc.). Those callers attach the cookie explicitly via
the Cookie: header rather than relying on browser
auto-attachment, so the cross-site forgery attack surface
the CSRF gate exists to protect against doesn’t apply.
Without this allowance every Next.js dashboard route that
calls a Pylon mutation server-side (pylon.json("/api/fn/X", {method: "POST"})) would 403 — Next.js SSR has no Origin to
send. We learned this the hard way via the dashboard
“Members” page returning empty after release 0.3.11.
When a header IS present it must match the allowlist; an attacker can never inject one, so its presence is always trustworthy.
Trait Implementations§
Source§impl Plugin for CsrfPlugin
impl Plugin for CsrfPlugin
Source§fn on_request(
&self,
_method: &str,
_path: &str,
_auth: &AuthContext,
) -> Result<(), PluginError>
fn on_request( &self, _method: &str, _path: &str, _auth: &AuthContext, ) -> Result<(), PluginError>
Source§fn on_init(&self, _ctx: &PluginContext)
fn on_init(&self, _ctx: &PluginContext)
Source§fn routes(&self) -> Vec<PluginRoute>
fn routes(&self) -> Vec<PluginRoute>
Source§fn before_insert(
&self,
_entity: &str,
_data: &mut Value,
_auth: &AuthContext,
) -> Result<(), PluginError>
fn before_insert( &self, _entity: &str, _data: &mut Value, _auth: &AuthContext, ) -> Result<(), PluginError>
Source§fn after_insert(
&self,
_entity: &str,
_id: &str,
_data: &Value,
_auth: &AuthContext,
)
fn after_insert( &self, _entity: &str, _id: &str, _data: &Value, _auth: &AuthContext, )
Source§fn before_update(
&self,
_entity: &str,
_id: &str,
_data: &mut Value,
_auth: &AuthContext,
) -> Result<(), PluginError>
fn before_update( &self, _entity: &str, _id: &str, _data: &mut Value, _auth: &AuthContext, ) -> Result<(), PluginError>
Source§fn after_update(
&self,
_entity: &str,
_id: &str,
_data: &Value,
_auth: &AuthContext,
)
fn after_update( &self, _entity: &str, _id: &str, _data: &Value, _auth: &AuthContext, )
Source§fn before_delete(
&self,
_entity: &str,
_id: &str,
_auth: &AuthContext,
) -> Result<(), PluginError>
fn before_delete( &self, _entity: &str, _id: &str, _auth: &AuthContext, ) -> Result<(), PluginError>
Source§fn after_delete(&self, _entity: &str, _id: &str, _auth: &AuthContext)
fn after_delete(&self, _entity: &str, _id: &str, _auth: &AuthContext)
Source§fn on_request_with_meta(
&self,
method: &str,
path: &str,
auth: &AuthContext,
_meta: &RequestMeta<'_>,
) -> Result<(), PluginError>
fn on_request_with_meta( &self, method: &str, path: &str, auth: &AuthContext, _meta: &RequestMeta<'_>, ) -> Result<(), PluginError>
on_request] that also receives per-request
metadata (peer IP today; more fields may be added later). The
default implementation delegates to on_request so existing
plugins keep working without changes. Plugins that care about
IP — notably rate limiting — override this hook.