diff options
Diffstat (limited to 'src/request.rs')
-rw-r--r-- | src/request.rs | 36 |
1 files changed, 27 insertions, 9 deletions
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. |