Bcrypt the Generated token for form implanting along with private cookies they wont be able to easily generate the original key. Started Config Options.
This commit is contained in:
parent
5257f9e644
commit
1148a492b0
|
@ -14,5 +14,7 @@ publish = true
|
|||
|
||||
[dependencies]
|
||||
base64 = { version = "0.13.0" }
|
||||
rand = { version = "0.7.3" }
|
||||
rocket = { version = "0.4.5", features = ["private-cookies"] }
|
||||
rand = { version = "0.8.3" }
|
||||
rocket = { version = "0.4.6", features = ["private-cookies"] }
|
||||
time = "0.2.25"
|
||||
bcrypt = "0.9"
|
109
src/lib.rs
109
src/lib.rs
|
@ -1,17 +1,43 @@
|
|||
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};
|
||||
use rand::{distributions::Standard, Rng};
|
||||
use rocket::{
|
||||
fairing::{Fairing as RocketFairing, Info, Kind},
|
||||
http::{Cookie, Status},
|
||||
request::{FromRequest, Outcome},
|
||||
Data, Request, Rocket, State,
|
||||
};
|
||||
use bcrypt::{hash, verify, DEFAULT_COST};
|
||||
use std::borrow::Cow;
|
||||
use time::Duration;
|
||||
|
||||
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;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CsrfConfig {
|
||||
/// CSRF Cookie lifespan
|
||||
lifespan: Duration,
|
||||
/// CSRF cookie name
|
||||
cookie_name: Cow<'static, str>,
|
||||
/// CSRF Token character length
|
||||
cookie_len: usize,
|
||||
}
|
||||
|
||||
impl Default for CsrfConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
/// Set to 6hour for default in Database Session stores.
|
||||
lifespan: Duration::day(),
|
||||
cookie_name: "csrf_token".into(),
|
||||
cookie_len: 32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Fairing {
|
||||
config: CsrfConfig,
|
||||
}
|
||||
|
||||
pub struct CsrfToken(String);
|
||||
|
||||
|
@ -19,22 +45,21 @@ pub struct VerificationFailure;
|
|||
|
||||
impl Fairing {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
Self {
|
||||
config: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CsrfToken {
|
||||
pub fn authenticity_token(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
pub fn authenticity_token(&self) -> String {
|
||||
hash(&self.0, DEFAULT_COST).unwrap()
|
||||
}
|
||||
|
||||
pub fn verify(&self, form_authenticity_token: &str)
|
||||
-> Result<(), VerificationFailure>
|
||||
{
|
||||
if self.0 == form_authenticity_token {
|
||||
pub fn verify(&self, form_authenticity_token: &String) -> Result<(), VerificationFailure> {
|
||||
if verify(&self.0, form_authenticity_token).unwrap_or(false) {
|
||||
Ok(())
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Err(VerificationFailure {})
|
||||
}
|
||||
}
|
||||
|
@ -44,27 +69,35 @@ impl RocketFairing for Fairing {
|
|||
fn info(&self) -> Info {
|
||||
Info {
|
||||
name: "CSRF",
|
||||
kind: Kind::Request,
|
||||
kind: Kind::Attach | 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));
|
||||
fn on_attach(&self, rocket: Rocket) -> std::result::Result<Rocket, Rocket> {
|
||||
Ok(rocket.manage(self.config.clone()))
|
||||
}
|
||||
|
||||
fn on_request(&self, request: &mut Request, _: &Data) {
|
||||
let config = request.guard::<State<CsrfConfig>>().unwrap();
|
||||
if let Some(_) = request.valid_csrf_token_from_session(&config) {
|
||||
return;
|
||||
}
|
||||
|
||||
let values: Vec<u8> = rand::thread_rng().sample_iter(Standard).take(config.cookie_len).collect();
|
||||
let encoded = base64::encode(&values[..]);
|
||||
|
||||
request
|
||||
.cookies()
|
||||
.add_private(Cookie::new(config.cookie_name.clone(), encoded));
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for CsrfToken {
|
||||
type Error = ();
|
||||
|
||||
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
|
||||
match request.valid_csrf_token_from_session() {
|
||||
let config = request.guard::<State<CsrfConfig>>().unwrap();
|
||||
match request.valid_csrf_token_from_session(&config) {
|
||||
None => Outcome::Failure((Status::Forbidden, ())),
|
||||
Some(token) => Outcome::Success(Self(base64::encode(token))),
|
||||
}
|
||||
|
@ -72,19 +105,23 @@ impl<'a, 'r> FromRequest<'a, 'r> for CsrfToken {
|
|||
}
|
||||
|
||||
trait RequestCsrf {
|
||||
fn valid_csrf_token_from_session(&self) -> Option<Vec<u8>> {
|
||||
self.csrf_token_from_session()
|
||||
.and_then(|raw|
|
||||
if raw.len() >= RAW_TOKEN_LENGTH { Some(raw) } else { None }
|
||||
)
|
||||
fn valid_csrf_token_from_session(&self, config: &CsrfConfig) -> Option<Vec<u8>> {
|
||||
self.csrf_token_from_session(config).and_then(|raw| {
|
||||
if raw.len() >= config.cookie_len {
|
||||
Some(raw)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn csrf_token_from_session(&self) -> Option<Vec<u8>>;
|
||||
fn csrf_token_from_session(&self, config: &CsrfConfig) -> Option<Vec<u8>>;
|
||||
}
|
||||
|
||||
impl RequestCsrf for Request<'_> {
|
||||
fn csrf_token_from_session(&self) -> Option<Vec<u8>> {
|
||||
self.cookies().get_private(COOKIE_NAME)
|
||||
fn csrf_token_from_session(&self, config: &CsrfConfig) -> Option<Vec<u8>> {
|
||||
self.cookies()
|
||||
.get_private(&config.cookie_name)
|
||||
.and_then(|cookie| base64::decode(cookie.value()).ok())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
use rand::RngCore;
|
||||
use rocket::http::Cookie;
|
||||
use rocket_csrf::CsrfToken;
|
||||
use bcrypt::verify;
|
||||
|
||||
fn client() -> rocket::local::Client {
|
||||
rocket::local::Client::new(rocket()).unwrap()
|
||||
|
@ -37,5 +38,5 @@ fn respond_with_valid_authenticity_token() {
|
|||
.into_string()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(body, encoded);
|
||||
assert!(verify(&encoded, &body).unwrap());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue