Add barebone CSRF protection fairing
This commit is contained in:
parent
d8ecd274ff
commit
df04180f09
|
@ -17,7 +17,6 @@ bcrypt = "0.8.2"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
r2d2 = "0.8.9"
|
r2d2 = "0.8.9"
|
||||||
regex = "1.4.1"
|
regex = "1.4.1"
|
||||||
rocket = "0.4.5"
|
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
@ -26,6 +25,10 @@ serde_json = "1.0"
|
||||||
version = "1.4.5"
|
version = "1.4.5"
|
||||||
features = ["postgres", "r2d2"]
|
features = ["postgres", "r2d2"]
|
||||||
|
|
||||||
|
[dependencies.rocket]
|
||||||
|
version = "0.4.5"
|
||||||
|
features = ["private-cookies"]
|
||||||
|
|
||||||
[dependencies.rocket_contrib]
|
[dependencies.rocket_contrib]
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
features = ["diesel_postgres_pool", "handlebars_templates"]
|
features = ["diesel_postgres_pool", "handlebars_templates"]
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,8 +34,9 @@ impl Deref for DbConn {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_db_pool(config: Config) -> DbPool {
|
pub fn create_db_pool(config: &Config) -> DbPool {
|
||||||
let manager = ConnectionManager::<PgConnection>::new(config.database_url);
|
let manager =
|
||||||
|
ConnectionManager::<PgConnection>::new(config.database_url.to_string());
|
||||||
|
|
||||||
DbPool(Pool::new(manager).expect("Failed to create database pool"))
|
DbPool(Pool::new(manager).expect("Failed to create database pool"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
#[cfg(test)] mod tests;
|
#[cfg(test)] mod tests;
|
||||||
|
|
||||||
pub mod config;
|
mod csrf;
|
||||||
|
mod config;
|
||||||
mod web;
|
mod web;
|
||||||
mod database;
|
mod database;
|
||||||
mod states;
|
mod states;
|
||||||
|
@ -22,5 +23,5 @@ fn main() {
|
||||||
let config = config::Config::from_env().unwrap();
|
let config = config::Config::from_env().unwrap();
|
||||||
println!("Running with {:#?}", config);
|
println!("Running with {:#?}", config);
|
||||||
println!("Public path: {:#?}", config.public_path().unwrap());
|
println!("Public path: {:#?}", config.public_path().unwrap());
|
||||||
web::rocket(config).unwrap().launch();
|
web::rocket(&config).unwrap().launch();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::csrf;
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::routes;
|
use crate::routes;
|
||||||
|
@ -5,13 +6,16 @@ use crate::routes;
|
||||||
use rocket_contrib::serve::{Options as ServeOptions, StaticFiles};
|
use rocket_contrib::serve::{Options as ServeOptions, StaticFiles};
|
||||||
use rocket_contrib::templates::Template;
|
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 rocket_config = config.to_rocket_config()?;
|
||||||
|
|
||||||
let public_path = config.public_path()?;
|
let public_path = config.public_path()?;
|
||||||
|
|
||||||
|
let secret_key = config.secret_key.as_ref().unwrap().to_string();
|
||||||
|
|
||||||
let result = rocket::custom(rocket_config)
|
let result = rocket::custom(rocket_config)
|
||||||
.manage(database::create_db_pool(config))
|
.manage(database::create_db_pool(config))
|
||||||
|
.attach(csrf::Fairing::new(secret_key))
|
||||||
.attach(Template::fairing())
|
.attach(Template::fairing())
|
||||||
.mount("/", routes::routes())
|
.mount("/", routes::routes())
|
||||||
.mount("/", StaticFiles::new(public_path, ServeOptions::None));
|
.mount("/", StaticFiles::new(public_path, ServeOptions::None));
|
||||||
|
|
Reference in New Issue