From d85d129e9aa43432c34e7dd2de15de86570d96d7 Mon Sep 17 00:00:00 2001 From: Alex Kotov Date: Sat, 17 Oct 2020 02:41:07 +0500 Subject: [PATCH] Add existing code --- .gitignore | 2 ++ Cargo.toml | 10 +++++++ src/lib.rs | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..042776a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/Cargo.lock +/target/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..525a763 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rocket_csrf" +version = "0.0.0" +authors = ["Alex Kotov "] +edition = "2018" + +[dependencies] +base64 = { version = "^0.13.0" } +rand = { version = "^0.7.3" } +rocket = { version = "^0.4.5", features = ["private-cookies"] } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0df9f95 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,86 @@ +use rand::RngCore; +use rocket::{Data, Request}; +use rocket::fairing::{Fairing as RocketFairing, Info, Kind}; +use rocket::http::{Cookie, Status}; +use rocket::request::{FromRequest, Outcome}; + +const COOKIE_NAME: &str = "csrf_token"; +const _PARAM_NAME: &str = "authenticity_token"; +const _HEADER_NAME: &str = "X-CSRF-Token"; +const _PARAM_META_NAME: &str = "csrf-param"; +const _TOKEN_META_NAME: &str = "csrf-token"; +const RAW_TOKEN_LENGTH: usize = 32; + +pub struct Fairing; + +pub struct Guard(pub String); + +pub struct VerificationFailure; + +impl Fairing { + pub fn new() -> Self { + Self {} + } +} + +impl Guard { + pub fn verify(&self, form_authenticity_token: &String) + -> Result<(), VerificationFailure> + { + if self.0 == *form_authenticity_token { + Ok(()) + } + else { + Err(VerificationFailure {}) + } + } +} + +impl RocketFairing for Fairing { + fn info(&self) -> Info { + Info { + name: "CSRF (Cross-Site Request Forgery) protection", + kind: Kind::Request, + } + } + + fn on_request(&self, request: &mut Request, _: &Data) { + if let Some(_) = request.valid_csrf_token_from_session() { return } + + let mut raw = [0u8; RAW_TOKEN_LENGTH]; + rand::thread_rng().fill_bytes(&mut raw); + + let encoded = base64::encode(raw); + + request.cookies().add_private(Cookie::new(COOKIE_NAME, encoded)); + } +} + +impl<'a, 'r> FromRequest<'a, 'r> for Guard { + type Error = (); + + fn from_request(request: &'a Request<'r>) -> Outcome { + match request.valid_csrf_token_from_session() { + None => Outcome::Failure((Status::Forbidden, ())), + Some(token) => Outcome::Success(Self(base64::encode(token))), + } + } +} + +trait RequestCsrf { + fn valid_csrf_token_from_session(&self) -> Option> { + self.csrf_token_from_session() + .and_then(|raw| + if raw.len() >= RAW_TOKEN_LENGTH { Some(raw) } else { None } + ) + } + + fn csrf_token_from_session(&self) -> Option>; +} + +impl RequestCsrf for Request<'_> { + fn csrf_token_from_session(&self) -> Option> { + self.cookies().get_private(COOKIE_NAME) + .and_then(|cookie| base64::decode(cookie.value()).ok()) + } +}