aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--README.md2
-rw-r--r--examples/csrf/src/main.rs2
-rw-r--r--src/request.rs36
4 files changed, 30 insertions, 12 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 1efc0de..91ced92 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "sputnik"
-version = "0.3.2"
+version = "0.3.3"
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 9892e21..4ee6456 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ async fn route(req: &mut Parts, body: Body) -> Result<Response, Error> {
fn get_form(req: &mut Parts) -> Response {
let mut response = Builder::new();
- let csrf_input = req.csrf_html_input(&mut response);
+ let csrf_input = req.csrf_token(&mut response).html_input();
response.content_type(mime::TEXT_HTML).body(
format!("<form method=post>
<input name=text>{}<button>Submit</button></form>", csrf_input).into()
diff --git a/examples/csrf/src/main.rs b/examples/csrf/src/main.rs
index 94fd09c..1048689 100644
--- a/examples/csrf/src/main.rs
+++ b/examples/csrf/src/main.rs
@@ -34,7 +34,7 @@ async fn route(req: &mut Parts, body: Body) -> Result<Response, Error> {
fn get_form(req: &mut Parts) -> Response {
let mut response = Builder::new();
- let csrf_input = req.csrf_html_input(&mut response);
+ let csrf_input = req.csrf_token(&mut response).html_input();
response.content_type(mime::TEXT_HTML).body(
format!("<form method=post>
<input name=text>{}<button>Submit</button></form>", csrf_input).into()
diff --git a/src/request.rs b/src/request.rs
index 5b5679d..7734d03 100644
--- a/src/request.rs
+++ b/src/request.rs
@@ -23,11 +23,9 @@ pub trait SputnikParts {
/// Enforces a specific Content-Type.
fn enforce_content_type(&self, mime: Mime) -> Result<(), WrongContentTypeError>;
- /// Retrievs the CSRF token from a cookie or generates
- /// a new token and stores it as a cookie if it doesn't exist.
- /// Returns a hidden HTML input to be embedded in forms that are received
- /// with [`crate::request::SputnikBody::into_form_csrf`].
- fn csrf_html_input(&mut self, builder: &mut Builder) -> String;
+ /// Returns a CSRF token, either extracted from the `csrf` cookie or newly
+ /// generated if the cookie wasn't sent (in which case the cookie is set).
+ fn csrf_token(&mut self, builder: &mut Builder) -> CsrfToken;
}
impl SputnikParts for hyper::http::request::Parts {
@@ -68,16 +66,36 @@ impl SputnikParts for hyper::http::request::Parts {
Err(WrongContentTypeError{expected: mime, received: self.headers.get(header::CONTENT_TYPE).as_ref().and_then(|h| h.to_str().ok().map(|s| s.to_owned()))})
}
- fn csrf_html_input(&mut self, builder: &mut Builder) -> String {
+ fn csrf_token(&mut self, builder: &mut Builder) -> CsrfToken {
let token = csrf_token_from_cookies(self).unwrap_or_else(|| {
- let token: String = rand::thread_rng().sample_iter(Alphanumeric).take(16).collect();
+ let token: String = rand::thread_rng().sample_iter(
+ Alphanumeric
+ // must be HTML-safe because we embed it in CsrfToken::html_input
+ ).take(16).collect();
let mut c = Cookie::new(CSRF_COOKIE_NAME, token.clone());
c.set_secure(Some(true));
c.set_max_age(Some(Duration::hours(1)));
builder.set_cookie(c);
token
});
- format!("<input name=csrf type=hidden value=\"{}\">", token)
+ CsrfToken(token)
+ }
+}
+
+/// A CSRF token retrievable with [`SputnikParts::csrf_token`].
+pub struct CsrfToken(String);
+
+impl std::fmt::Display for CsrfToken {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl CsrfToken {
+ /// Returns a hidden HTML input to be embedded in forms that are received
+ /// with [`crate::request::SputnikBody::into_form_csrf`].
+ pub fn html_input(&self) -> String {
+ format!("<input name=csrf type=hidden value=\"{}\">", self)
}
}
@@ -94,7 +112,7 @@ pub trait SputnikBody {
/// 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 HTML form must embed a hidden input generated with [`crate::request::SputnikParts::csrf_html_input`].
+ /// The HTML form must embed a hidden input generated with [`CsrfToken::html_input`].
async fn into_form_csrf<T: DeserializeOwned>(self, req: &mut Parts) -> Result<T, CsrfProtectedFormError>;
/// Attempts to deserialize the request body as JSON.