aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2021-01-25 22:38:06 +0100
committerMartin Fischer <martin@push-f.com>2021-01-25 22:50:40 +0100
commit3fd065757f02ebf1d055912e6809fac15c8bf058 (patch)
treef09e640f5c46111f5d63b68e5ec91bfbaee57541
parente04c832200f38ab49ce8e7a6d08ffc549b8a98e2 (diff)
reintroduce CsrfToken typev0.3.3
Raw HTML is potentially dangerous so it's better to provide an encapsulating type instead of relying on naked strings. bump version to 0.3.3
-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.