aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2021-01-18 10:03:50 +0100
committerMartin Fischer <martin@push-f.com>2021-01-18 10:17:10 +0100
commit9b2d39933f3403245f97247166818bef609a0125 (patch)
treee5819cf6314e357b7b37e23ae277cd2fea165afc
parentc27c5b3109e2b4fd8cdbe312f4925edc238a25e9 (diff)
split Request wrapper into Parts & Bodyv0.2.0
Originally the into_ functions actually consumed the request but I changed that to make request information like URI and method still accessible after the request has been read. Not consuming the Request however allows e.g. into_form() to be called twice, which results in a panic since the body can only be read once. This commit splits the Request wrapper into two wrappers Parts & Body, allowing the borrow checker to guarantee that the body is only consumed once, while keeping the other request information accessible after the body has been consumed. Version bumped to 0.2.0.
-rw-r--r--.gitignore2
-rw-r--r--Cargo.toml2
-rw-r--r--README.md19
-rw-r--r--examples/csrf/Cargo.toml14
-rw-r--r--examples/csrf/src/main.rs59
-rw-r--r--src/lib.rs202
-rw-r--r--src/request.rs159
-rw-r--r--src/response.rs60
-rw-r--r--src/security.rs6
9 files changed, 310 insertions, 213 deletions
diff --git a/.gitignore b/.gitignore
index 436987b..a4cee7a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-/target
+target/
# See https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
diff --git a/Cargo.toml b/Cargo.toml
index f469a0f..f974ccc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "sputnik"
-version = "0.1.1"
+version = "0.2.0"
authors = ["Martin Fischer <martin@push-f.com>"]
license = "MIT"
description = "A lightweight layer on top of hyper to facilitate building web applications."
diff --git a/README.md b/README.md
index ec67b2e..04604fa 100644
--- a/README.md
+++ b/README.md
@@ -32,19 +32,19 @@ use hyper::service::{service_fn, make_service_fn};
use hyper::{Method, Server};
use serde::Deserialize;
use sputnik::security::CsrfToken;
-use sputnik::{Request, Response, Error};
+use sputnik::{Error, request::{Parts, Body}, response::Response};
-async fn route(req: &mut Request) -> Result<Response,Error> {
+async fn route(req: &mut Parts, body: Body) -> Result<Response,Error> {
match (req.method(), req.uri().path()) {
(&Method::GET, "/form") => get_form(req).await,
- (&Method::POST, "/form") => post_form(req).await,
+ (&Method::POST, "/form") => post_form(req, body).await,
_ => return Err(Error::not_found("page not found".to_owned()))
}
}
-async fn get_form(req: &mut Request) -> Result<Response, Error> {
+async fn get_form(req: &mut Parts) -> Result<Response, Error> {
let mut response = Response::new();
- let csrf_token = CsrfToken::from_request(req, &mut response);
+ let csrf_token = CsrfToken::from_parts(req, &mut response);
*response.body() = format!("<form method=post>
<input name=text>{}<button>Submit</button></form>", csrf_token.html_input()).into();
Ok(response)
@@ -53,17 +53,18 @@ async fn get_form(req: &mut Request) -> Result<Response, Error> {
#[derive(Deserialize)]
struct FormData {text: String}
-async fn post_form(req: &mut Request) -> Result<Response, Error> {
+async fn post_form(req: &mut Parts, body: Body) -> Result<Response, Error> {
let mut response = Response::new();
- let csrf_token = CsrfToken::from_request(req, &mut response);
- let msg: FormData = req.into_form_csrf(&csrf_token).await?;
+ let csrf_token = CsrfToken::from_parts(req, &mut response);
+ let msg: FormData = body.into_form_csrf(&csrf_token).await?;
*response.body() = format!("hello {}", msg.text).into();
Ok(response)
}
/// adapt between Hyper's types and Sputnik's convenience types
async fn service(req: hyper::Request<hyper::Body>) -> Result<hyper::Response<hyper::Body>, Infallible> {
- match route(&mut req.into()).await {
+ let (mut parts, body) = sputnik::request::adapt(req);
+ match route(&mut parts, body).await {
Ok(res) => Ok(res.into()),
Err(err) => Ok(err.response_builder().body(err.message.into()).unwrap())
// you can easily wrap or log errors here
diff --git a/examples/csrf/Cargo.toml b/examples/csrf/Cargo.toml
new file mode 100644
index 0000000..1f0066b
--- /dev/null
+++ b/examples/csrf/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "csrf"
+version = "0.1.0"
+authors = ["Martin Fischer <martin@push-f.com>"]
+edition = "2018"
+publish = false
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+hyper = "0.13"
+sputnik = {path = "../../"}
+serde = { version = "1.0", features = ["derive"] }
+tokio = { version = "0.2", features = ["full"] } \ No newline at end of file
diff --git a/examples/csrf/src/main.rs b/examples/csrf/src/main.rs
new file mode 100644
index 0000000..16b31a1
--- /dev/null
+++ b/examples/csrf/src/main.rs
@@ -0,0 +1,59 @@
+use std::convert::Infallible;
+use hyper::service::{service_fn, make_service_fn};
+use hyper::{Method, Server};
+use serde::Deserialize;
+use sputnik::security::CsrfToken;
+use sputnik::{Error, request::{Parts, Body}, response::Response};
+
+async fn route(req: &mut Parts, body: Body) -> Result<Response,Error> {
+ match (req.method(), req.uri().path()) {
+ (&Method::GET, "/form") => get_form(req).await,
+ (&Method::POST, "/form") => post_form(req, body).await,
+ _ => return Err(Error::not_found("page not found".to_owned()))
+ }
+}
+
+async fn get_form(req: &mut Parts) -> Result<Response, Error> {
+ let mut response = Response::new();
+ let csrf_token = CsrfToken::from_parts(req, &mut response);
+ *response.body() = format!("<form method=post>
+ <input name=text>{}<button>Submit</button></form>", csrf_token.html_input()).into();
+ Ok(response)
+}
+
+#[derive(Deserialize)]
+struct FormData {text: String}
+
+async fn post_form(req: &mut Parts, body: Body) -> Result<Response, Error> {
+ let mut response = Response::new();
+ let csrf_token = CsrfToken::from_parts(req, &mut response);
+ let msg: FormData = body.into_form_csrf(&csrf_token).await?;
+ *response.body() = format!("hello {}", msg.text).into();
+ Ok(response)
+}
+
+/// adapt between Hyper's types and Sputnik's convenience types
+async fn service(req: hyper::Request<hyper::Body>) -> Result<hyper::Response<hyper::Body>, Infallible> {
+ let (mut parts, body) = sputnik::request::adapt(req);
+ match route(&mut parts, body).await {
+ Ok(res) => Ok(res.into()),
+ Err(err) => Ok(err.response_builder().body(err.message.into()).unwrap())
+ // you can easily wrap or log errors here
+ }
+}
+
+#[tokio::main]
+async fn main() {
+ let service = make_service_fn(move |_| {
+ async move {
+ Ok::<_, hyper::Error>(service_fn(move |req| {
+ service(req)
+ }))
+ }
+ });
+
+ let addr = ([127, 0, 0, 1], 8000).into();
+ let server = Server::bind(&addr).serve(service);
+ println!("Listening on http://{}", addr);
+ server.await;
+} \ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index 9ea1c0f..9719823 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,207 +1,11 @@
//! A lightweight layer on top of [Hyper](https://hyper.rs/)
//! to facilitate building web applications.
-use std::collections::HashMap;
-
pub use error::Error;
pub use mime;
-use cookie::Cookie;
-use header::HeaderName;
-use mime::{APPLICATION_WWW_FORM_URLENCODED, Mime};
-use serde::{Deserialize, de::DeserializeOwned};
-use hyper::{Body, StatusCode, body::Bytes, header::{self, HeaderValue}, http::request::Parts};
-use time::{Duration, OffsetDateTime};
-
pub use httpdate;
-type HyperRequest = hyper::Request<Body>;
-type HyperResponse = hyper::Response<Body>;
-
pub mod security;
+pub mod request;
+pub mod response;
mod error;
-mod signed;
-
-/// Convenience wrapper around [`hyper::Request`].
-pub struct Request {
- body: Body,
- parts: Parts,
- cookies: Option<HashMap<String,Cookie<'static>>>,
-}
-
-impl From<HyperRequest> for Request {
- fn from(req: HyperRequest) -> Self {
- let (parts, body) = req.into_parts();
- Request{parts, body, cookies: None}
- }
-}
-
-impl Into<HyperResponse> for Response {
- fn into(self) -> HyperResponse {
- self.res
- }
-}
-
-fn enforce_content_type(req: &Parts, mime: Mime) -> Result<(),Error> {
- let received_type = req.headers.get(header::CONTENT_TYPE).ok_or(Error::bad_request(format!("expected content-type: {}", mime)))?;
- if *received_type != mime.to_string() {
- return Err(Error::bad_request(format!("expected content-type: {}", mime)))
- }
- Ok(())
-}
-
-#[derive(Deserialize)]
-struct CsrfData {
- csrf: String,
-}
-
-impl Request {
- pub fn cookies(&mut self) -> &HashMap<String,Cookie> {
- if let Some(ref cookies) = self.cookies {
- return cookies
- }
- let mut cookies = HashMap::new();
- for header in self.parts.headers.get_all(header::COOKIE) {
- let raw_str = match std::str::from_utf8(header.as_bytes()) {
- Ok(string) => string,
- Err(_) => continue
- };
-
- for cookie_str in raw_str.split(';').map(|s| s.trim()) {
- if let Ok(cookie) = Cookie::parse_encoded(cookie_str) {
- cookies.insert(cookie.name().to_string(), cookie.into_owned());
- }
- }
- }
- self.cookies = Some(cookies);
- &self.cookies.as_ref().unwrap()
- }
-
- pub fn method(&self) -> &hyper::Method {
- &self.parts.method
- }
-
- pub fn uri(&self) -> &hyper::Uri {
- &self.parts.uri
- }
-
- /// Parses a `application/x-www-form-urlencoded` request body into a given struct.
- ///
- /// This does make you vulnerable to CSRF so you normally want to use
- /// [`parse_form_csrf()`] instead.
- ///
- /// # Example
- ///
- /// ```
- /// use hyper::{Response, Body};
- /// use sputnik::{Request, Error};
- /// use serde::Deserialize;
- ///
- /// #[derive(Deserialize)]
- /// struct Message {text: String, year: i64}
- ///
- /// async fn greet(req: &mut Request) -> Result<Response<Body>, Error> {
- /// let msg: Message = req.into_form().await?;
- /// Ok(Response::new(format!("hello {}", msg.text).into()))
- /// }
- /// ```
- pub async fn into_form<T: DeserializeOwned>(&mut self) -> Result<T,Error> {
- enforce_content_type(&self.parts, APPLICATION_WWW_FORM_URLENCODED)?;
- let full_body = self.into_body().await?;
- serde_urlencoded::from_bytes::<T>(&full_body).map_err(|e|Error::bad_request(e.to_string()))
- }
-
- /// Parses a `application/x-www-form-urlencoded` request body into a given struct.
- /// Protects from CSRF by checking that the request body contains the same token retrieved from the cookies.
- ///
- /// The CSRF parameter is expected as the `csrf` parameter in the request body.
- /// This means for HTML forms you need to embed the token as a hidden input.
- ///
- /// # Example
- ///
- /// ```
- /// use hyper::{Method};
- /// use sputnik::{Request, Response, Error};
- /// use sputnik::security::CsrfToken;
- /// use serde::Deserialize;
- ///
- /// #[derive(Deserialize)]
- /// struct Message {text: String}
- ///
- /// async fn greet(req: &mut Request) -> Result<Response, Error> {
- /// let mut response = Response::new();
- /// let csrf_token = CsrfToken::from_request(req, &mut response);
- /// *response.body() = match (req.method()) {
- /// &Method::GET => format!("<form method=post>
- /// <input name=text>{}<button>Submit</button></form>", csrf_token.html_input()).into(),
- /// &Method::POST => {
- /// let msg: Message = req.into_form_csrf(&csrf_token).await?;
- /// format!("hello {}", msg.text).into()
- /// },
- /// _ => return Err(Error::method_not_allowed("only GET and POST allowed".to_owned())),
- /// };
- /// Ok(response)
- /// }
- /// ```
- pub async fn into_form_csrf<T: DeserializeOwned>(&mut self, csrf_token: &security::CsrfToken) -> Result<T,Error> {
- enforce_content_type(&self.parts, APPLICATION_WWW_FORM_URLENCODED)?;
- let full_body = self.into_body().await?;
- let csrf_data = serde_urlencoded::from_bytes::<CsrfData>(&full_body).map_err(|_|Error::bad_request("no csrf token".to_string()))?;
- csrf_token.matches(csrf_data.csrf)?;
- serde_urlencoded::from_bytes::<T>(&full_body).map_err(|e|Error::bad_request(e.to_string()))
- }
-
- pub async fn into_body(&mut self) -> Result<Bytes,Error> {
- hyper::body::to_bytes(&mut self.body).await.map_err(|_|Error::internal("failed to read body".to_string()))
- }
-
- /// Parses the query string of the request into a given struct.
- pub fn query<T: DeserializeOwned>(&self) -> Result<T,Error> {
- serde_urlencoded::from_str::<T>(self.parts.uri.query().unwrap_or("")).map_err(|e|Error::bad_request(e.to_string()))
- }
-}
-
-/// Convenience wrapper around [`hyper::Response`].
-pub struct Response {
- res: HyperResponse
-}
-
-impl Response {
- pub fn new() -> Self {
- Response{res: HyperResponse::new(Body::empty())}
- }
-
- pub fn status(&mut self) -> &mut StatusCode {
- self.res.status_mut()
- }
-
- pub fn body(&mut self) -> &mut Body {
- self.res.body_mut()
- }
-
- pub fn headers(&mut self) -> &mut hyper::HeaderMap<header::HeaderValue> {
- self.res.headers_mut()
- }
-
- pub fn set_header<S: AsRef<str>>(&mut self, header: HeaderName, value: S) {
- self.res.headers_mut().insert(header, HeaderValue::from_str(value.as_ref()).unwrap());
- }
-
- pub fn set_content_type(&mut self, mime: mime::Mime) {
- self.res.headers_mut().insert(header::CONTENT_TYPE, mime.to_string().parse().unwrap());
- }
-
- pub fn redirect<S: AsRef<str>>(&mut self, location: S, code: StatusCode) {
- *self.res.status_mut() = code;
- self.set_header(header::LOCATION, location);
- }
-
- pub fn set_cookie(&mut self, cookie: Cookie) {
- self.res.headers_mut().append(header::SET_COOKIE, cookie.encoded().to_string().parse().unwrap());
- }
-
- pub fn delete_cookie(&mut self, name: &str) {
- let mut cookie = Cookie::new(name, "");
- cookie.set_max_age(Duration::seconds(0));
- cookie.set_expires(OffsetDateTime::now_utc() - Duration::days(365));
- self.set_cookie(cookie);
- }
-} \ No newline at end of file
+mod signed; \ No newline at end of file
diff --git a/src/request.rs b/src/request.rs
new file mode 100644
index 0000000..0c25e67
--- /dev/null
+++ b/src/request.rs
@@ -0,0 +1,159 @@
+//! Provides the [`Parts`] and [`Body`] convenience wrappers.
+
+use cookie::Cookie;
+use header::CONTENT_TYPE;
+use mime::{APPLICATION_WWW_FORM_URLENCODED, Mime};
+use serde::{Deserialize, de::DeserializeOwned};
+use hyper::{body::Bytes, header};
+use hyper::http::request::Parts as ReqParts;
+use std::collections::HashMap;
+
+use crate::{Error, security};
+
+type HyperRequest = hyper::Request<hyper::Body>;
+
+/// Convenience wrapper around [`hyper::Body`].
+pub struct Body {
+ body: hyper::Body,
+ content_type: Option<header::HeaderValue>,
+}
+
+/// Convert [`hyper::Request`] to ([`Parts`], [`Body`])
+pub fn adapt<'a>(req: HyperRequest) -> (Parts, Body) {
+ let (parts, body) = req.into_parts();
+ let body = Body{body, content_type: parts.headers.get(CONTENT_TYPE).map(|x| x.to_owned())};
+ let parts = Parts{parts, cookies: None};
+ (parts, body)
+}
+
+/// Convenience wrapper around [`hyper::http::request::Parts`].
+pub struct Parts {
+ parts: ReqParts,
+ cookies: Option<HashMap<String,Cookie<'static>>>,
+}
+
+#[derive(Deserialize)]
+struct CsrfData {
+ csrf: String,
+}
+
+impl Parts {
+ pub fn cookies(&mut self) -> &HashMap<String,Cookie> {
+ if let Some(ref cookies) = self.cookies {
+ return cookies
+ }
+ let mut cookies = HashMap::new();
+ for header in self.parts.headers.get_all(header::COOKIE) {
+ let raw_str = match std::str::from_utf8(header.as_bytes()) {
+ Ok(string) => string,
+ Err(_) => continue
+ };
+
+ for cookie_str in raw_str.split(';').map(|s| s.trim()) {
+ if let Ok(cookie) = Cookie::parse_encoded(cookie_str) {
+ cookies.insert(cookie.name().to_string(), cookie.into_owned());
+ }
+ }
+ }
+ self.cookies = Some(cookies);
+ &self.cookies.as_ref().unwrap()
+ }
+
+ pub fn method(&self) -> &hyper::Method {
+ &self.parts.method
+ }
+
+ pub fn headers(&self) -> &hyper::HeaderMap<header::HeaderValue> {
+ &self.parts.headers
+ }
+
+ pub fn uri(&self) -> &hyper::Uri {
+ &self.parts.uri
+ }
+
+ /// Parses the query string of the request into a given struct.
+ pub fn query<T: DeserializeOwned>(&self) -> Result<T,Error> {
+ serde_urlencoded::from_str::<T>(self.parts.uri.query().unwrap_or("")).map_err(|e|Error::bad_request(e.to_string()))
+ }
+}
+
+impl Body {
+ pub async fn into_bytes(self) -> Result<Bytes,Error> {
+ hyper::body::to_bytes(self.body).await.map_err(|_|Error::internal("failed to read body".to_string()))
+ }
+
+ /// Parses a `application/x-www-form-urlencoded` request body into a given struct.
+ ///
+ /// This does make you vulnerable to CSRF so you normally want to use
+ /// [`parse_form_csrf()`] instead.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use hyper::{Response};
+ /// use sputnik::{request::Body, Error};
+ /// use serde::Deserialize;
+ ///
+ /// #[derive(Deserialize)]
+ /// struct Message {text: String, year: i64}
+ ///
+ /// async fn greet(body: Body) -> Result<Response<hyper::Body>, Error> {
+ /// let msg: Message = body.into_form().await?;
+ /// Ok(Response::new(format!("hello {}", msg.text).into()))
+ /// }
+ /// ```
+ pub async fn into_form<T: DeserializeOwned>(self) -> Result<T,Error> {
+ self.enforce_content_type(APPLICATION_WWW_FORM_URLENCODED)?;
+ let full_body = self.into_bytes().await?;
+ serde_urlencoded::from_bytes::<T>(&full_body).map_err(|e|Error::bad_request(e.to_string()))
+ }
+
+ /// Parses a `application/x-www-form-urlencoded` request body into a given struct.
+ /// Protects from CSRF by checking that the request body contains the same token retrieved from the cookies.
+ ///
+ /// The CSRF parameter is expected as the `csrf` parameter in the request body.
+ /// This means for HTML forms you need to embed the token as a hidden input.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use hyper::{Method};
+ /// use sputnik::{request::{Parts, Body}, response::Response, Error};
+ /// use sputnik::security::CsrfToken;
+ /// use serde::Deserialize;
+ ///
+ /// #[derive(Deserialize)]
+ /// struct Message {text: String}
+ ///
+ /// async fn greet(req: &mut Parts, body: Body) -> Result<Response, Error> {
+ /// let mut response = Response::new();
+ /// let csrf_token = CsrfToken::from_parts(req, &mut response);
+ /// *response.body() = match (req.method()) {
+ /// &Method::GET => format!("<form method=post>
+ /// <input name=text>{}<button>Submit</button></form>", csrf_token.html_input()).into(),
+ /// &Method::POST => {
+ /// let msg: Message = body.into_form_csrf(&csrf_token).await?;
+ /// format!("hello {}", msg.text).into()
+ /// },
+ /// _ => return Err(Error::method_not_allowed("only GET and POST allowed".to_owned())),
+ /// };
+ /// Ok(response)
+ /// }
+ /// ```
+ pub async fn into_form_csrf<T: DeserializeOwned>(self, csrf_token: &security::CsrfToken) -> Result<T,Error> {
+ self.enforce_content_type(APPLICATION_WWW_FORM_URLENCODED)?;
+ let full_body = self.into_bytes().await?;
+ let csrf_data = serde_urlencoded::from_bytes::<CsrfData>(&full_body).map_err(|_|Error::bad_request("no csrf token".to_string()))?;
+ csrf_token.matches(csrf_data.csrf)?;
+ serde_urlencoded::from_bytes::<T>(&full_body).map_err(|e|Error::bad_request(e.to_string()))
+ }
+
+ fn enforce_content_type(&self, mime: Mime) -> Result<(),Error> {
+ if let Some(content_type) = &self.content_type {
+ if *content_type == mime.to_string() {
+ return Ok(())
+ }
+ }
+ Err(Error::bad_request(format!("expected content-type: {}", mime)))
+ }
+} \ No newline at end of file
diff --git a/src/response.rs b/src/response.rs
new file mode 100644
index 0000000..39e6c98
--- /dev/null
+++ b/src/response.rs
@@ -0,0 +1,60 @@
+//! Provides the [`Response`] convenience wrapper.
+
+use cookie::Cookie;
+use hyper::{StatusCode, header::{self, HeaderName, HeaderValue}};
+use time::{Duration, OffsetDateTime};
+
+type HyperResponse = hyper::Response<hyper::Body>;
+
+/// Convenience wrapper around [`hyper::Response`].
+pub struct Response {
+ res: HyperResponse
+}
+
+impl Into<HyperResponse> for Response {
+ fn into(self) -> HyperResponse {
+ self.res
+ }
+}
+
+impl Response {
+ pub fn new() -> Self {
+ Response{res: HyperResponse::new(hyper::Body::empty())}
+ }
+
+ pub fn status(&mut self) -> &mut StatusCode {
+ self.res.status_mut()
+ }
+
+ pub fn body(&mut self) -> &mut hyper::Body {
+ self.res.body_mut()
+ }
+
+ pub fn headers(&mut self) -> &mut hyper::HeaderMap<header::HeaderValue> {
+ self.res.headers_mut()
+ }
+
+ pub fn set_header<S: AsRef<str>>(&mut self, header: HeaderName, value: S) {
+ self.res.headers_mut().insert(header, HeaderValue::from_str(value.as_ref()).unwrap());
+ }
+
+ pub fn set_content_type(&mut self, mime: mime::Mime) {
+ self.res.headers_mut().insert(header::CONTENT_TYPE, mime.to_string().parse().unwrap());
+ }
+
+ pub fn redirect<S: AsRef<str>>(&mut self, location: S, code: StatusCode) {
+ *self.res.status_mut() = code;
+ self.set_header(header::LOCATION, location);
+ }
+
+ pub fn set_cookie(&mut self, cookie: Cookie) {
+ self.res.headers_mut().append(header::SET_COOKIE, cookie.encoded().to_string().parse().unwrap());
+ }
+
+ pub fn delete_cookie(&mut self, name: &str) {
+ let mut cookie = Cookie::new(name, "");
+ cookie.set_max_age(Duration::seconds(0));
+ cookie.set_expires(OffsetDateTime::now_utc() - Duration::days(365));
+ self.set_cookie(cookie);
+ }
+} \ No newline at end of file
diff --git a/src/security.rs b/src/security.rs
index fe9c26b..4a17fe3 100644
--- a/src/security.rs
+++ b/src/security.rs
@@ -6,9 +6,9 @@ use time::{Duration, OffsetDateTime};
pub use crate::signed::Key;
-use crate::{Error, Request, Response};
+use crate::{Error, request::Parts, response::Response};
-/// A cookie-based CSRF token to be used with [`crate::Request::into_form_csrf`].
+/// A cookie-based CSRF token to be used with [`crate::request::Body::into_form_csrf`].
pub struct CsrfToken {
token: String,
from_client: bool,
@@ -17,7 +17,7 @@ pub struct CsrfToken {
impl CsrfToken {
/// Retrieves the CSRF token from a `csrf` cookie or generates
/// a new token and stores it as a cookie if it doesn't exist.
- pub fn from_request(request: &mut Request, response: &mut Response) -> Self {
+ pub fn from_parts(request: &mut Parts, response: &mut Response) -> Self {
if let Some(cookie) = request.cookies().get("csrf") {
return CsrfToken{token: cookie.value().to_string(), from_client: true}
}