1
0
Fork 0

Add barebone CSRF protection fairing

This commit is contained in:
Alex Kotov 2020-10-16 10:33:52 +05:00
parent d8ecd274ff
commit df04180f09
Signed by: kotovalexarian
GPG Key ID: 553C0EBBEB5D5F08
5 changed files with 116 additions and 6 deletions

View File

@ -17,7 +17,6 @@ bcrypt = "0.8.2"
dotenv = "0.15.0"
r2d2 = "0.8.9"
regex = "1.4.1"
rocket = "0.4.5"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
@ -26,6 +25,10 @@ serde_json = "1.0"
version = "1.4.5"
features = ["postgres", "r2d2"]
[dependencies.rocket]
version = "0.4.5"
features = ["private-cookies"]
[dependencies.rocket_contrib]
version = "0.4.5"
features = ["diesel_postgres_pool", "handlebars_templates"]

101
src/csrf.rs Normal file
View File

@ -0,0 +1,101 @@
use rocket::{Data, Request, Response, Rocket};
use rocket::fairing::{Fairing as RocketFairing, Info, Kind};
use rocket::http::{Cookie};
const COOKIE_NAME: &str = "csrf_token";
const EXPIRE_TIME: u32 = 2_629_746; // 1 month
const REFRESH_TIME: u32 = 604_800; // 1 week
pub struct Fairing {
secret_key: String,
}
struct Token {
timestamp: u32,
value: String,
}
impl Fairing {
pub fn new(secret_key: String) -> Self {
Self { secret_key }
}
}
impl Token {
// TODO: implement this
fn generate() -> Self {
Self {
timestamp: 0,
value: "".to_string(),
}
}
fn from_cookie(cookie: &Cookie) -> Self {
Self::from_string(cookie.value().to_string())
}
// TODO: implement this
fn from_string(token: String) -> Self {
Self {
timestamp: 0,
value: "".to_string(),
}
}
// TODO: implement this
fn to_string(&self) -> String {
"".to_string()
}
// TODO: implement this
fn is_expired(&self) -> bool {
true
}
// TODO: implement this
fn is_refreshable(&self) -> bool {
true
}
fn not_expired_or_none(self) -> Option<Self> {
if self.is_expired() {
None
}
else {
Some(self)
}
}
fn not_refreshable_or_none(self) -> Option<Self> {
if self.is_refreshable() {
None
}
else {
Some(self)
}
}
}
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) {
let token: Option<Token> = request.cookies()
.get_private(COOKIE_NAME)
.and_then(|cookie| Some(Token::from_cookie(&cookie)))
.and_then(|token| token.not_refreshable_or_none());
if token.is_some() { return }
let new_token = Token::generate();
let mut new_cookie = Cookie::new(COOKIE_NAME, new_token.to_string());
request.cookies().add_private(new_cookie);
}
}

View File

@ -34,8 +34,9 @@ impl Deref for DbConn {
}
}
pub fn create_db_pool(config: Config) -> DbPool {
let manager = ConnectionManager::<PgConnection>::new(config.database_url);
pub fn create_db_pool(config: &Config) -> DbPool {
let manager =
ConnectionManager::<PgConnection>::new(config.database_url.to_string());
DbPool(Pool::new(manager).expect("Failed to create database pool"))
}

View File

@ -2,7 +2,8 @@
#[cfg(test)] mod tests;
pub mod config;
mod csrf;
mod config;
mod web;
mod database;
mod states;
@ -22,5 +23,5 @@ fn main() {
let config = config::Config::from_env().unwrap();
println!("Running with {:#?}", config);
println!("Public path: {:#?}", config.public_path().unwrap());
web::rocket(config).unwrap().launch();
web::rocket(&config).unwrap().launch();
}

View File

@ -1,3 +1,4 @@
use crate::csrf;
use crate::config;
use crate::database;
use crate::routes;
@ -5,13 +6,16 @@ use crate::routes;
use rocket_contrib::serve::{Options as ServeOptions, StaticFiles};
use rocket_contrib::templates::Template;
pub fn rocket(config: config::Config) -> Result<rocket::Rocket, ()> {
pub fn rocket(config: &config::Config) -> Result<rocket::Rocket, ()> {
let rocket_config = config.to_rocket_config()?;
let public_path = config.public_path()?;
let secret_key = config.secret_key.as_ref().unwrap().to_string();
let result = rocket::custom(rocket_config)
.manage(database::create_db_pool(config))
.attach(csrf::Fairing::new(secret_key))
.attach(Template::fairing())
.mount("/", routes::routes())
.mount("/", StaticFiles::new(public_path, ServeOptions::None));